Skip to content

Commit 8126271

Browse files
committed
feat: add edit information page
1 parent 7e86976 commit 8126271

File tree

17 files changed

+434
-32
lines changed

17 files changed

+434
-32
lines changed
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
import { authClient } from '@/graphql/clients/authClient';
12
import { GET_CUSTOMER_BILLING } from '@/graphql/queries/customer';
23
import { GetCustomerBillingQuery } from '@/graphql/types/graphql';
34
import { useQuery } from '@apollo/client';
45
import React from 'react';
56

67
const Billing = () => {
7-
const { data } = useQuery<GetCustomerBillingQuery>(GET_CUSTOMER_BILLING);
8+
const { data } = useQuery<GetCustomerBillingQuery>(GET_CUSTOMER_BILLING, {
9+
client: authClient,
10+
});
811
const billing = data?.customer?.billing;
912

10-
return <div>{billing?.state}</div>;
13+
return <div></div>;
1114
};
1215

1316
export default Billing;

src/app/[locale]/(main)/(container)/profile/components/Menu.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { authClient } from '@/graphql/clients/authClient';
12
import { GET_CUSTOMER_PROFILE } from '@/graphql/queries/customer';
23
import { GetCustomerProfileQuery } from '@/graphql/types/graphql';
34
import { useQuery } from '@apollo/client';
@@ -7,13 +8,19 @@ import MenuHeader from './MenuHeader';
78
import MenuItems from './MenuItems';
89

