@@ -12,10 +12,18 @@ import type { ContactFormElements } from './@types'
1212import { validateEmailField } from './email'
1313import { actions , isInputError } from 'astro:actions'
1414import type { UploadController } from './upload'
15+ import { queryContactFormGeneratedFieldError , queryContactFormGenericFields } from './selectors'
16+
17+ export type ContactUiState = 'idle' | 'loading' | 'success' | 'error' | 'validation'
18+
19+ export const contactPreviewStates = [ 'loading' , 'success' , 'error' , 'validation' ] as const
20+
21+ type ContactPreviewState = ( typeof contactPreviewStates ) [ number ]
1522
1623interface SubmissionControllers {
1724 labelController : LabelController
1825 uploadController ?: UploadController | null
26+ rootElement ?: HTMLElement | null
1927}
2028
2129export const appendUploadFiles = (
@@ -33,6 +41,13 @@ const hideMessages = (elements: ContactFormElements): void => {
3341 elements . messages . style . display = 'none'
3442}
3543
44+ const setContactState = (
45+ rootElement : HTMLElement | null | undefined ,
46+ state : ContactUiState
47+ ) : void => {
48+ rootElement ?. setAttribute ( 'data-contact-state' , state )
49+ }
50+
3651const showSuccessMessage = ( elements : ContactFormElements ) : void => {
3752 elements . messages . style . display = 'block'
3853 elements . successMessage . classList . remove ( 'hidden' )
@@ -43,7 +58,7 @@ const showErrorMessage = (elements: ContactFormElements, message: string): void
4358 elements . messages . style . display = 'block'
4459 elements . successMessage . classList . add ( 'hidden' )
4560 elements . errorMessage . classList . remove ( 'hidden' )
46- elements . errorMessage . style . display = 'flex '
61+ elements . errorMessage . style . display = 'block '
4762 elements . errorText . textContent = message
4863}
4964
@@ -61,6 +76,72 @@ const setLoading = (elements: ContactFormElements, loading: boolean): void => {
6176 }
6277}
6378
79+ const clearGeneratedGenericErrors = ( elements : ContactFormElements ) : void => {
80+ queryContactFormGenericFields ( elements . form ) . forEach ( field => {
81+ field . classList . remove ( 'error' )
82+ field . setAttribute ( 'aria-invalid' , 'false' )
83+ field . removeAttribute ( 'aria-errormessage' )
84+ queryContactFormGeneratedFieldError ( field ) ?. remove ( )
85+ } )
86+ }
87+
88+ const resetSubmissionFeedback = ( elements : ContactFormElements ) : void => {
89+ hideMessages ( elements )
90+ hideErrorBanner ( elements . formErrorBanner )
91+ setLoading ( elements , false )
92+ clearGeneratedGenericErrors ( elements )
93+ Object . values ( elements . fields ) . forEach ( clearFieldFeedback )
94+ }
95+
96+ export const resolveContactPreviewState = ( search : string ) : ContactPreviewState | null => {
97+ const params = new URLSearchParams ( search )
98+ const previewState = params . get ( 'contactState' ) ?. trim ( ) . toLowerCase ( )
99+
100+ if ( ! previewState ) {
101+ return null
102+ }
103+
104+ return contactPreviewStates . includes ( previewState as ContactPreviewState )
105+ ? ( previewState as ContactPreviewState )
106+ : null
107+ }
108+
109+ export const applyContactPreviewState = (
110+ elements : ContactFormElements ,
111+ options : {
112+ state : ContactPreviewState
113+ rootElement ?: HTMLElement | null
114+ }
115+ ) : void => {
116+ resetSubmissionFeedback ( elements )
117+
118+ switch ( options . state ) {
119+ case 'loading' :
120+ setLoading ( elements , true )
121+ setContactState ( options . rootElement , 'loading' )
122+ return
123+
124+ case 'success' :
125+ showSuccessMessage ( elements )
126+ setContactState ( options . rootElement , 'success' )
127+ return
128+
129+ case 'error' :
130+ showErrorMessage ( elements , 'Unable to send message. Please try again later.' )
131+ setContactState ( options . rootElement , 'error' )
132+ return
133+
134+ case 'validation' :
135+ showFieldFeedback ( elements . fields . name , 'Please enter your name' , 'error' )
136+ showFieldFeedback ( elements . fields . email , 'Please enter a valid email address.' , 'error' )
137+ showFieldFeedback ( elements . fields . message , 'Please describe your project' , 'error' )
138+ showErrorBanner ( elements . formErrorBanner )
139+ showErrorMessage ( elements , 'Please correct the highlighted fields and try again.' )
140+ setContactState ( options . rootElement , 'validation' )
141+ return
142+ }
143+ }
144+
64145const runFieldValidations = ( elements : ContactFormElements ) : boolean => {
65146 const validations = [
66147 validateEmailField ( elements . fields . email ) ,
@@ -129,18 +210,21 @@ export const initFormSubmission = (
129210
130211 if ( ! isValid ) {
131212 showErrorBanner ( elements . formErrorBanner )
213+ setContactState ( controllers . rootElement , 'validation' )
132214 return
133215 }
134216
135217 hideErrorBanner ( elements . formErrorBanner )
136218 setLoading ( elements , true )
137219 hideMessages ( elements )
220+ setContactState ( controllers . rootElement , 'loading' )
138221
139222 try {
140223 const formData = new FormData ( elements . form )
141224 appendUploadFiles ( formData , controllers . uploadController )
142225 const { data, error } = await actions . contact . submit ( formData )
143226 if ( showActionInputErrors ( elements , error ) ) {
227+ setContactState ( controllers . rootElement , 'validation' )
144228 return
145229 }
146230
@@ -149,10 +233,12 @@ export const initFormSubmission = (
149233 elements ,
150234 error ?. message || data ?. message || 'An error occurred while sending your message.'
151235 )
236+ setContactState ( controllers . rootElement , 'error' )
152237 return
153238 }
154239
155240 showSuccessMessage ( elements )
241+ setContactState ( controllers . rootElement , 'success' )
156242
157243 elements . submitBtn . dispatchEvent (
158244 new CustomEvent ( 'confetti:fire' , {
@@ -165,12 +251,14 @@ export const initFormSubmission = (
165251 } catch ( error ) {
166252 handleScriptError ( error , context )
167253 showErrorMessage ( elements , 'Unable to send message. Please try again later.' )
254+ setContactState ( controllers . rootElement , 'error' )
168255 } finally {
169256 setLoading ( elements , false )
170257 }
171258 } catch ( error ) {
172259 handleScriptError ( error , context )
173260 setLoading ( elements , false )
261+ setContactState ( controllers . rootElement , 'error' )
174262 }
175263 } )
176264}
0 commit comments