Skip to content

Commit 3bc665c

Browse files
committed
server actions for contact equinor forms #3478
1 parent 203b711 commit 3bc665c

3 files changed

Lines changed: 114 additions & 76 deletions

File tree

web/lib/actions/getAccessToken.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//web lib actions getAccessToken
2+
// lib/getAccessToken.ts
3+
export async function getAccessToken() {
4+
const res = await fetch(process.env.ACTION_FORM_ACCESS_TOKEN_URL!, {
5+
method: 'POST',
6+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
7+
body: new URLSearchParams({
8+
grant_type: 'client_credentials',
9+
client_id: process.env.ACTION_FORM_CLIENT_ID!,
10+
client_secret: process.env.ACTION_FORM_CLIENT_SECRET!,
11+
scope: process.env.ACTION_FORM_SCOPE!
12+
}),
13+
});
14+
15+
if (!res.ok) throw new Error('Failed to fetch access token');
16+
const data = await res.json();
17+
return data.access_token as string;
18+
}
19+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//web lib actions equinorFormServerAction
2+
'use server'
3+
4+
import { ContactFormCatalogType } from '../../types'
5+
import { validateFormRequest } from '../../app/api/forms/validateFormRequest'
6+
import { getAccessToken } from './getAccessToken'
7+
8+
export default async function submitFormServerAction(formData: any, catelogNumber: string) {
9+
const urlString = process.env.ACTION_BASE_URL_FOR_FORMS + '/' + catelogNumber;
10+
11+
try {
12+
const token = await getAccessToken()
13+
const response = await fetch(urlString, {
14+
method: 'POST',
15+
headers: {
16+
'Ocp-Apim-Subscription-Key': process.env.ACTION_SUBSCRIPTION_KEY!,
17+
'Content-Type': 'application/json',
18+
'Authorization': `Bearer ${token}`,
19+
},
20+
body: formData
21+
})
22+
23+
const parsed = await response.json()
24+
console.log(response, parsed)
25+
if (parsed.status === 'failure' || parsed.Status?.includes('Failure')) {
26+
console.error('Failed to create ticket in ServiceNow')
27+
return { status: 500 }
28+
}
29+
30+
return { status: 200 }
31+
} catch (error) {
32+
console.error('Error occurred while sending request to ServiceNow', error)
33+
return { status: 500 }
34+
}
35+
}

web/templates/forms/ContactEquinorForm.tsx

Lines changed: 60 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//ContactEquinorForm
2+
13
'use client'
24
import { Icon } from '@equinor/eds-core-react'
35
import { error_filled } from '@equinor/eds-icons'
@@ -12,6 +14,10 @@ import type { ContactFormCatalogType } from '../../types'
1214
import FriendlyCaptcha from './FriendlyCaptcha'
1315
import { contentRegex, emailRegex, nameRegex } from './validations'
1416