910
const Menu = () => {
10-
const { data, loading, error } =
11-
useQuery<GetCustomerProfileQuery>(GET_CUSTOMER_PROFILE);
11+
const { data, loading, error } = useQuery<GetCustomerProfileQuery>(
12+
GET_CUSTOMER_PROFILE,
13+
{
14+
client: authClient,
15+
},
16+
);
1217

1318
const t = useTranslations();
1419

15-
const fullName = data?.customer?.firstName
16-
? data?.customer?.firstName + ' ' + data?.customer?.lastName
20+
const fullName = data?.customer?.billing?.firstName
21+
? data?.customer?.billing?.firstName +
22+
' ' +
23+
data?.customer?.billing?.lastName
1724
: t('profile.user');
1825

1926
const { items } = useMenuItems({
Lines changed: 328 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,333 @@
1-
import React from 'react';
1+
'use client';
2+
3+
import CustomSkeleton from '@/components/CustomSkeleton/CustomSkeleton';
4+
import ButtonWithLoading from '@/components/common/ButtonWithLoading';
5+
import { authClient } from '@/graphql/clients/authClient';
6+
import {
7+
GET_CUSTOMER_BILLING,
8+
UPDATE_CUSTOMER_MUTATION,
9+
} from '@/graphql/queries/customer';
10+
import { GET_COUNTRY_STATES } from '@/graphql/queries/general';
11+
import {
12+
GetCountryStatesQuery,
13+
GetCustomerBillingQuery,
14+
UpdateCustomerMutation,
15+
} from '@/graphql/types/graphql';
16+
import { Locale, languages } from '@/navigation';
17+
import { useMutation, useQuery } from '@apollo/client';
18+
import { yupResolver } from '@hookform/resolvers/yup';
19+
import { Autocomplete, Grid, TextField } from '@mui/material';
20+
import Card from '@mui/material/Card';
21+
import CardContent from '@mui/material/CardContent';
22+
import { useLocale, useTranslations } from 'next-intl';
23+
import {
24+
Controller,
25+
FormProvider,
26+
SubmitHandler,
27+
useForm,
28+
} from 'react-hook-form';
29+
import toast from 'react-hot-toast';
30+
import * as yup from 'yup';
31+
import CardHeader from '../components/CardHeader';
32+
33+
type FieldNames = Record<
34+
'firstName' | 'lastName' | 'state' | 'city' | 'address1' | 'postcode',
35+
string | null
36+
>;
237

338
const Page = () => {
4-
return <div>Page</div>;
39+
const t = useTranslations();
40+
41+
const locale = useLocale();
42+
43+
const states = useQuery<GetCountryStatesQuery>(GET_COUNTRY_STATES, {
44+
variables: {
45+
country: languages[locale as Locale].country,
46+
},
47+
client: authClient,
48+
});
49+
50+
const options =
51+
states.data?.countryStates?.map((state) => {
52+
return {
53+
id: state?.code!,
54+
value: state?.code!,
55+
label: state?.name!,
56+
};
57+
}) || [];
58+
59+
const labels: Record<keyof FieldNames, string> = {
60+
firstName: t('fields.firstName'),
61+
lastName: t('fields.lastName'),
62+
state: t('fields.state'),
63+
city: t('fields.city'),
64+
address1: t('fields.address1'),
65+
postcode: t('fields.postcode'),
66+
};
67+
const resolveSchema: yup.ObjectSchema<FieldNames> = yup.object({
68+
firstName: yup.string().nullable().required().label(labels.firstName),
69+
lastName: yup.string().nullable().required().label(labels.lastName),
70+
state: yup.string().nullable().required().label(labels.state),
71+
city: yup.string().nullable().required().label(labels.city),
72+
address1: yup.string().nullable().required().label(labels.address1),
73+
postcode: yup.string().nullable().required().label(labels.postcode),
74+
});
75+
76+
const form = useForm<FieldNames>({
77+
resolver: yupResolver(resolveSchema),
78+
});
79+
80+
const [mutateAsync, { loading }] = useMutation<UpdateCustomerMutation>(
81+
UPDATE_CUSTOMER_MUTATION,
82+
{
83+
client: authClient,
84+
},
85+
);
86+
87+
const onSubmit: SubmitHandler<FieldNames> = async (payload) => {
88+
const { errors } = await mutateAsync({
89+
variables: {
90+
billing: payload,
91+
},
92+
});
93+
if (!errors?.length) {
94+
toast.success(t('messages.defaultSuccess'));
95+
}
96+
};
97+
98+
const customer = useQuery<GetCustomerBillingQuery>(GET_CUSTOMER_BILLING, {
99+
client: authClient,
100+
onCompleted: (data) => {
101+
const billing = data.customer?.billing!;
102+
delete billing.__typename;
103+
form.reset({
104+
...billing,
105+
});
106+
},
107+
});
108+
109+
return (
110+
<FormProvider {...form}>
111+
<Card
112+
variant="outlined"
113+
sx={{
114+
flexGrow: 1,
115+
}}
116+
>
117+
<CardContent>
118+
<CardHeader title={t('profile.accountInfo')} />
119+
120+
<Grid
121+
container
122+
spacing={2}
123+
onSubmit={form.handleSubmit(onSubmit)}
124+
component="form"
125+
>
126+
<Grid item xs={6}>
127+
<CustomSkeleton isLoading={customer.loading}>
128+
<Controller
129+
control={form.control}
130+
name="firstName"
131+
render={({
132+
field: { name, value, onChange },
133+
fieldState: { error },
134+
}) => {
135+
return (
136+
<TextField
137+
disabled={loading}
138+
onChange={onChange}
139+
name={name}
140+
value={value || ''}
141+
variant="outlined"
142+
fullWidth
143+
label={labels[name]}
144+
error={!!error?.message}
145+
helperText={error?.message?.toString()}
146+
InputProps={{
147+
autoComplete: 'new-password',
148+
}}
149+
/>
150+
);
151+
}}
152+
/>
153+
</CustomSkeleton>
154+
</Grid>
155+
<Grid item xs={6}>
156+
<CustomSkeleton isLoading={customer.loading}>
157+
<Controller
158+
control={form.control}
159+
name="lastName"
160+
render={({
161+
field: { name, value, onChange },
162+
fieldState: { error },
163+
}) => {
164+
return (
165+
<TextField
166+
disabled={loading}
167+
onChange={onChange}
168+
name={name}
169+
value={value || ''}
170+
variant="outlined"
171+
fullWidth
172+
label={labels[name]}
173+
error={!!error?.message}
174+
helperText={error?.message?.toString()}
175+
InputProps={{
176+
autoComplete: 'new-password',
177+
}}
178+
/>
179+
);
180+
}}
181+
/>
182+
</CustomSkeleton>
183+
</Grid>
184+
<Grid item xs={6}>
185+
<CustomSkeleton isLoading={customer.loading || states.loading}>
186+
<Controller
187+
control={form.control}
188+
name="state"
189+
render={({
190+
field: { name, value, onChange },
191+
fieldState: { error },
192+
}) => {
193+
const _value = options.find(
194+
(option) => option.value === value,
195+
);
196+
197+
return (
198+
<Autocomplete
199+
onChange={(_event, option) => {
200+
onChange(option?.value);
201+
}}
202+
value={_value || { id: '', value: '', label: '' }}
203+
options={options}
204+
fullWidth
205+
disabled={loading}
206+
renderInput={(params) => (
207+
<TextField
208+
{...params}
209+
error={!!error?.message}
210+
helperText={error?.message?.toString()}
211+
label={labels[name]}
212+
InputProps={{
213+
autoComplete: 'new-password',
214+
...params.InputProps,
215+
}}
216+
/>
217+
)}
218+
/>
219+
);
220+
}}
221+
/>
222+
</CustomSkeleton>
223+
</Grid>
224+
<Grid item xs={6}>
225+
<CustomSkeleton isLoading={customer.loading}>
226+
<Controller
227+
control={form.control}
228+
name="city"
229+
render={({
230+
field: { name, value, onChange },
231+
fieldState: { error },
232+
}) => {
233+
return (
234+
<TextField
235+
disabled={loading}
236+
onChange={onChange}
237+
name={name}
238+
value={value || ''}
239+
variant="outlined"
240+
fullWidth
241+
label={labels[name]}
242+
error={!!error?.message}
243+
helperText={error?.message?.toString()}
244+
InputProps={{
245+
autoComplete: 'new-password',
246+
}}
247+
/>
248+
);
249+
}}
250+
/>
251+
</CustomSkeleton>
252+
</Grid>
253+
<Grid item xs={12}>
254+
<CustomSkeleton isLoading={customer.loading}>
255+
<Controller
256+
control={form.control}
257+
name="address1"
258+
render={({
259+
field: { name, value, onChange },
260+
fieldState: { error },
261+
}) => {
262+
return (
263+
<TextField
264+
disabled={loading}
265+
multiline
266+
rows={3}
267+
onChange={onChange}
268+
name={name}
269+
value={value || ''}
270+
variant="outlined"
271+
fullWidth
272+
label={labels[name]}
273+
error={!!error?.message}
274+
helperText={error?.message?.toString()}
275+
InputProps={{
276+
autoComplete: 'new-password',
277+
}}
278+
/>
279+
);
280+
}}
281+
/>
282+
</CustomSkeleton>
283+
</Grid>
284+
<Grid item xs={12}>
285+
<CustomSkeleton isLoading={customer.loading}>
286+
<Controller
287+
control={form.control}
288+
name="postcode"
289+
render={({
290+
field: { name, value, onChange },
291+
fieldState: { error },
292+
}) => {
293+
return (
294+
<TextField
295+
disabled={loading}
296+
onChange={onChange}
297+
name={name}
298+
value={value || ''}
299+
variant="outlined"
300+
fullWidth
301+
label={labels[name]}
302+
error={!!error?.message}
303+
helperText={error?.message?.toString()}
304+
InputProps={{
305+
autoComplete: 'new-password',
306+
}}
307+
/>
308+
);
309+
}}
310+
/>
311+
</CustomSkeleton>
312+
</Grid>
313+
<Grid item xs={12}>
314+
<CustomSkeleton isLoading={customer.loading}>
315+
<ButtonWithLoading
316+
isLoading={loading}
317+
size="large"
318+
variant="contained"
319+
color="primary"
320+
type="submit"
321+
>
322+
{t('buttons.save')}
323+
</ButtonWithLoading>
324+
</CustomSkeleton>
325+
</Grid>
326+
</Grid>
327+
</CardContent>
328+
</Card>
329+
</FormProvider>
330+
);
5331
};
6332

7333
export default Page;

src/app/[locale]/(main)/(container)/profile/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const Layout: FC<LayoutProps> = ({ children }) => {
1818
<DesktopView>
1919
<Box
2020
sx={{
21-
width: 260,
21+
minWidth: 250,
2222
border: '1px solid',
2323
borderColor: 'divider',
2424
borderRadius: 1,

0 commit comments

Comments
 (0)