Skip to content

Commit 387f2f9

Browse files
Magdalena Markusikorzechdev
Magdalena Markusik
andauthored
Add useSubmit hook (saleor#660)
- add some user mutations, since SDK is no longer maintained it's a matter of time until we'll need to remove it altogether - add useSubmit hook - most submits in checkout are simple such util could be used so it reduces boilerplate a little - move set billing same as shipping login to a separate hook to make the component more readable - add better names for "FormData" that was named the same everywhere and now its like UserRegisterFormData, DeliveryMethodUpdateFormData etc. Co-authored-by: Dawid <[email protected]>
1 parent 21c70ed commit 387f2f9

19 files changed

+521
-573
lines changed

packages/checkout-storefront/src/graphql/index.ts

+72
Original file line numberDiff line numberDiff line change
@@ -25997,6 +25997,44 @@ export type UserRegisterMutation = {
2599725997
} | null;
2599825998
};
2599925999

26000+
export type PasswordResetMutationVariables = Exact<{
26001+
email: Scalars["String"];
26002+
token: Scalars["String"];
26003+
password: Scalars["String"];
26004+
}>;
26005+
26006+
export type PasswordResetMutation = {
26007+
__typename?: "Mutation";
26008+
setPassword?: {
26009+
__typename?: "SetPassword";
26010+
errors: Array<{
26011+
__typename?: "AccountError";
26012+
message?: string | null;
26013+
field?: string | null;
26014+
code: AccountErrorCode;
26015+
}>;
26016+
} | null;
26017+
};
26018+
26019+
export type RequestPasswordResetMutationVariables = Exact<{
26020+
email: Scalars["String"];
26021+
channel: Scalars["String"];
26022+
redirectUrl: Scalars["String"];
26023+
}>;
26024+
26025+
export type RequestPasswordResetMutation = {
26026+
__typename?: "Mutation";
26027+
requestPasswordReset?: {
26028+
__typename?: "RequestPasswordReset";
26029+
errors: Array<{
26030+
__typename?: "AccountError";
26031+
message?: string | null;
26032+
field?: string | null;
26033+
code: AccountErrorCode;
26034+
}>;
26035+
} | null;
26036+
};
26037+
2600026038
export const AccountErrorFragmentDoc = gql`
2600126039
fragment AccountErrorFragment on AccountError {
2600226040
message
@@ -26728,3 +26766,37 @@ export function useUserRegisterMutation() {
2672826766
UserRegisterDocument
2672926767
);
2673026768
}
26769+
export const PasswordResetDocument = gql`
26770+
mutation passwordReset($email: String!, $token: String!, $password: String!) {
26771+
setPassword(email: $email, token: $token, password: $password) {
26772+
errors {
26773+
message
26774+
field
26775+
code
26776+
}
26777+
}
26778+
}
26779+
`;
26780+
26781+
export function usePasswordResetMutation() {
26782+
return Urql.useMutation<PasswordResetMutation, PasswordResetMutationVariables>(
26783+
PasswordResetDocument
26784+
);
26785+
}
26786+
export const RequestPasswordResetDocument = gql`
26787+
mutation requestPasswordReset($email: String!, $channel: String!, $redirectUrl: String!) {
26788+
requestPasswordReset(email: $email, channel: $channel, redirectUrl: $redirectUrl) {
26789+
errors {
26790+
message
26791+
field
26792+
code
26793+
}
26794+
}
26795+
}
26796+
`;
26797+
26798+
export function useRequestPasswordResetMutation() {
26799+
return Urql.useMutation<RequestPasswordResetMutation, RequestPasswordResetMutationVariables>(
26800+
RequestPasswordResetDocument
26801+
);
26802+
}

packages/checkout-storefront/src/graphql/user.graphql

+20
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,23 @@ mutation userRegister($input: AccountRegisterInput!) {
77
}
88
}
99
}
10+
11+
mutation passwordReset($email: String!, $token: String!, $password: String!) {
12+
setPassword(email: $email, token: $token, password: $password) {
13+
errors {
14+
message
15+
field
16+
code
17+
}
18+
}
19+
}
20+
21+
mutation requestPasswordReset($email: String!, $channel: String!, $redirectUrl: String!) {
22+
requestPasswordReset(email: $email, channel: $channel, redirectUrl: $redirectUrl) {
23+
errors {
24+
message
25+
field
26+
code
27+
}
28+
}
29+
}

packages/checkout-storefront/src/hooks/useAlerts/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type CheckoutScope =
3333
| "userRegister"
3434
| "requestPasswordReset"
3535
| "checkoutLinesUpdate"
36+
| "checkoutLinesDelete"
3637
| "checkoutEmailUpdate"
3738
| "resetPassword"
3839
| "login";
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,23 @@
11
import { useCheckoutCustomerAttachMutation } from "@/checkout-storefront/graphql";
22
import { useCheckout } from "@/checkout-storefront/hooks";
3-
import { useCheckoutUpdateStateChange } from "@/checkout-storefront/state/updateStateStore";
4-
import { useLocale } from "@/checkout-storefront/hooks/useLocale";
5-
import { extractMutationErrors, localeToLanguageCode } from "@/checkout-storefront/lib/utils";
63
import { useAuthState } from "@saleor/sdk";
7-
import { useEffect, useCallback } from "react";
4+
import { useEffect } from "react";
5+
import { useSubmit } from "@/checkout-storefront/hooks/useSubmit";
86

97
export const useCustomerAttach = () => {
108
const { checkout, loading } = useCheckout();
119
const { user, authenticated } = useAuthState();
12-
const { locale } = useLocale();
1310

1411
const [{ fetching }, customerAttach] = useCheckoutCustomerAttachMutation();
1512

16-
const { setCheckoutUpdateState } = useCheckoutUpdateStateChange("checkoutCustomerAttach");
17-
18-
const attachCustomer = useCallback(async () => {
19-
if (checkout?.user?.id === user?.id || fetching || loading) {
20-
return;
21-
}
22-
23-
setCheckoutUpdateState("loading");
24-
25-
const response = await customerAttach({
26-
checkoutId: checkout.id,
27-
languageCode: localeToLanguageCode(locale),
28-
});
29-
30-
const [hasErrors] = extractMutationErrors(response);
31-
32-
setCheckoutUpdateState(hasErrors ? "error" : "success");
33-
}, [
34-
checkout?.user?.id,
35-
checkout.id,
36-
user?.id,
37-
fetching,
38-
loading,
39-
setCheckoutUpdateState,
40-
customerAttach,
41-
locale,
42-
]);
13+
const handleSubmit = useSubmit<{}, typeof customerAttach>({
14+
scope: "checkoutCustomerAttach",
15+
shouldAbort: () => checkout?.user?.id === user?.id || fetching || loading,
16+
onSubmit: customerAttach,
17+
formDataParse: ({ languageCode, checkoutId }) => ({ languageCode, checkoutId }),
18+
});
4319

4420
useEffect(() => {
45-
void attachCustomer();
46-
}, [authenticated, attachCustomer]);
21+
void handleSubmit({});
22+
}, [authenticated, handleSubmit]);
4723
};

packages/checkout-storefront/src/hooks/useFormDebouncedSubmit.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { debounce, isEqual } from "lodash-es";
33
import { useCallback, useRef } from "react";
44

55
interface UseFormAutofillSubmit<TFormData extends FormDataBase> {
6-
onSubmit: (formData: TFormData) => Promise<void> | void;
6+
onSubmit: (formData: TFormData) => Promise<any> | void;
77
getValues: () => TFormData;
88
defaultFormData?: TFormData;
99
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { Exact, LanguageCodeEnum } from "@/checkout-storefront/graphql";
2+
import { useAlerts } from "@/checkout-storefront/hooks/useAlerts";
3+
import { useCheckout } from "@/checkout-storefront/hooks/useCheckout";
4+
import { ApiErrors } from "@/checkout-storefront/hooks/useErrors";
5+
import { useLocale } from "@/checkout-storefront/hooks/useLocale";
6+
import { FormDataBase } from "@/checkout-storefront/lib/globalTypes";
7+
import { extractMutationErrors, localeToLanguageCode } from "@/checkout-storefront/lib/utils";
8+
import {
9+
CheckoutUpdateStateScope,
10+
useCheckoutUpdateStateChange,
11+
} from "@/checkout-storefront/state/updateStateStore";
12+
import { useCallback } from "react";
13+
import { OperationResult } from "urql";
14+
15+
type MutationVars<MutationFn> = MutationFn extends (vars: Exact<infer Vars>) => any ? Vars : never;
16+
type MutationData<MutationFn> = MutationFn extends (vars: any) => Promise<infer Data>
17+
? Data
18+
: never;
19+
20+
const commonVars = ["languageCode", "channel", "checkoutId"] as const;
21+
type CommonVar = typeof commonVars[number];
22+
23+
type CommonVars = Record<CommonVar, string> & { languageCode: LanguageCodeEnum };
24+
25+
export type SubmitReturnWithErrors<TData extends FormDataBase> = Promise<{
26+
hasErrors: boolean;
27+
errors: ApiErrors<TData>;
28+
}>;
29+
30+
interface UseSubmitProps<
31+
TData extends FormDataBase,
32+
TMutationFn extends (vars: any) => Promise<OperationResult<any, any>>
33+
> {
34+
scope: CheckoutUpdateStateScope;
35+
onSubmit: (vars: MutationVars<TMutationFn>) => Promise<MutationData<TMutationFn>>;
36+
formDataParse: (data: TData & CommonVars) => MutationVars<TMutationFn>;
37+
shouldAbort?: ((formData: TData) => Promise<boolean>) | ((formData: TData) => boolean);
38+
onAbort?: (FormData: TData) => void;
39+
onSuccess?: (formData: TData, result: MutationData<TMutationFn>) => void;
40+
onError?: (errors: ApiErrors<TData>, formData: TData) => void;
41+
onEnter?: (formData: TData) => void;
42+
}
43+
44+
type SubmitFn<TData extends FormDataBase> = (formData: TData) => SubmitReturnWithErrors<TData>;
45+
46+
export const useSubmit = <
47+
TData extends FormDataBase,
48+
TMutationFn extends (vars: any) => Promise<OperationResult<any, any>>
49+
>({
50+
onSuccess,
51+
onError,
52+
onEnter,
53+
onSubmit,
54+
onAbort,
55+
scope,
56+
shouldAbort,
57+
formDataParse,
58+
}: UseSubmitProps<TData, TMutationFn>): SubmitFn<TData> => {
59+
const { setCheckoutUpdateState } = useCheckoutUpdateStateChange(scope);
60+
const { checkout } = useCheckout();
61+
const { showErrors } = useAlerts("checkoutDeliveryMethodUpdate");
62+
const localeData = useLocale();
63+
64+
const handleSubmit = useCallback(
65+
async (formData: TData = {} as TData) => {
66+
if (typeof onEnter === "function") {
67+
onEnter(formData);
68+
}
69+
70+
const shouldAbortSubmit =
71+
typeof shouldAbort === "function" ? await shouldAbort(formData) : false;
72+
73+
if (shouldAbortSubmit) {
74+
if (typeof onAbort === "function") {
75+
onAbort(formData);
76+
}
77+
return { hasErrors: false, errors: [] };
78+
}
79+
80+
const commonData: CommonVars = {
81+
languageCode: localeToLanguageCode(localeData.locale),
82+
channel: checkout.channel.slug,
83+
checkoutId: checkout.id,
84+
};
85+
86+
const result = await onSubmit(formDataParse({ ...formData, ...commonData }));
87+
88+
const [hasErrors, errors] = extractMutationErrors<TData>(result);
89+
90+
if (!hasErrors) {
91+
typeof onSuccess === "function" && onSuccess(formData, result);
92+
setCheckoutUpdateState("success");
93+
return { hasErrors, errors };
94+
}
95+
96+
typeof onError === "function" && onError(errors, formData);
97+
setCheckoutUpdateState("error");
98+
showErrors(errors);
99+
return { hasErrors, errors };
100+
},
101+
[
102+
checkout.channel.slug,
103+
checkout.id,
104+
formDataParse,
105+
localeData.locale,
106+
onAbort,
107+
onEnter,
108+
onError,
109+
onSubmit,
110+
onSuccess,
111+
setCheckoutUpdateState,
112+
shouldAbort,
113+
showErrors,
114+
]
115+
);
116+
117+
return handleSubmit;
118+
};

0 commit comments

Comments
 (0)