17+
// Import the server action
18+
import submitFormServerAction from '@/lib/actions/submitFormServerAction'
19+
import { verifyCaptcha } from '@/lib/actions/verifyCaptcha'
20+
1521
type FormValues = {
1622
name: string
1723
email: string
@@ -31,13 +37,14 @@ const ContactEquinorForm = () => {
3137
return 'loginIssues'
3238
return null
3339
}
40+
3441
const {
3542
handleSubmit,
3643
control,
3744
reset,
3845
setError,
3946
formState: { errors, isSubmitted, isSubmitting, isSubmitSuccessful },
40-
} = useForm({
47+
} = useForm<FormValues>({
4148
defaultValues: {
4249
name: '',
4350
email: '',
@@ -47,21 +54,35 @@ const ContactEquinorForm = () => {
4754
})
4855

4956
const onSubmit = async (data: FormValues, event?: BaseSyntheticEvent) => {
57+
5058
if (isFriendlyChallengeDone) {
51-
const res = await fetch('/api/forms/service-now-contact-us', {
52-
body: JSON.stringify({
53-
data,
54-
frcCaptchaSolution: (event?.target as any)['frc-captcha-response']
55-
.value,
56-
catalogType: getCatalog(data.category),
57-
}),
58-
headers: {
59-
'Content-Type': 'application/json',
60-
},
61-
method: 'POST',
62-
})
63-
setServerError(res.status !== 200)
64-
setSuccessfullySubmitted(res.status === 200)
59+
const frcCaptchaSolution = (event?.target as any)['frc-captcha-response'].value
60+
const isCaptchaVerified = await verifyCaptcha(frcCaptchaSolution)
61+
62+
if(!isCaptchaVerified){
63+
return
64+
}
65+
66+
let cid = data.category.toLowerCase() === 'login issues' ? '49f29a93dbb2ac10f42b2208059619a7' : '66f0ff89db2e2644ff6272dabf961945';
67+
68+
let finalFormData = {
69+
"variables": {
70+
"requested_for": "tpawe",
71+
"cid": cid,
72+
"copytoemail": data.email,
73+
"external_emails": data.email,
74+
"tryingtoreach": "whoever can assist",
75+
"name": data.name,
76+
"category": data.category,
77+
"howcanwehelp": data.message,
78+
}
79+
}
80+
81+
// Call the server action directly
82+
const result = await submitFormServerAction(JSON.stringify(finalFormData), process.env.ACTION_CAT_CONTACTUS || '')
83+
84+
setServerError(result.status !== 200)
85+
setSuccessfullySubmitted(result.status === 200)
6586
} else {
6687
//@ts-ignore: TODO: types
6788
setError('root.notCompletedCaptcha', {
@@ -87,6 +108,7 @@ const ContactEquinorForm = () => {
87108
>
88109
{!isSuccessfullySubmitted && !isServerError && (
89110
<>
111+
{/* Name Field */}
90112
<Controller
91113
name='name'
92114
control={control}
@@ -97,10 +119,7 @@ const ContactEquinorForm = () => {
97119
message: intl('not_valid_input'),
98120
},
99121
}}
100-
render={({
101-
field: { ref, ...props },
102-
fieldState: { invalid, error },
103-
}) => {
122+
render={({ field: { ref, ...props }, fieldState: { invalid, error } }) => {
104123
const { name } = props
105124
return (
106125
<TextField
@@ -109,17 +128,15 @@ const ContactEquinorForm = () => {
109128
label={`${intl('name')}*`}
110129
inputRef={ref}
111130
aria-required='true'
112-
inputIcon={
113-
invalid ? (
114-
<Icon data={error_filled} title='error' />
115-
) : undefined
116-
}
131+
inputIcon={invalid ? <Icon data={error_filled} title='error' /> : undefined}
117132
helperText={error?.message}
118133
{...(invalid && { variant: 'error' })}
119134
/>
120135
)
121136
}}
122137
/>
138+
139+
{/* Email Field */}
123140
<Controller
124141
name='email'
125142
control={control}
@@ -130,58 +147,43 @@ const ContactEquinorForm = () => {
130147
message: intl('email_validation'),
131148
},
132149
}}
133-
render={({
134-
field: { ref, ...props },
135-
fieldState: { invalid, error },
136-
}) => {
150+
render={({ field: { ref, ...props }, fieldState: { invalid, error } }) => {
137151
const { name } = props
138152
return (
139153
<TextField
140154
{...props}
141155
id={`${name}_${formId}`}
142156
label={`${intl('email')}*`}
143157
inputRef={ref}
144-
inputIcon={
145-
invalid ? (
146-
<Icon data={error_filled} title='error' />
147-
) : undefined
148-
}
158+
inputIcon={invalid ? <Icon data={error_filled} title='error' /> : undefined}
149159
helperText={error?.message}
150160
aria-required='true'
151161
{...(invalid && { variant: 'error' })}
152162
/>
153163
)
154164
}}
155165
/>
166+
167+
{/* Category Select */}
156168
<Controller
157169
name='category'
158170
control={control}
159171
render={({ field: { ref, ...props } }) => {
160172
const { name } = props
161173
return (
162-
<Select
163-
{...props}
164-
selectRef={ref}
165-
id={`${name}_${formId}`}
166-
label={intl('category')}
167-
>
168-
<option value=''>
169-
{intl('form_please_select_an_option')}
170-
</option>
174+
<Select {...props} selectRef={ref} id={`${name}_${formId}`} label={intl('category')}>
175+
<option value=''>{intl('form_please_select_an_option')}</option>
171176
<option>{intl('contact_form_report_error')}</option>
172-
<option>
173-
{intl('contact_form_contact_department')}
174-
</option>
175-
<option>
176-
{intl('contact_form_investor_relations')}
177-
</option>
177+
<option>{intl('contact_form_contact_department')}</option>
178+
<option>{intl('contact_form_investor_relations')}</option>
178179
<option>{intl('contact_form_login_issues')}</option>
179180
<option>{intl('contact_form_other')}</option>
180181
</Select>
181182
)
182183
}}
183184
/>
184185

186+
{/* Message Field */}
185187
<Controller
186188
name='message'
187189
control={control}
@@ -192,10 +194,7 @@ const ContactEquinorForm = () => {
192194
message: intl('not_valid_input'),
193195
},
194196
}}
195-
render={({
196-
field: { ref, ...props },
197-
fieldState: { invalid, error },
198-
}) => {
197+
render={({ field: { ref, ...props }, fieldState: { invalid, error } }) => {
199198
const { name } = props
200199
return (
201200
<TextField
@@ -207,48 +206,33 @@ const ContactEquinorForm = () => {
207206
multiline
208207
rowsMax={10}
209208
aria-required='true'
210-
inputIcon={
211-
invalid ? (
212-
<Icon data={error_filled} title='error' />
213-
) : undefined
214-
}
209+
inputIcon={invalid ? <Icon data={error_filled} title='error' /> : undefined}
215210
helperText={error?.message}
216211
{...(invalid && { variant: 'error' })}
217212
/>
218213
)
219214
}}
220215
/>
216+
217+
{/* Captcha */}
221218
<div className='flex flex-col gap-2'>
222219
<FriendlyCaptcha
223-
doneCallback={() => {
224-
setIsFriendlyChallengeDone(true)
225-
}}
220+
doneCallback={() => setIsFriendlyChallengeDone(true)}
226221
errorCallback={(error: any) => {
227-
console.error(
228-
'FriendlyCaptcha encountered an error',
229-
error,
230-
)
222+
console.error('FriendlyCaptcha encountered an error', error)
231223
setIsFriendlyChallengeDone(true)
232224
}}
233225
/>
234-
{/*@ts-ignore: TODO: types*/}
235226
{errors?.root?.notCompletedCaptcha && (
236-
<p
237-
role='alert'
238-
className='flex gap-2 border border-clear-red-100 px-6 py-4 font-semibold text-slate-80'
239-
>
240-
{/*@ts-ignore: TODO: types*/}
241-
<span className='mt-1'>
242-
{errors.root.notCompletedCaptcha.message}
243-
</span>
227+
<p role='alert' className='flex gap-2 border border-clear-red-100 px-6 py-4 font-semibold text-slate-80'>
228+
<span className='mt-1'>{errors.root.notCompletedCaptcha.message}</span>
244229
<Icon data={error_filled} aria-label='Error' />
245230
</p>
246231
)}
247232
</div>
233+
248234
<Button type='submit'>
249-
{isSubmitting
250-
? intl('form_sending')
251-
: intl('contact_form_cta')}
235+
{isSubmitting ? intl('form_sending') : intl('contact_form_cta')}
252236
</Button>
253237
</>
254238
)}

0 commit comments

Comments
 (0)