From a54c38b0979ee1f10daff1274467fd4d2832d80e Mon Sep 17 00:00:00 2001 From: Serhii Zautkin Date: Mon, 2 Mar 2026 14:16:32 -0800 Subject: [PATCH 1/5] CADC-14486 Path fix1 --- .../src/app/api/auth/[...nextauth]/route.ts | 26 +------------------ .../src/auth/cadc-auth/credentials.ts | 3 +++ 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/rafts/frontend/src/app/api/auth/[...nextauth]/route.ts b/rafts/frontend/src/app/api/auth/[...nextauth]/route.ts index af53c8e5..370ff0cb 100644 --- a/rafts/frontend/src/app/api/auth/[...nextauth]/route.ts +++ b/rafts/frontend/src/app/api/auth/[...nextauth]/route.ts @@ -65,30 +65,6 @@ ************************************************************************ */ -import { NextRequest } from 'next/server' import { GET as AuthGET, POST as AuthPOST } from '@/auth/cadc-auth/credentials' -const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '' - -// Fix the URL path for NextAuth v5 which doesn't handle base paths well -function fixUrl(req: NextRequest): NextRequest { - if (!basePath) return req - - const url = new URL(req.url) - // Remove the base path from the pathname for NextAuth processing - if (url.pathname.startsWith(`${basePath}/api/auth`)) { - url.pathname = url.pathname.replace(basePath, '') - } - - return new NextRequest(url, req) -} - -export async function GET(req: NextRequest) { - const fixedReq = fixUrl(req) - return AuthGET(fixedReq) -} - -export async function POST(req: NextRequest) { - const fixedReq = fixUrl(req) - return AuthPOST(fixedReq) -} +export { AuthGET as GET, AuthPOST as POST } diff --git a/rafts/frontend/src/auth/cadc-auth/credentials.ts b/rafts/frontend/src/auth/cadc-auth/credentials.ts index 6f706b1e..50b458b3 100644 --- a/rafts/frontend/src/auth/cadc-auth/credentials.ts +++ b/rafts/frontend/src/auth/cadc-auth/credentials.ts @@ -72,12 +72,15 @@ import { fetchUserInfo } from './fetchUserInfo' import { fetchUserGroups } from './fetchUserGroups' import { authenticateUser } from '@/auth/cadc-auth/authenticateUser' +const appBasePath = process.env.NEXT_PUBLIC_BASE_PATH || '' + export const { handlers: { GET, POST }, auth, signIn, signOut, } = NextAuth({ + basePath: `${appBasePath}/api/auth`, providers: [ CredentialsProvider({ name: 'CADC Login', From 1a73ec3fda53c67327c0be5e6aee7bdfeeb295e7 Mon Sep 17 00:00:00 2001 From: Serhii Zautkin Date: Thu, 5 Mar 2026 10:04:02 -0800 Subject: [PATCH 2/5] CADC-14486 Fixes: implement direct URL support for attachment downloads and update base path configuration. --- rafts/.env.example | 2 +- rafts/frontend/src/actions/attachments.ts | 3 ++- .../app/api/attachments/[doiId]/[filename]/route.ts | 5 ++++- .../{route-wrapper.ts => route-wrapper.old.ts} | 0 rafts/frontend/src/auth/cadc-auth/credentials.ts | 3 --- .../{parseUserGroups.ts => parseUserGroups.old.ts} | 0 .../{authorization.ts => authorization.old.ts} | 0 .../src/auth/{credentials.ts => credentials.old.ts} | 0 rafts/frontend/src/auth/{types.ts => types.old.ts} | 0 .../components/Form/FileUpload/FileUploadImage.tsx | 2 +- .../src/components/Form/FormLayoutWithContext.tsx | 8 ++++++++ .../src/components/RaftDetail/tabs/OverviewTab.tsx | 8 ++++---- .../src/components/common/AttachmentImage.tsx | 10 +++++++--- .../src/components/common/AttachmentText.tsx | 8 ++++++-- rafts/frontend/src/context/RaftFormContext.tsx | 6 +++++- rafts/frontend/src/hooks/useAttachmentUpload.ts | 13 +++++++++---- rafts/frontend/src/services/attachmentService.ts | 8 +++++--- rafts/frontend/src/types/attachments.ts | 4 ++++ 18 files changed, 56 insertions(+), 24 deletions(-) rename rafts/frontend/src/app/api/auth/[...nextauth]/{route-wrapper.ts => route-wrapper.old.ts} (100%) rename rafts/frontend/src/auth/cadc-auth/utils/{parseUserGroups.ts => parseUserGroups.old.ts} (100%) rename rafts/frontend/src/auth/config/{authorization.ts => authorization.old.ts} (100%) rename rafts/frontend/src/auth/{credentials.ts => credentials.old.ts} (100%) rename rafts/frontend/src/auth/{types.ts => types.old.ts} (100%) diff --git a/rafts/.env.example b/rafts/.env.example index dbb34d3c..614da0a0 100644 --- a/rafts/.env.example +++ b/rafts/.env.example @@ -57,7 +57,7 @@ NEXTAUTH_DEBUG=false # ============================================================================= # Base path prefix (empty for root, or '/subpath' for subdomain deployment) -NEXT_PUBLIC_BASE_PATH= +NEXT_PUBLIC_BASE_PATH="/rafts" # Public API URL (usually same as NEXTAUTH_URL + /api) NEXT_PUBLIC_API_URL=http://localhost:3000/api diff --git a/rafts/frontend/src/actions/attachments.ts b/rafts/frontend/src/actions/attachments.ts index 085dc2e6..06d73030 100644 --- a/rafts/frontend/src/actions/attachments.ts +++ b/rafts/frontend/src/actions/attachments.ts @@ -174,6 +174,7 @@ export async function downloadAttachment( doiIdentifier: string, filename: string, asText: boolean = false, + directUrl?: string, ): Promise { const session = await auth() const accessToken = session?.accessToken @@ -183,7 +184,7 @@ export async function downloadAttachment( } try { - const result = await downloadFromVOSpace(doiIdentifier, filename, accessToken, asText) + const result = await downloadFromVOSpace(doiIdentifier, filename, accessToken, asText, directUrl) if (!result.success || !result.content) { return { success: false, error: result.error || 'Download failed' } diff --git a/rafts/frontend/src/app/api/attachments/[doiId]/[filename]/route.ts b/rafts/frontend/src/app/api/attachments/[doiId]/[filename]/route.ts index 38c95f9e..ec8d67f7 100644 --- a/rafts/frontend/src/app/api/attachments/[doiId]/[filename]/route.ts +++ b/rafts/frontend/src/app/api/attachments/[doiId]/[filename]/route.ts @@ -95,8 +95,11 @@ export async function GET( // Decode the filename (it may be URL encoded) const decodedFilename = decodeURIComponent(filename) + // Use the vault URL from the FileReference JSON if provided + const directUrl = request.nextUrl.searchParams.get('url') || undefined + // Download from VOSpace - const result = await downloadAttachment(doiId, decodedFilename, accessToken, false) + const result = await downloadAttachment(doiId, decodedFilename, accessToken, false, directUrl) if (!result.success || !result.content) { console.error('[API attachments] Download failed:', result.error) diff --git a/rafts/frontend/src/app/api/auth/[...nextauth]/route-wrapper.ts b/rafts/frontend/src/app/api/auth/[...nextauth]/route-wrapper.old.ts similarity index 100% rename from rafts/frontend/src/app/api/auth/[...nextauth]/route-wrapper.ts rename to rafts/frontend/src/app/api/auth/[...nextauth]/route-wrapper.old.ts diff --git a/rafts/frontend/src/auth/cadc-auth/credentials.ts b/rafts/frontend/src/auth/cadc-auth/credentials.ts index 50b458b3..6f706b1e 100644 --- a/rafts/frontend/src/auth/cadc-auth/credentials.ts +++ b/rafts/frontend/src/auth/cadc-auth/credentials.ts @@ -72,15 +72,12 @@ import { fetchUserInfo } from './fetchUserInfo' import { fetchUserGroups } from './fetchUserGroups' import { authenticateUser } from '@/auth/cadc-auth/authenticateUser' -const appBasePath = process.env.NEXT_PUBLIC_BASE_PATH || '' - export const { handlers: { GET, POST }, auth, signIn, signOut, } = NextAuth({ - basePath: `${appBasePath}/api/auth`, providers: [ CredentialsProvider({ name: 'CADC Login', diff --git a/rafts/frontend/src/auth/cadc-auth/utils/parseUserGroups.ts b/rafts/frontend/src/auth/cadc-auth/utils/parseUserGroups.old.ts similarity index 100% rename from rafts/frontend/src/auth/cadc-auth/utils/parseUserGroups.ts rename to rafts/frontend/src/auth/cadc-auth/utils/parseUserGroups.old.ts diff --git a/rafts/frontend/src/auth/config/authorization.ts b/rafts/frontend/src/auth/config/authorization.old.ts similarity index 100% rename from rafts/frontend/src/auth/config/authorization.ts rename to rafts/frontend/src/auth/config/authorization.old.ts diff --git a/rafts/frontend/src/auth/credentials.ts b/rafts/frontend/src/auth/credentials.old.ts similarity index 100% rename from rafts/frontend/src/auth/credentials.ts rename to rafts/frontend/src/auth/credentials.old.ts diff --git a/rafts/frontend/src/auth/types.ts b/rafts/frontend/src/auth/types.old.ts similarity index 100% rename from rafts/frontend/src/auth/types.ts rename to rafts/frontend/src/auth/types.old.ts diff --git a/rafts/frontend/src/components/Form/FileUpload/FileUploadImage.tsx b/rafts/frontend/src/components/Form/FileUpload/FileUploadImage.tsx index dbbccc6b..d3533e68 100644 --- a/rafts/frontend/src/components/Form/FileUpload/FileUploadImage.tsx +++ b/rafts/frontend/src/components/Form/FileUpload/FileUploadImage.tsx @@ -441,7 +441,7 @@ const FileUploadImage: React.FC = ({ )} {/* Use regular img for API routes, Next.js Image for base64 */} - {imagePreview.startsWith('/api/') ? ( + {imagePreview.includes('/api/attachments/') ? ( // eslint-disable-next-line @next/next/no-img-element { dispatchAlert({ type: 'show', severity: 'success', message: t('submission_success') }) setFormIsDirty(DIRTY_FORM) + // After a successful save, validate all sections to update checkmarks and Submit button + console.log('[handleSubmit] res.success:', res.success, 'isDraft:', isDraft) + if (isDraft) { + console.log('[handleSubmit] calling validateAllSections') + const allValid = validateAllSections(syncedData) + console.log('[handleSubmit] validateAllSections returned:', allValid) + } + if (isDraft && isNewRaft && res.data) { const newId = typeof res.data === 'string' ? res.data.split('/').pop() : null if (newId) { diff --git a/rafts/frontend/src/components/RaftDetail/tabs/OverviewTab.tsx b/rafts/frontend/src/components/RaftDetail/tabs/OverviewTab.tsx index 07fecbdf..c14beeff 100644 --- a/rafts/frontend/src/components/RaftDetail/tabs/OverviewTab.tsx +++ b/rafts/frontend/src/components/RaftDetail/tabs/OverviewTab.tsx @@ -264,8 +264,8 @@ export default function OverviewTab({ Acknowledgements - - + + {acknowledgements} @@ -276,8 +276,8 @@ export default function OverviewTab({ Related RAFTSs - - + + {relatedPublishedRafts} diff --git a/rafts/frontend/src/components/common/AttachmentImage.tsx b/rafts/frontend/src/components/common/AttachmentImage.tsx index d42d3cd9..f27768a0 100644 --- a/rafts/frontend/src/components/common/AttachmentImage.tsx +++ b/rafts/frontend/src/components/common/AttachmentImage.tsx @@ -160,8 +160,12 @@ export default function AttachmentImage({ const parsed = typeof value === 'string' ? parseStoredAttachment(value) : value if (isFileReference(parsed) && doiId) { - // It's a FileReference - use API route - const apiUrl = `/api/attachments/${doiId}/${encodeURIComponent(parsed.filename)}` + // It's a FileReference - use API route, pass stored url if available + const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '' + let apiUrl = `${basePath}/api/attachments/${doiId}/${encodeURIComponent(parsed.filename)}` + if (parsed.url) { + apiUrl += `?url=${encodeURIComponent(parsed.url)}` + } setImageUrl(apiUrl) setIsLoading(true) // Start loading - will be set to false by onLoad/onError lastResolvedRef.current = valueKey @@ -180,7 +184,7 @@ export default function AttachmentImage({ return null } - const isApiRoute = imageUrl.startsWith('/api/') + const isApiRoute = imageUrl.includes('/api/attachments/') return ( <> diff --git a/rafts/frontend/src/components/common/AttachmentText.tsx b/rafts/frontend/src/components/common/AttachmentText.tsx index 6981045a..0d8bb6c9 100644 --- a/rafts/frontend/src/components/common/AttachmentText.tsx +++ b/rafts/frontend/src/components/common/AttachmentText.tsx @@ -139,11 +139,15 @@ export default function AttachmentText({ const parsed = typeof value === 'string' ? parseStoredAttachment(value) : value if (isFileReference(parsed) && doiId) { - // It's a FileReference - fetch from API + // It's a FileReference - fetch from API, pass stored url if available setIsLoading(true) setError(null) - const apiUrl = `/api/attachments/${doiId}/${encodeURIComponent(parsed.filename)}` + const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '' + let apiUrl = `${basePath}/api/attachments/${doiId}/${encodeURIComponent(parsed.filename)}` + if (parsed.url) { + apiUrl += `?url=${encodeURIComponent(parsed.url)}` + } fetch(apiUrl) .then(async (response) => { diff --git a/rafts/frontend/src/context/RaftFormContext.tsx b/rafts/frontend/src/context/RaftFormContext.tsx index 8a56789c..e63fcca0 100644 --- a/rafts/frontend/src/context/RaftFormContext.tsx +++ b/rafts/frontend/src/context/RaftFormContext.tsx @@ -330,7 +330,11 @@ export function RaftFormProvider({ const isValid = validateWithSchema(VALIDATION_SCHEMAS[section], sectionData) newValidation[section] = isValid if (!isValid) { - newErrors[section] = getValidationErrors(VALIDATION_SCHEMAS[section], sectionData) + const errors = getValidationErrors(VALIDATION_SCHEMAS[section], sectionData) + newErrors[section] = errors + console.log(`[validateAllSections] FAILED: ${section}`, { sectionData, errors }) + } else { + console.log(`[validateAllSections] PASSED: ${section}`) } } diff --git a/rafts/frontend/src/hooks/useAttachmentUpload.ts b/rafts/frontend/src/hooks/useAttachmentUpload.ts index b37f37d9..b85920bc 100644 --- a/rafts/frontend/src/hooks/useAttachmentUpload.ts +++ b/rafts/frontend/src/hooks/useAttachmentUpload.ts @@ -145,7 +145,7 @@ export interface UseAttachmentUploadReturn { /** Resolve an attachment value to displayable content */ resolveAttachment: (value: AttachmentValue) => Promise /** Get the API URL for viewing an attachment (for images) */ - getAttachmentUrl: (filename: string) => string | null + getAttachmentUrl: (filename: string, vaultUrl?: string) => string | null } // ============================================================================ @@ -177,9 +177,14 @@ export function useAttachmentUpload( * Get the API URL for viewing an attachment (for images) */ const getAttachmentUrl = useCallback( - (filename: string): string | null => { + (filename: string, vaultUrl?: string): string | null => { if (!doiIdentifier) return null - return `/api/attachments/${doiIdentifier}/${encodeURIComponent(filename)}` + const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '' + let apiUrl = `${basePath}/api/attachments/${doiIdentifier}/${encodeURIComponent(filename)}` + if (vaultUrl) { + apiUrl += `?url=${encodeURIComponent(vaultUrl)}` + } + return apiUrl }, [doiIdentifier], ) @@ -402,7 +407,7 @@ export function useAttachmentUpload( // For images, use the API route URL (avoids downloading to client memory) if (value.mimeType.startsWith('image/')) { - return getAttachmentUrl(value.filename) + return getAttachmentUrl(value.filename, value.url) } // For text files, download via server action diff --git a/rafts/frontend/src/services/attachmentService.ts b/rafts/frontend/src/services/attachmentService.ts index 96c8e230..b5d6e889 100644 --- a/rafts/frontend/src/services/attachmentService.ts +++ b/rafts/frontend/src/services/attachmentService.ts @@ -353,8 +353,9 @@ export async function uploadAttachment( throw new Error(`Upload failed: ${response.status} ${errorText}`) } - // Create and return FileReference - const fileReference = createFileReference(sanitizedFilename, mimeType, contentLength) + // Create and return FileReference with the full download URL + const downloadUrl = getAttachmentDownloadUrl(doiIdentifier, sanitizedFilename) + const fileReference = createFileReference(sanitizedFilename, mimeType, contentLength, downloadUrl) return { success: true, fileReference } } catch (error) { @@ -405,9 +406,10 @@ export async function downloadAttachment( filename: string, accessToken: string, asText: boolean = false, + directUrl?: string, ): Promise { try { - const url = getAttachmentDownloadUrl(doiIdentifier, filename) + const url = directUrl || getAttachmentDownloadUrl(doiIdentifier, filename) const response = await fetch(url, { method: 'GET', diff --git a/rafts/frontend/src/types/attachments.ts b/rafts/frontend/src/types/attachments.ts index 02c2d8c8..5ed0b1ca 100644 --- a/rafts/frontend/src/types/attachments.ts +++ b/rafts/frontend/src/types/attachments.ts @@ -88,6 +88,8 @@ export interface FileReference { size: number /** ISO timestamp when uploaded */ uploadedAt: string + /** Full download URL for the file in vault */ + url?: string } /** @@ -246,6 +248,7 @@ export function createFileReference( filename: string, mimeType: string, size: number, + url?: string, ): FileReference { return { type: 'file-reference', @@ -253,6 +256,7 @@ export function createFileReference( mimeType, size, uploadedAt: new Date().toISOString(), + ...(url && { url }), } } From 443fdd5f534f25d2208c2b4c72a3ade7a81a691f Mon Sep 17 00:00:00 2001 From: Serhii Zautkin Date: Thu, 5 Mar 2026 13:16:06 -0800 Subject: [PATCH 3/5] CADC-14486 Fixes: Improve form validation feedback, optimize file upload components, and refine DOI minting API call. --- rafts/frontend/messages/en.json | 30 +++++---- rafts/frontend/messages/fr.json | 30 +++++---- rafts/frontend/src/actions/publishRaftDoi.ts | 11 ++++ .../Form/FileUpload/ADESFileUpload.tsx | 24 ++++--- .../Form/FileUpload/FileUploadImage.tsx | 24 ++++--- .../Form/FileUpload/TextFileUpload.tsx | 39 +++++++---- .../components/Form/FormLayoutWithContext.tsx | 66 +++++++++++-------- .../components/Form/MiscellaneousInfoForm.tsx | 1 + .../src/components/Form/ReviewForm.tsx | 2 +- .../components/ReviewerSidePanel.tsx | 20 ++++-- .../frontend/src/context/RaftFormContext.tsx | 9 +-- 11 files changed, 161 insertions(+), 95 deletions(-) diff --git a/rafts/frontend/messages/en.json b/rafts/frontend/messages/en.json index 7788cce3..0bcbd273 100644 --- a/rafts/frontend/messages/en.json +++ b/rafts/frontend/messages/en.json @@ -163,9 +163,13 @@ "reset_form": "Reset Form", "modal_reset_title": "Reset Form", "confirm_reset": "Are you sure you want to reset the form? All your progress will be lost.", + "draft_save_success": "Your draft has been saved successfully.", + "draft_save_error": "There was an error saving your draft. Please try again.", "submission_success": "Your RAFTS has been submitted successfully!", "submission_error": "There was an error submitting your RAFTS. Please try again.", "validation_incomplete": "Please complete all required sections before submitting.", + "section_validated": "Section validated successfully.", + "section_validation_errors": "Please fix the errors in this section.", "form_reset": "Form has been reset.", "corresponding": "Corresponding", "review_title": "Review Your RAFTS Submission", @@ -362,14 +366,14 @@ }, "tutorial": { "step_title": "Welcome to the RAFTS submission form! This is where you'll enter the title of your astronomical observation or discovery. Make your title clear, descriptive, and specific to help others understand your work.", - "step_navigation": "This navigation bar shows your progress through the 5-step submission process. Green circles indicate completed sections, and you can click on any completed step to return to it. The connecting lines show your progression through the form.", + "step_navigation": "This navigation bar shows your progress through the 5-step submission process. Green circles indicate completed sections. You can click on any step to navigate freely between sections. The connecting lines show your progression through the form.", "step_author_info": "Step 1: Author Information - Enter details about the corresponding author (primary contact) and any contributing authors. Include names, affiliations, emails, and ORCID identifiers when available. All required fields must be completed.", - "step_announcement": "Step 2: Announcement - Provide the core information about your astronomical observation including the topic classification, object name/designation, a detailed abstract, and any supporting figures or images.", - "step_observation": "Step 3: Observation Details - Add technical information about your observation including telescope specifications, coordinates, photometric measurements, and upload supporting data files like spectra or astrometry.", + "step_announcement": "Step 2: Announcement Info - Provide the core information about your astronomical observation including the topic classification, object name/designation, a detailed abstract, and any supporting figures or images.", + "step_observation": "Step 3: Observation Information - Add technical information about your observation including telescope, identifiers, photometric measurements, and upload supporting data files like spectra or astrometry.", "step_miscellaneous": "Step 4: Miscellaneous - Include any additional information using key-value pairs for parameters like orbital periods, magnitudes, or drift rates. This section helps provide complete context for your observation.", "step_review": "Step 5: Review - Final step to carefully review all your information before submitting. You can save as a draft to continue later, or submit for peer review when ready.", "step_progress": "The progress indicator shows which sections are complete. Green circles indicate finished sections, blue shows your current location, and gray indicates incomplete sections.", - "step_save_as_draft": "Save as Draft - Use this button to save your work at any time. Your progress will be preserved and you can continue editing later. Note: This button is disabled until you enter minimal author information and a RAFTS title.", + "step_save_as_draft": "Save as Draft - Use this button to save your work at any time. Your progress will be preserved and you can continue editing later. Note: This button is disabled until you enter a RAFTS title.", "step_submit": "Submit - When all required sections are complete, use this button to submit your RAFTS for peer review. This button is only enabled when all mandatory fields are filled out.", "button_back": "Back", "button_close": "Close", @@ -381,23 +385,23 @@ "author_contributing": "Contributing authors are colleagues who participated in the observation, analysis, or interpretation of the data. Add each author with their complete information including name, email, and institutional affiliation. ORCID IDs are highly recommended for all authors.", "author_collaborations": "Collaborations are institutional or organizational groups that contributed to this work. Unlike individual authors, collaborations are listed by name only (e.g., 'NEOWISE Team', 'Catalina Sky Survey'). Add collaboration names that should be credited for this observation.", "author_add_button": "Click this 'Add Author' button to include additional contributing authors to your submission. You can add as many authors as necessary and remove them if needed using the delete button next to each entry.", - "author_save": "Important: Click 'Validate' to store your author information before proceeding to the next section. Your progress will be lost if you navigate away without saving.", - "announcement_section_welcome": "Welcome to the Announcement section! This is the heart of your RAFTS submission where you'll describe your astronomical observation or discovery. Provide clear, comprehensive information to help the scientific community understand your findings.", + "author_save": "Click 'Validate' to check your author information for completeness. A green checkmark will appear in the navigation if all required fields are filled. Use 'Save as Draft' to persist your work to the server.", + "announcement_section_welcome": "Welcome to the Announcement Info section! This is the heart of your RAFTS submission where you'll describe your astronomical observation or discovery. Provide clear, comprehensive information to help the scientific community understand your findings.", "announcement_topic": "Select the topic category that best describes your observation. Choose from options like 'Comet', 'Near Earth Object', 'Trans-Neptunian Object', or other celestial object types. This helps categorize your submission appropriately.", "announcement_object": "Enter the official name, designation, or identifier of the astronomical object you observed. Use standard nomenclature when possible (e.g., 'C/2023 A1', '2023 AB', 'NGC 1234'). If it's a new discovery, provide your provisional designation.", "announcement_abstract": "Write a comprehensive abstract describing your observation, methodology, key findings, and their scientific significance. Include important details like observation dates, instruments used, and notable characteristics. Maximum 2000 characters - make every word count!", "announcement_figure": "Upload supporting images, charts, or figures that illustrate your observation. This could include photographs, light curves, spectra, or finder charts. Images help reviewers and readers better understand your work.", - "announcement_save": "Save your announcement information to lock in your progress and proceed to the technical details section. Review your abstract carefully before saving as this is a key part of your submission.", - "observation_section_welcome": "Welcome to the Observation Details section! Here you'll provide the technical specifications and measurements that support your astronomical observation. This data is crucial for scientific verification and follow-up studies.", - "observation_coordinates": "Provide precise coordinates and positional information for your observed object. Include MPC ID numbers for known objects, alert IDs for transient events, and Modified Julian Date (MJD) for time-sensitive observations.", + "announcement_save": "Click 'Validate' to check your announcement information for completeness. A green checkmark will appear in the navigation if all required fields are filled. You can also add optional Acknowledgements and Previously Published RAFTs references above this button. Use 'Save as Draft' to persist your work to the server.", + "observation_section_welcome": "Welcome to the Observation Information section! Here you'll provide the technical specifications and measurements that support your astronomical observation. This data is crucial for scientific verification and follow-up studies.", + "observation_coordinates": "Provide identifiers and timing information for your observed object. Enter MPC ID numbers for known objects, alert IDs for transient events, and Modified Julian Date (MJD) for time-sensitive observations. All fields in this section are optional.", "observation_brightness": "Enter photometric measurements including wavelength/filter information, brightness values, and associated uncertainties. Use standard astronomical magnitude systems and specify your photometric bands (e.g., V, R, I, g', r', i').", - "observation_telescope": "Specify the telescope and instrumentation used for your observation. Include telescope aperture, focal ratio, detector type, and any relevant technical specifications that affect the quality and interpretation of your data.", + "observation_telescope": "Enter the telescope and instrument used for your observation (e.g., 'Gemini North / GMOS'). You can include details like aperture or detector type in a single entry. This field is optional.", "observation_files": "Upload supporting data files that validate your observation. This can include ephemeris files, orbital elements, spectroscopic data, or astrometric measurements. Use standard formats when possible (ADES for astrometry, etc.).", - "observation_save": "Save your technical observation details to preserve your work. Ensure all critical measurements and data files are included before proceeding to the final section.", + "observation_save": "Click 'Validate' to check your observation details for completeness. A green checkmark will appear in the navigation if all required fields are filled. Use 'Save as Draft' to persist your work to the server.", "misc_section_welcome": "Welcome to the Miscellaneous Information section! This is where you can add any additional context, parameters, or details that don't fit in the previous sections but are important for understanding your observation.", "misc_key_value": "Use the key-value pair system to record specific parameters relevant to your observation. Examples: 'Period' = '6.2 hours', 'Absolute Magnitude' = '15.3', 'Yarkovsky drift' = '0.01 au/Myr'. Each pair should contain meaningful scientific information.", - "misc_additional_files": "Click 'Add Additional Information' to include more key-value pairs for parameters like rotational periods, color indices, orbital elements, outburst magnitudes, or any other quantitative data relevant to your observation.", - "misc_save": "Save your miscellaneous information to complete this section. Review all your key-value pairs to ensure they're accurate and provide meaningful scientific context for your submission.", + "misc_additional_files": "Use 'Add Text' to include key-value pairs for parameters like rotational periods, color indices, or outburst magnitudes. Use 'Add File' to attach supporting documents. You can add as many entries as needed and remove them with the delete button.", + "misc_save": "Click 'Validate' to check your miscellaneous information for completeness. A green checkmark will appear in the navigation if valid. This section is optional — leave it empty if you have no additional parameters to report. Use 'Save as Draft' to persist your work to the server.", "section_help": "Show Section Tutorial", "welcome_message": "Welcome to RAFTS - Research Announcements For The Solar System! This tutorial will guide you through submitting your astronomical observation or discovery.", "getting_started": "Let's get started with your RAFTS submission. Follow the step-by-step process to ensure your observation is properly documented and ready for scientific review.", diff --git a/rafts/frontend/messages/fr.json b/rafts/frontend/messages/fr.json index c65c879a..68aac313 100644 --- a/rafts/frontend/messages/fr.json +++ b/rafts/frontend/messages/fr.json @@ -171,9 +171,13 @@ "reset_form": "Réinitialiser le formulaire", "modal_reset_title": "Réinitialiser le formulaire", "confirm_reset": "Êtes-vous certain(e) de vouloir réinitialiser le formulaire? Toute votre progression sera perdue.", + "draft_save_success": "Votre brouillon a été enregistré avec succès.", + "draft_save_error": "Une erreur s'est produite lors de l'enregistrement de votre brouillon. Veuillez réessayer.", "submission_success": "Votre RAFTS a été soumis avec succès!", "submission_error": "Une erreur s'est produite lors de la soumission de votre RAFTS. Veuillez réessayer.", "validation_incomplete": "Veuillez compléter toutes les sections requises avant de soumettre.", + "section_validated": "Section validée avec succès.", + "section_validation_errors": "Veuillez corriger les erreurs dans cette section.", "form_reset": "Le formulaire a été réinitialisé.", "corresponding": "Correspondant", "review_title": "Réviser votre soumission RAFTS", @@ -371,14 +375,14 @@ }, "tutorial": { "step_title": "Bienvenue dans le formulaire de soumission RAFTS ! C'est ici que vous saisirez le titre de votre observation ou découverte astronomique. Rendez votre titre clair, descriptif et spécifique pour aider les autres à comprendre votre travail.", - "step_navigation": "Cette barre de navigation montre votre progression dans le processus de soumission en 5 étapes. Les cercles verts indiquent les sections complétées, et vous pouvez cliquer sur n'importe quelle étape terminée pour y retourner. Les lignes de connexion montrent votre progression dans le formulaire.", + "step_navigation": "Cette barre de navigation montre votre progression dans le processus de soumission en 5 étapes. Les cercles verts indiquent les sections complétées. Vous pouvez cliquer sur n'importe quelle étape pour naviguer librement entre les sections. Les lignes de connexion montrent votre progression dans le formulaire.", "step_author_info": "Étape 1 : Informations sur l'auteur - Saisissez les détails sur l'auteur correspondant (contact principal) et tous les auteurs contributeurs. Incluez les noms, affiliations, courriels et identifiants ORCID lorsqu'ils sont disponibles. Tous les champs requis doivent être complétés.", - "step_announcement": "Étape 2 : Annonce - Fournissez les informations principales sur votre observation astronomique incluant la classification du sujet, le nom/désignation de l'objet, un résumé détaillé et toutes figures ou images de support.", - "step_observation": "Étape 3 : Détails de l'observation - Ajoutez les informations techniques sur votre observation incluant les spécifications du télescope, les coordonnées, les mesures photométriques et téléversez les fichiers de données de support comme les spectres ou l'astrométrie.", + "step_announcement": "Étape 2 : Informations sur l'observation - Fournissez les informations principales sur votre observation astronomique incluant la classification du sujet, le nom/désignation de l'objet, un résumé détaillé et toutes figures ou images de support.", + "step_observation": "Étape 3 : Informations techniques - Ajoutez les informations techniques sur votre observation incluant le télescope, les identifiants, les mesures photométriques et téléversez les fichiers de données de support comme les spectres ou l'astrométrie.", "step_miscellaneous": "Étape 4 : Divers - Incluez toute information supplémentaire en utilisant des paires clé-valeur pour des paramètres comme les périodes orbitales, magnitudes ou taux de dérive. Cette section aide à fournir un contexte complet pour votre observation.", "step_review": "Étape 5 : Révision - Dernière étape pour réviser soigneusement toutes vos informations avant la soumission. Vous pouvez sauvegarder comme brouillon pour continuer plus tard, ou soumettre pour révision par les pairs quand prêt.", "step_progress": "L'indicateur de progression montre quelles sections sont complètes. Les cercles verts indiquent les sections terminées, le bleu montre votre emplacement actuel, et le gris indique les sections incomplètes.", - "step_save_as_draft": "Sauvegarder comme brouillon - Utilisez ce bouton pour sauvegarder votre travail à tout moment. Votre progression sera préservée et vous pourrez continuer l'édition plus tard. Note : Ce bouton est désactivé jusqu'à ce que vous entriez les informations minimales de l'auteur et un titre RAFTS.", + "step_save_as_draft": "Sauvegarder comme brouillon - Utilisez ce bouton pour sauvegarder votre travail à tout moment. Votre progression sera préservée et vous pourrez continuer l'édition plus tard. Note : Ce bouton est désactivé jusqu'à ce que vous entriez un titre RAFTS.", "step_submit": "Soumettre - Lorsque toutes les sections requises sont complètes, utilisez ce bouton pour soumettre votre RAFTS pour révision par les pairs. Ce bouton n'est activé que lorsque tous les champs obligatoires sont remplis.", "button_back": "Retour", "button_close": "Fermer", @@ -390,23 +394,23 @@ "author_contributing": "Les auteurs contributeurs sont des collègues qui ont participé à l'observation, l'analyse ou l'interprétation des données. Ajoutez chaque auteur avec leurs informations complètes incluant nom, courriel et affiliation institutionnelle. Les identifiants ORCID sont fortement recommandés pour tous les auteurs.", "author_collaborations": "Les collaborations sont des groupes institutionnels ou organisationnels qui ont contribué à ce travail. Contrairement aux auteurs individuels, les collaborations sont listées par nom seulement (ex. 'Équipe NEOWISE', 'Catalina Sky Survey'). Ajoutez les noms de collaboration qui devraient être crédités pour cette observation.", "author_add_button": "Cliquez sur ce bouton 'Ajouter Auteur' pour inclure des auteurs contributeurs supplémentaires à votre soumission. Vous pouvez ajouter autant d'auteurs que nécessaire et les supprimer si besoin en utilisant le bouton supprimer à côté de chaque entrée.", - "author_save": "Important : Cliquez sur 'Valider' pour stocker vos informations d'auteur avant de procéder à la section suivante. Votre progression sera perdue si vous naviguez ailleurs sans sauvegarder.", - "announcement_section_welcome": "Bienvenue dans la section Annonce ! C'est le cœur de votre soumission RAFTS où vous décrirez votre observation ou découverte astronomique. Fournissez des informations claires et complètes pour aider la communauté scientifique à comprendre vos découvertes.", + "author_save": "Cliquez sur 'Valider' pour vérifier la complétude de vos informations d'auteur. Une coche verte apparaîtra dans la navigation si tous les champs requis sont remplis. Utilisez 'Sauvegarder comme brouillon' pour enregistrer votre travail sur le serveur.", + "announcement_section_welcome": "Bienvenue dans la section Informations sur l'observation ! C'est le cœur de votre soumission RAFTS où vous décrirez votre observation ou découverte astronomique. Fournissez des informations claires et complètes pour aider la communauté scientifique à comprendre vos découvertes.", "announcement_topic": "Sélectionnez la catégorie de sujet qui décrit le mieux votre observation. Choisissez parmi des options comme 'Comète', 'Objet géocroiseur', 'Objet transneptunien', ou autres types d'objets célestes. Cela aide à catégoriser votre soumission de manière appropriée.", "announcement_object": "Entrez le nom officiel, la désignation ou l'identifiant de l'objet astronomique que vous avez observé. Utilisez la nomenclature standard quand possible (ex. 'C/2023 A1', '2023 AB', 'NGC 1234'). Si c'est une nouvelle découverte, fournissez votre désignation provisoire.", "announcement_abstract": "Rédigez un résumé complet décrivant votre observation, méthodologie, découvertes clés et leur importance scientifique. Incluez des détails importants comme les dates d'observation, instruments utilisés et caractéristiques notables. Maximum 2000 caractères - faites que chaque mot compte !", "announcement_figure": "Téléversez des images, graphiques ou figures de support qui illustrent votre observation. Cela pourrait inclure des photographies, courbes de lumière, spectres ou cartes de repérage. Les images aident les réviseurs et lecteurs à mieux comprendre votre travail.", - "announcement_save": "Sauvegardez vos informations d'annonce pour verrouiller votre progression et procéder à la section des détails techniques. Révisez votre résumé soigneusement avant de sauvegarder car c'est une partie clé de votre soumission.", - "observation_section_welcome": "Bienvenue dans la section Détails de l'observation ! Ici vous fournirez les spécifications techniques et mesures qui soutiennent votre observation astronomique. Ces données sont cruciales pour la vérification scientifique et les études de suivi.", - "observation_coordinates": "Fournissez les coordonnées précises et informations de position pour votre objet observé. Incluez les numéros MPC ID pour les objets connus, identifiants d'alerte pour les événements transitoires, et Date Julienne Modifiée (MJD) pour les observations sensibles au temps.", + "announcement_save": "Cliquez sur 'Valider' pour vérifier la complétude de vos informations d'annonce. Une coche verte apparaîtra dans la navigation si tous les champs requis sont remplis. Vous pouvez également ajouter des Remerciements et des références de RAFTs précédemment publiés au-dessus de ce bouton. Utilisez 'Sauvegarder comme brouillon' pour enregistrer votre travail sur le serveur.", + "observation_section_welcome": "Bienvenue dans la section Informations techniques ! Ici vous fournirez les spécifications techniques et mesures qui soutiennent votre observation astronomique. Ces données sont cruciales pour la vérification scientifique et les études de suivi.", + "observation_coordinates": "Fournissez les identifiants et informations temporelles pour votre objet observé. Entrez les numéros MPC ID pour les objets connus, identifiants d'alerte pour les événements transitoires, et Date Julienne Modifiée (MJD) pour les observations sensibles au temps. Tous les champs de cette section sont optionnels.", "observation_brightness": "Entrez les mesures photométriques incluant les informations de longueur d'onde/filtre, valeurs de luminosité et incertitudes associées. Utilisez les systèmes de magnitude astronomiques standards et spécifiez vos bandes photométriques (ex. V, R, I, g', r', i').", - "observation_telescope": "Spécifiez le télescope et l'instrumentation utilisés pour votre observation. Incluez l'ouverture du télescope, rapport focal, type de détecteur et toutes spécifications techniques pertinentes qui affectent la qualité et l'interprétation de vos données.", + "observation_telescope": "Entrez le télescope et l'instrument utilisés pour votre observation (ex. 'Gemini Nord / GMOS'). Vous pouvez inclure des détails comme l'ouverture ou le type de détecteur dans une seule entrée. Ce champ est optionnel.", "observation_files": "Téléversez les fichiers de données de support qui valident votre observation. Cela peut inclure des fichiers d'éphémérides, éléments orbitaux, données spectroscopiques ou mesures astrométriques. Utilisez des formats standards quand possible (ADES pour l'astrométrie, etc.).", - "observation_save": "Sauvegardez vos détails techniques d'observation pour préserver votre travail. Assurez-vous que toutes les mesures critiques et fichiers de données sont inclus avant de procéder à la section finale.", + "observation_save": "Cliquez sur 'Valider' pour vérifier la complétude de vos détails d'observation. Une coche verte apparaîtra dans la navigation si tous les champs requis sont remplis. Utilisez 'Sauvegarder comme brouillon' pour enregistrer votre travail sur le serveur.", "misc_section_welcome": "Bienvenue dans la section Informations diverses ! C'est ici que vous pouvez ajouter tout contexte, paramètres ou détails supplémentaires qui ne s'intègrent pas dans les sections précédentes mais sont importants pour comprendre votre observation.", "misc_key_value": "Utilisez le système de paires clé-valeur pour enregistrer des paramètres spécifiques pertinents à votre observation. Exemples : 'Période' = '6,2 heures', 'Magnitude absolue' = '15,3', 'Dérive Yarkovsky' = '0,01 au/Man'. Chaque paire devrait contenir des informations scientifiques significatives.", - "misc_additional_files": "Cliquez sur 'Ajouter Information Supplémentaire' pour inclure plus de paires clé-valeur pour des paramètres comme les périodes rotationnelles, indices de couleur, éléments orbitaux, magnitudes d'éruption, ou toute autre donnée quantitative pertinente à votre observation.", - "misc_save": "Sauvegardez vos informations diverses pour compléter cette section. Révisez toutes vos paires clé-valeur pour vous assurer qu'elles sont précises et fournissent un contexte scientifique significatif pour votre soumission.", + "misc_additional_files": "Utilisez 'Ajouter Texte' pour inclure des paires clé-valeur pour des paramètres comme les périodes rotationnelles, indices de couleur ou magnitudes d'éruption. Utilisez 'Ajouter Fichier' pour joindre des documents de support. Vous pouvez ajouter autant d'entrées que nécessaire et les supprimer avec le bouton supprimer.", + "misc_save": "Cliquez sur 'Valider' pour vérifier la complétude de vos informations diverses. Une coche verte apparaîtra dans la navigation si valide. Cette section est optionnelle — laissez-la vide si vous n'avez pas de paramètres supplémentaires à signaler. Utilisez 'Sauvegarder comme brouillon' pour enregistrer votre travail sur le serveur.", "section_help": "Afficher le tutoriel de section", "welcome_message": "Bienvenue dans RAFTS - Annonces de Recherche pour le Système Solaire ! Ce tutoriel vous guidera dans la soumission de votre observation ou découverte astronomique.", "getting_started": "Commençons avec votre soumission RAFTS. Suivez le processus étape par étape pour vous assurer que votre observation est correctement documentée et prête pour la révision scientifique.", diff --git a/rafts/frontend/src/actions/publishRaftDoi.ts b/rafts/frontend/src/actions/publishRaftDoi.ts index 8437f213..cf2bdeef 100644 --- a/rafts/frontend/src/actions/publishRaftDoi.ts +++ b/rafts/frontend/src/actions/publishRaftDoi.ts @@ -102,16 +102,27 @@ export const publishRAFTDOI = async ( const url = `${SUBMIT_DOI_URL}/${raftId}/mint` + console.log(`[publishRAFTDOI] POST ${url}`) + const response = await fetch(url, { method: 'POST', headers: { + 'Content-Type': 'application/json', Cookie: `CADC_SSO=${accessToken}`, }, + body: '{}', }) const responseText = await response.text() + console.log(`[publishRAFTDOI] Response: ${response.status} ${response.statusText}`, responseText) + if (!response.ok) { + console.error(`[publishRAFTDOI] Failed: ${response.status}`, { + url, + raftId, + responseText, + }) return { [SUCCESS]: false, [MESSAGE]: `Failed to mint DOI: ${response.status} ${responseText}`, diff --git a/rafts/frontend/src/components/Form/FileUpload/ADESFileUpload.tsx b/rafts/frontend/src/components/Form/FileUpload/ADESFileUpload.tsx index fbe7bf87..f70c7779 100644 --- a/rafts/frontend/src/components/Form/FileUpload/ADESFileUpload.tsx +++ b/rafts/frontend/src/components/Form/FileUpload/ADESFileUpload.tsx @@ -65,7 +65,7 @@ ************************************************************************ */ -import React, { useState, useRef, ChangeEvent, useEffect } from 'react' +import React, { useState, useRef, useMemo, ChangeEvent, useEffect } from 'react' import { Button, Box, @@ -175,6 +175,14 @@ const ADESFileUpload: React.FC = ({ // Determine if we're in VOSpace upload mode const useVOSpaceUpload = Boolean(doiIdentifier) && canUpload + // Stable key for initialText — avoids re-running the effect when the object + // reference changes but the value is identical (e.g. parseStoredAttachment + // returning a new FileReference object from the same JSON on every render). + const initialTextKey = useMemo( + () => (isFileReference(initialText) ? initialText.filename + initialText.uploadedAt : String(initialText ?? '')), + [initialText], + ) + // Resolve initial text on mount or when it changes useEffect(() => { const resolveInitialText = async () => { @@ -207,7 +215,8 @@ const ADESFileUpload: React.FC = ({ } resolveInitialText() - }, [initialText, resolveAttachment]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialTextKey]) const handleFileKindChange = (event: SelectChangeEvent) => { setFileKind(event.target.value as ADESFileKind) @@ -299,15 +308,12 @@ const ADESFileUpload: React.FC = ({ } } - const handleClear = async () => { - // If we have a FileReference and doiIdentifier, delete from VOSpace + const handleClear = () => { + // Delete from VOSpace in the background — don't block the UI if (currentFileRef && doiIdentifier) { - try { - await deleteFile(currentFileRef.filename) - } catch (err) { + deleteFile(currentFileRef.filename).catch((err) => { console.error('[ADESFileUpload] Failed to delete from VOSpace:', err) - // Continue with UI clear even if delete fails - } + }) } resetValidation() diff --git a/rafts/frontend/src/components/Form/FileUpload/FileUploadImage.tsx b/rafts/frontend/src/components/Form/FileUpload/FileUploadImage.tsx index d3533e68..d1c3dbd4 100644 --- a/rafts/frontend/src/components/Form/FileUpload/FileUploadImage.tsx +++ b/rafts/frontend/src/components/Form/FileUpload/FileUploadImage.tsx @@ -65,7 +65,7 @@ ************************************************************************ */ -import React, { useState, useRef, ChangeEvent, useEffect } from 'react' +import React, { useState, useRef, useMemo, ChangeEvent, useEffect } from 'react' import { Button, Box, @@ -152,6 +152,14 @@ const FileUploadImage: React.FC = ({ // Determine if we're in VOSpace upload mode const useVOSpaceUpload = Boolean(doiIdentifier) && canUpload + // Stable key for initialImage — avoids re-running the effect when the object + // reference changes but the value is identical (e.g. parseStoredAttachment + // returning a new FileReference object from the same JSON on every render). + const initialImageKey = useMemo( + () => (isFileReference(initialImage) ? initialImage.filename + initialImage.uploadedAt : String(initialImage ?? '')), + [initialImage], + ) + // Resolve initial image on mount or when it changes useEffect(() => { const resolveInitialImage = async () => { @@ -199,7 +207,8 @@ const FileUploadImage: React.FC = ({ } resolveInitialImage() - }, [initialImage, resolveAttachment]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialImageKey]) const handleFileChange = (event: ChangeEvent) => { const files = event.target.files @@ -312,15 +321,12 @@ const FileUploadImage: React.FC = ({ } } - const handleClear = async () => { - // If we have a FileReference and doiIdentifier, delete from VOSpace + const handleClear = () => { + // Delete from VOSpace in the background — don't block the UI if (currentFileRef && doiIdentifier) { - try { - await deleteFile(currentFileRef.filename) - } catch (err) { + deleteFile(currentFileRef.filename).catch((err) => { console.error('[FileUploadImage] Failed to delete from VOSpace:', err) - // Continue with UI clear even if delete fails - } + }) } setError(null) diff --git a/rafts/frontend/src/components/Form/FileUpload/TextFileUpload.tsx b/rafts/frontend/src/components/Form/FileUpload/TextFileUpload.tsx index 09b61c0d..6f483786 100644 --- a/rafts/frontend/src/components/Form/FileUpload/TextFileUpload.tsx +++ b/rafts/frontend/src/components/Form/FileUpload/TextFileUpload.tsx @@ -65,7 +65,7 @@ ************************************************************************ */ -import React, { useState, useRef, ChangeEvent, useEffect } from 'react' +import React, { useState, useRef, useMemo, ChangeEvent, useEffect } from 'react' import { Button, Box, @@ -158,6 +158,14 @@ const TextFileUpload: React.FC = ({ // Determine if we're in VOSpace upload mode const useVOSpaceUpload = Boolean(doiIdentifier) && canUpload + // Stable key for initialText — avoids re-running the effect when the object + // reference changes but the value is identical (e.g. parseStoredAttachment + // returning a new FileReference object from the same JSON on every render). + const initialTextKey = useMemo( + () => (isFileReference(initialText) ? initialText.filename + initialText.uploadedAt : String(initialText ?? '')), + [initialText], + ) + // Resolve initial text on mount or when it changes useEffect(() => { const resolveInitialText = async () => { @@ -191,7 +199,8 @@ const TextFileUpload: React.FC = ({ } resolveInitialText() - }, [initialText, resolveAttachment]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialTextKey]) const handleFileChange = (event: ChangeEvent) => { const files = event.target.files @@ -230,7 +239,7 @@ const TextFileUpload: React.FC = ({ }) if (!isAccepted && file.type !== '') { - setError(`Please upload a text file (${accept})`) + setError(`File type not accepted (${accept})`) return } @@ -244,14 +253,19 @@ const TextFileUpload: React.FC = ({ if (useVOSpaceUpload) { setIsLoading(true) try { - // First read the file to get preview - const textContent = await readFileAsText(file) - if (showPreview) { + // Only read text preview for text-like files (skip binary/image files) + const isTextFile = file.type.startsWith('text/') || file.type === '' || + /\.(txt|csv|tsv|psv|xml|json|ades|mpc|dat|log)$/i.test(file.name) + if (showPreview && isTextFile) { + const textContent = await readFileAsText(file) const previewText = textContent.length > 500 ? textContent.substring(0, 500) + '...' : textContent setTextPreview(previewText) + setShowTextPreview(true) + } else { + setTextPreview('') + setShowTextPreview(false) } - setShowTextPreview(true) // Then upload const filename = customFilename || file.name @@ -318,15 +332,12 @@ const TextFileUpload: React.FC = ({ } } - const handleClear = async () => { - // If we have a FileReference and doiIdentifier, delete from VOSpace + const handleClear = () => { + // Delete from VOSpace in the background — don't block the UI if (currentFileRef && doiIdentifier) { - try { - await deleteFile(currentFileRef.filename) - } catch (err) { + deleteFile(currentFileRef.filename).catch((err) => { console.error('[TextFileUpload] Failed to delete from VOSpace:', err) - // Continue with UI clear even if delete fails - } + }) } setError(null) diff --git a/rafts/frontend/src/components/Form/FormLayoutWithContext.tsx b/rafts/frontend/src/components/Form/FormLayoutWithContext.tsx index c84fa4cf..d56fc9e7 100644 --- a/rafts/frontend/src/components/Form/FormLayoutWithContext.tsx +++ b/rafts/frontend/src/components/Form/FormLayoutWithContext.tsx @@ -99,7 +99,7 @@ import { useTranslations } from 'next-intl' import { Button, Alert, Snackbar, Grid } from '@mui/material' import RaftBreadcrumbs from '@/components/RaftDetail/components/RaftBreadcrumbs' import { useRouter } from '@/i18n/routing' -import { TAuthor, TMiscInfo, TObservation, TRaftSubmission, TTechInfo } from '@/shared/model' +import { TAuthor, TMiscInfo, TObservation, TRaftSubmission, TSection, TTechInfo } from '@/shared/model' import JsonImportComponent from '@/components/Form/FileUpload/JsonImportComponent' import WarningDialog from '@/components/Layout/WarningDialog' import { AttentionBanner } from '@/components/Layout/AttentionBanner' @@ -355,35 +355,44 @@ const FormLayoutWithContext = () => { (checked: boolean) => { setPostOptOut(checked) - updateRaftSection(PROP_GENERAL_INFO, { + const sectionData = { [PROP_TITLE]: titleValueRef.current, [PROP_POST_OPT_OUT]: checked, [PROP_STATUS]: raftData?.[PROP_GENERAL_INFO]?.[PROP_STATUS] ?? OPTION_DRAFT, - }) + } + + updateRaftSection(PROP_GENERAL_INFO, sectionData) + // Pass data directly to avoid stale closure over raftData + validateSection(PROP_GENERAL_INFO, sectionData) setFormIsDirty((prev) => ({ ...prev, [PROP_GENERAL_INFO]: true })) }, - [raftData, updateRaftSection, setFormIsDirty], + [raftData, updateRaftSection, setFormIsDirty, validateSection], ) // Handle section save - updates form data in context, marks as clean, and validates const handleSectionSave = useCallback( - (section: string) => { + (section: string, data?: TSection) => { setFormIsDirty((prev) => ({ ...prev, [section]: false })) - // Validate the section on save (user clicked Save button) + // Validate with the data just submitted (avoids stale raftData closure) if (section in VALIDATION_SCHEMAS) { - validateSection(section as keyof typeof VALIDATION_SCHEMAS) + const isValid = validateSection(section as keyof typeof VALIDATION_SCHEMAS, data) + dispatchAlert({ + type: 'show', + severity: isValid ? 'success' : 'warning', + message: t(isValid ? 'section_validated' : 'section_validation_errors'), + }) } }, - [validateSection], + [validateSection, t], ) // Memoized callbacks for form sections to prevent re-renders const handleAuthorSubmit = useCallback( (data: TAuthor) => { updateRaftSection(PROP_AUTHOR_INFO, data) - handleSectionSave(PROP_AUTHOR_INFO) + handleSectionSave(PROP_AUTHOR_INFO, data) }, [updateRaftSection, handleSectionSave], ) @@ -391,7 +400,7 @@ const FormLayoutWithContext = () => { const handleObservationSubmit = useCallback( (data: TObservation) => { updateRaftSection(PROP_OBSERVATION_INFO, data) - handleSectionSave(PROP_OBSERVATION_INFO) + handleSectionSave(PROP_OBSERVATION_INFO, data) }, [updateRaftSection, handleSectionSave], ) @@ -399,7 +408,7 @@ const FormLayoutWithContext = () => { const handleTechnicalSubmit = useCallback( (data: TTechInfo) => { updateRaftSection(PROP_TECHNICAL_INFO, data) - handleSectionSave(PROP_TECHNICAL_INFO) + handleSectionSave(PROP_TECHNICAL_INFO, data) }, [updateRaftSection, handleSectionSave], ) @@ -407,7 +416,7 @@ const FormLayoutWithContext = () => { const handleMiscellaneousSubmit = useCallback( (data: TMiscInfo) => { updateRaftSection(PROP_MISC_INFO, data) - handleSectionSave(PROP_MISC_INFO) + handleSectionSave(PROP_MISC_INFO, data) }, [updateRaftSection, handleSectionSave], ) @@ -517,15 +526,13 @@ const FormLayoutWithContext = () => { const res = await submitForm(isDraft, syncedData) if (res.success) { - dispatchAlert({ type: 'show', severity: 'success', message: t('submission_success') }) + const successKey = isDraft ? 'draft_save_success' : 'submission_success' + dispatchAlert({ type: 'show', severity: 'success', message: t(successKey) }) setFormIsDirty(DIRTY_FORM) // After a successful save, validate all sections to update checkmarks and Submit button - console.log('[handleSubmit] res.success:', res.success, 'isDraft:', isDraft) if (isDraft) { - console.log('[handleSubmit] calling validateAllSections') - const allValid = validateAllSections(syncedData) - console.log('[handleSubmit] validateAllSections returned:', allValid) + validateAllSections(syncedData) } if (isDraft && isNewRaft && res.data) { @@ -534,21 +541,28 @@ const FormLayoutWithContext = () => { router.replace(`/form/edit/${newId}`) } } + + if (!isDraft) { + setTimeout(() => { + router.push('/view/rafts') + }, 1000) + } } else { + const errorKey = isDraft ? 'draft_save_error' : 'submission_error' dispatchAlert({ type: 'show', severity: 'error', - message: `${t('submission_error')} [${res.message}]`, + message: `${t(errorKey)} [${res.message}]`, }) } - - if (!isDraft) { - setTimeout(() => { - router.push('/view/rafts') - }, 3000) - } - } catch { - dispatchAlert({ type: 'show', severity: 'error', message: t('submission_error') }) + } catch (err) { + const errorKey = isDraft ? 'draft_save_error' : 'submission_error' + const detail = err instanceof Error ? err.message : '' + dispatchAlert({ + type: 'show', + severity: 'error', + message: detail ? `${t(errorKey)} [${detail}]` : t(errorKey), + }) } finally { setSubmittingAction(null) } diff --git a/rafts/frontend/src/components/Form/MiscellaneousInfoForm.tsx b/rafts/frontend/src/components/Form/MiscellaneousInfoForm.tsx index bc721c8d..7586f2d9 100644 --- a/rafts/frontend/src/components/Form/MiscellaneousInfoForm.tsx +++ b/rafts/frontend/src/components/Form/MiscellaneousInfoForm.tsx @@ -298,6 +298,7 @@ const MiscellaneousInfoForm = forwardRef< { setValue( `${PROP_MISC}.${index}.${PROP_MISC_VALUE}`, diff --git a/rafts/frontend/src/components/Form/ReviewForm.tsx b/rafts/frontend/src/components/Form/ReviewForm.tsx index 81bed814..d912bfe9 100644 --- a/rafts/frontend/src/components/Form/ReviewForm.tsx +++ b/rafts/frontend/src/components/Form/ReviewForm.tsx @@ -171,7 +171,7 @@ const ReviewForm = ({ raftData, onOptOutChange, doiId }: ReviewFormProps) => { {title} - + {content} diff --git a/rafts/frontend/src/components/RaftDetail/components/ReviewerSidePanel.tsx b/rafts/frontend/src/components/RaftDetail/components/ReviewerSidePanel.tsx index 5b24288b..aaf5d189 100644 --- a/rafts/frontend/src/components/RaftDetail/components/ReviewerSidePanel.tsx +++ b/rafts/frontend/src/components/RaftDetail/components/ReviewerSidePanel.tsx @@ -171,12 +171,18 @@ export default function ReviewerSidePanel({ raftData, review, onNotify }: Review const handlePublishingDOI = async () => { try { setActionLoading(true) + console.log('[handlePublishingDOI] Publishing DOI for:', raftId, { + dataDirectory: raftData.dataDirectory, + previousStatus: raftData.generalInfo?.status, + }) const result = await publishRAFTDOI(raftId, { dataDirectory: raftData.dataDirectory, previousStatus: raftData.generalInfo?.status, }) + console.log('[handlePublishingDOI] Result:', result) + if (result.success) { onNotify('success', result.message || `RAFTS DOI published successfully.`) @@ -185,10 +191,11 @@ export default function ReviewerSidePanel({ raftData, review, onNotify }: Review router.refresh() }, 2000) } else { + console.error('[handlePublishingDOI] Failed:', result.message) onNotify('error', result.message || 'Failed to publish RAFTS DOI.') } } catch (error) { - console.error('Error publishing DOI: ', error) + console.error('[handlePublishingDOI] Error:', error) onNotify('error', 'An unexpected error occurred.') } } @@ -599,9 +606,14 @@ export default function ReviewerSidePanel({ raftData, review, onNotify }: Review Topic {raftData.observationInfo?.topic?.map?.((top) => ( - - {top.replace('_', ' ') || 'N/A'} - + ))} diff --git a/rafts/frontend/src/context/RaftFormContext.tsx b/rafts/frontend/src/context/RaftFormContext.tsx index e63fcca0..6ff97042 100644 --- a/rafts/frontend/src/context/RaftFormContext.tsx +++ b/rafts/frontend/src/context/RaftFormContext.tsx @@ -132,7 +132,7 @@ interface RaftFormContextType { allSectionsCompleted: boolean errors: RecursiveStringify /** Validate a single section imperatively (runs Zod once, updates validationState + errors) */ - validateSection: (section: keyof typeof VALIDATION_SCHEMAS) => boolean + validateSection: (section: keyof typeof VALIDATION_SCHEMAS, data?: TSection) => boolean /** Validate all sections imperatively. Returns true if all pass. */ validateAllSections: (data?: TRaftContext) => boolean /** DOI identifier for attachment uploads (available after first save) */ @@ -300,8 +300,8 @@ export function RaftFormProvider({ // Validate a single section imperatively (runs Zod once, updates validationState + errors) const validateSection = useCallback( - (section: keyof typeof VALIDATION_SCHEMAS) => { - const sectionData = raftData?.[section] ?? {} + (section: keyof typeof VALIDATION_SCHEMAS, data?: TSection) => { + const sectionData = data ?? raftData?.[section] ?? {} const isValid = validateWithSchema(VALIDATION_SCHEMAS[section], sectionData) setValidationState((prev) => ({ ...prev, [section]: isValid })) @@ -332,9 +332,6 @@ export function RaftFormProvider({ if (!isValid) { const errors = getValidationErrors(VALIDATION_SCHEMAS[section], sectionData) newErrors[section] = errors - console.log(`[validateAllSections] FAILED: ${section}`, { sectionData, errors }) - } else { - console.log(`[validateAllSections] PASSED: ${section}`) } } From 0d789dea783829541d2d6759730b0aa57cd89637 Mon Sep 17 00:00:00 2001 From: Serhii Zautkin Date: Thu, 5 Mar 2026 15:05:51 -0800 Subject: [PATCH 4/5] CADC-14486 Fixes: update publish DOI request content type and body, and add debug logging to CADC authentication flow. --- rafts/frontend/src/actions/publishRaftDoi.ts | 4 ++-- rafts/frontend/src/auth/cadc-auth/credentials.ts | 8 ++++++++ rafts/frontend/src/auth/cadc-auth/fetchUserGroups.ts | 7 +++++++ rafts/frontend/src/auth/cadc-auth/fetchUserInfo.ts | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/rafts/frontend/src/actions/publishRaftDoi.ts b/rafts/frontend/src/actions/publishRaftDoi.ts index cf2bdeef..9f608237 100644 --- a/rafts/frontend/src/actions/publishRaftDoi.ts +++ b/rafts/frontend/src/actions/publishRaftDoi.ts @@ -107,10 +107,10 @@ export const publishRAFTDOI = async ( const response = await fetch(url, { method: 'POST', headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', Cookie: `CADC_SSO=${accessToken}`, }, - body: '{}', + body: '', }) const responseText = await response.text() diff --git a/rafts/frontend/src/auth/cadc-auth/credentials.ts b/rafts/frontend/src/auth/cadc-auth/credentials.ts index 6f706b1e..808b953e 100644 --- a/rafts/frontend/src/auth/cadc-auth/credentials.ts +++ b/rafts/frontend/src/auth/cadc-auth/credentials.ts @@ -108,6 +108,14 @@ export const { // Step 4: Fetch user groups/roles const { role: userRole, groups: userGroups } = await fetchUserGroups(token) + + // Debug: log full user profile and groups + console.log(`[AUTH] User login: ${credentials.username}`, { + profile: user, + role: userRole, + groups: userGroups, + }) + // Step 5: Combine all information into a complete user object return { id: credentials.username as string, diff --git a/rafts/frontend/src/auth/cadc-auth/fetchUserGroups.ts b/rafts/frontend/src/auth/cadc-auth/fetchUserGroups.ts index f1789a28..86c3d722 100644 --- a/rafts/frontend/src/auth/cadc-auth/fetchUserGroups.ts +++ b/rafts/frontend/src/auth/cadc-auth/fetchUserGroups.ts @@ -88,9 +88,16 @@ export const fetchUserGroups = async (token?: string): Promise => credentials: 'include', }) + const responseText = await response.text() const isRaftReviewer = response.ok const assignedRole = isRaftReviewer ? ROLE_REVIEWER : ROLE_CONTRIBUTOR + console.log(`[fetchUserGroups] GET ${groupCheckUrl} -> ${response.status}`, { + isRaftReviewer, + assignedRole, + responseBody: responseText, + }) + return { role: assignedRole, groups: isRaftReviewer ? [CANFAR_RAFT_REVIEWER_GROUP] : [], diff --git a/rafts/frontend/src/auth/cadc-auth/fetchUserInfo.ts b/rafts/frontend/src/auth/cadc-auth/fetchUserInfo.ts index 517efb30..950674c9 100644 --- a/rafts/frontend/src/auth/cadc-auth/fetchUserInfo.ts +++ b/rafts/frontend/src/auth/cadc-auth/fetchUserInfo.ts @@ -86,6 +86,7 @@ export const fetchUserInfo = async (token?: string): Promise => } const userData = await userResponse.json() + console.log(`[fetchUserInfo] Raw CADC profile response:`, JSON.stringify(userData, null, 2)) return parseUserInfo(userData) } catch (error) { console.warn('Error fetching user data:', error) From 83ba0549d5c639f29b34c1f006177623962a9447 Mon Sep 17 00:00:00 2001 From: Serhii Zautkin Date: Thu, 5 Mar 2026 15:15:22 -0800 Subject: [PATCH 5/5] CADC-14486 Clean up routes commit --- .../src/app/api/auth/[...nextauth]/route.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/rafts/frontend/src/app/api/auth/[...nextauth]/route.ts b/rafts/frontend/src/app/api/auth/[...nextauth]/route.ts index 370ff0cb..af53c8e5 100644 --- a/rafts/frontend/src/app/api/auth/[...nextauth]/route.ts +++ b/rafts/frontend/src/app/api/auth/[...nextauth]/route.ts @@ -65,6 +65,30 @@ ************************************************************************ */ +import { NextRequest } from 'next/server' import { GET as AuthGET, POST as AuthPOST } from '@/auth/cadc-auth/credentials' -export { AuthGET as GET, AuthPOST as POST } +const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '' + +// Fix the URL path for NextAuth v5 which doesn't handle base paths well +function fixUrl(req: NextRequest): NextRequest { + if (!basePath) return req + + const url = new URL(req.url) + // Remove the base path from the pathname for NextAuth processing + if (url.pathname.startsWith(`${basePath}/api/auth`)) { + url.pathname = url.pathname.replace(basePath, '') + } + + return new NextRequest(url, req) +} + +export async function GET(req: NextRequest) { + const fixedReq = fixUrl(req) + return AuthGET(fixedReq) +} + +export async function POST(req: NextRequest) { + const fixedReq = fixUrl(req) + return AuthPOST(fixedReq) +}