| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- "use client";
- import {useState, useEffect} from "react";
- import {useApolloClient} from "@apollo/client/react";
- import { GET_CHECKOUT_ADDRESSES,CREATE_CHECKOUT_ADDRESS } from "@/graphql";
- import { LoadingSpinner } from "@components/common/LoadingSpinner";
- // import { useForm, SubmitHandler } from "react-hook-form"
- import { useForm, FormProvider } from "react-hook-form"
- import {normalizePhoneForForm} from "@/utils/phoneNumberTools";
- import {
- CheckoutAddressNode,
- Country,
- ShipAddressFormData,
- BillAddressFormData,
- FullAddressFormData
- } from "@/types/checkout/type";
- import AddressResultDisplay from "./AddressResultDisplay";
- import AddressResultDisplayLoading from "./AddressResultDisplayLoading";
- import CheckoutAddressModal from "./CheckoutAddressModal";
- import ShippingAddressCheckout from "./ShippingAddressCheckout";
- import BillingAddressCheckout from "./BillingAddressCheckout";
- import { useCustomToast } from "@/utils/hooks/useToast";
- /***
- * 不用useForShipping字段了
- * 以shipping address 为准,根据shippingaddress 设置billing address
- * 保存完地址之后要重新获取运输方式和支付方式
- */
- /**
- * 生成createCheckoutAddress接口的参数
- */
- function generateSaveCheckoutAddressParam(formData: FullAddressFormData) {
- const formDataShippingAddress:ShipAddressFormData = {
- shippingAddressId: formData.shippingAddressId,
- shippingEmail: formData.shippingEmail,
- shippingFirstName: formData.shippingFirstName,
- shippingLastName: formData.shippingLastName,
- shippingCompanyName: formData.shippingCompanyName,
- shippingAddress: formData.shippingAddress,
- shippingCountry: formData.shippingCountry,
- shippingState: formData.shippingState,
- shippingCity: formData.shippingCity,
- shippingPostcode: formData.shippingPostcode,
- shippingPhoneNumber: formData.shippingPhoneNumber,
- };
- let formDataBillingAddress = {
- billingAddressId: formData.billingAddressId,
- billingEmail: formData.billingEmail,
- billingFirstName: formData.billingFirstName,
- billingLastName: formData.billingLastName,
- billingCompanyName : formData.billingCompanyName,
- billingAddress: formData.billingAddress,
- billingCountry: formData.billingCountry,
- billingState: formData.billingState,
- billingCity: formData.billingCity,
- billingPostcode: formData.billingPostcode,
- billingPhoneNumber: formData.billingPhoneNumber,
- useForShipping: false,
- };
- if(formData.billingSameAsShipping) {
- formDataBillingAddress = {
- billingAddressId: formData.billingAddressId,
- billingEmail: formDataShippingAddress.shippingEmail,
- billingFirstName: formDataShippingAddress.shippingFirstName,
- billingLastName: formDataShippingAddress.shippingLastName,
- billingCompanyName : formDataShippingAddress.shippingCompanyName,
- billingAddress: formDataShippingAddress.shippingAddress,
- billingCountry: formDataShippingAddress.shippingCountry,
- billingState: formDataShippingAddress.shippingState,
- billingCity: formDataShippingAddress.shippingCity,
- billingPostcode: formDataShippingAddress.shippingPostcode,
- billingPhoneNumber: formDataShippingAddress.shippingPhoneNumber,
- useForShipping: true,
- }
- }
- return {
- ...formDataShippingAddress,
- ...formDataBillingAddress
- };
-
- }
- /**
- * 对比shipping address与billing address的数据是否完全相同
- */
- function addressIsSame(billAddress: BillAddressFormData | null, shipAddress: ShipAddressFormData | null) {
- if(billAddress === null && shipAddress === null) {
- return true;
- } else if(billAddress !== null && shipAddress !== null) {
- return (
- billAddress.billingEmail === shipAddress.shippingEmail &&
- billAddress.billingFirstName === shipAddress.shippingFirstName &&
- billAddress.billingLastName === shipAddress.shippingLastName &&
- billAddress.billingCompanyName === shipAddress.shippingCompanyName &&
- billAddress.billingAddress === shipAddress.shippingAddress &&
- billAddress.billingCity === shipAddress.shippingCity &&
- billAddress.billingState === shipAddress.shippingState &&
- billAddress.billingCountry === shipAddress.shippingCountry &&
- billAddress.billingPostcode === shipAddress.shippingPostcode &&
- billAddress.billingPhoneNumber === shipAddress.shippingPhoneNumber
- );
- } else {
- return false;
- }
- }
- export default function CheckoutWrapper({
- loginEmail,
- countries
- }: {
- loginEmail: string;
- countries: Country[]
- }) {
- const { showToast } = useCustomToast();
- const apolloClient = useApolloClient();
- // useForm() 只会在组件初始化时读取一次 defaultValues
- const addressForm = useForm<FullAddressFormData>({
- mode: "onBlur", // 或 "onChange"
- reValidateMode: "onChange",
- });
- const [addressModalOpen, setAddressModalOpen] = useState(false);
- const [loadingData, setLoadingData] = useState(true);
- const [serverShippingAddress, setServerShippingAddress] = useState<ShipAddressFormData | null>(null);
- const [serverBillingAddress, setServerBillingAddress] = useState<BillAddressFormData | null>(null);
- const [addressSaving, setAddressSaving] = useState(false);
- // 获取checkout address
- function getCheckoutAddress(
- resolveCallback: (shippingAddress:ShipAddressFormData, billingAddress: BillAddressFormData) => void = () => {},
- rejectCallback: () => void = ()=> {}
- ) {
- return apolloClient.query({
- query: GET_CHECKOUT_ADDRESSES,
- fetchPolicy: "no-cache",
- context: {
- fetchOptions: {
- cache: 'no-store',
- },
-
- }
- }).then((res) => {
- const address = res.data?.collectionGetCheckoutAddresses?.edges?.map(
- (edge: { node: CheckoutAddressNode }) => edge.node
- ) || [];
- const billAddress = address.find((a: CheckoutAddressNode) => a?.addressType === "cart_billing") || null;
- const shipAddress = address.find((a: CheckoutAddressNode) => a?.addressType === "cart_shipping") || null;
- const shipCountry = shipAddress?.country || "US";
- const billCountry = billAddress?.country || "US";
- const defaultBillAddress: BillAddressFormData = {
- billingAddressId: billAddress?._id || "",
- billingEmail: billAddress?.email ?? loginEmail ?? "",
- billingFirstName: billAddress?.firstName || "",
- billingLastName: billAddress?.lastName || "",
- billingCompanyName: billAddress?.companyName || "",
- billingAddress: billAddress?.address || "",
- billingCountry: billCountry,
- billingState: billAddress?.state || "",
- billingCity: billAddress?.city || "",
- billingPostcode: billAddress?.postcode || "",
- billingPhoneNumber: normalizePhoneForForm(billAddress?.phone, billCountry) || "",
- billingSameAsShipping: true, // 这个字段前端维护,不传给后端(bagisto是以billing address为准,但是运营要以shipping address为准,所以前端自己维护billing address与shipping address 同步)
- };
- const defaultShipAddress: ShipAddressFormData = {
- shippingAddressId: shipAddress?._id || "",
- shippingEmail: shipAddress?.email ?? loginEmail ?? "",
- shippingFirstName: shipAddress?.firstName || "",
- shippingLastName: shipAddress?.lastName || "",
- shippingCompanyName: shipAddress?.companyName || "",
- shippingAddress: shipAddress?.address || "",
- shippingCountry: shipCountry,
- shippingState: shipAddress?.state || "",
- shippingCity: shipAddress?.city || "",
- shippingPostcode: shipAddress?.postcode || "",
- shippingPhoneNumber: normalizePhoneForForm(shipAddress?.phone, shipCountry) || "",
- };
- const billSameAsShip = addressIsSame(defaultBillAddress, defaultShipAddress);
- defaultBillAddress.billingSameAsShipping = billSameAsShip;
- console.log('GET_CHECKOUT_ADDRESSES res ---- ',defaultShipAddress,defaultBillAddress);
- resolveCallback(defaultShipAddress,defaultBillAddress);
- return {
- shippingAddress: defaultShipAddress,
- billingAddress: defaultBillAddress
- }
-
- }).catch((err) => {
- console.error('GET_CHECKOUT_ADDRESSES error ---- ',err);
- // setLoadingData(false);
- rejectCallback();
- return null;
- });
- }
- // 开发环境会执行两次 https://zh-hans.react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development
- useEffect(() => {
- getCheckoutAddress((shippingAddress,billingAddress)=> {
- addressForm.reset({
- ...shippingAddress,
- ...billingAddress,
- });
- setServerShippingAddress({
- ...shippingAddress
- });
- setServerBillingAddress({
- ...billingAddress
- });
- setLoadingData(false);
- }, ()=> {
- setLoadingData(false);
- });
- }, []);
- const openAddressModal = () => {
- setAddressModalOpen(true);
- if(serverShippingAddress && serverBillingAddress) {
- addressForm.reset({
- ...serverShippingAddress,
- ...serverBillingAddress,
- });
- }
-
- };
- const closeAddressModal = (e:boolean) => {
- setAddressModalOpen(e);
- // 重置表单
- addressForm.reset();
- };
- const addressFormOnSubmit = async (formData: FullAddressFormData) => {
- // console.log('addressFormOnSubmit ---- ',formData); return;
- // 保存完地址之后还要重新请求地址,把保存后的地址id同步到表单里;还需要获取运输方式
- const saveAddressParam = generateSaveCheckoutAddressParam(formData);
- setAddressSaving(true);
- try {
- const res = await apolloClient.mutate({
- mutation: CREATE_CHECKOUT_ADDRESS,
- variables:saveAddressParam,
- });
- console.log('CREATE_CHECKOUT_ADDRESS res ====== ',res);
- const queryAddressRes = await getCheckoutAddress();
- if(queryAddressRes !== null) {
- addressForm.resetField('shippingAddressId',{
- defaultValue: queryAddressRes.shippingAddress.shippingAddressId
- });
- addressForm.resetField('billingAddressId',{
- defaultValue: queryAddressRes.billingAddress.billingAddressId
- });
- setServerShippingAddress({
- ...queryAddressRes.shippingAddress
- });
- setServerBillingAddress({
- ...queryAddressRes.billingAddress
- });
- }
- setAddressModalOpen(false);
- } catch(err) {
- // 错误处理
- console.error("save address error", err);
-
- showToast('Save address failed. Please try again.', 'danger');
- } finally {
- setAddressSaving(false);
- }
-
- };
- // function tt() {
- // const arr = [
- // ['US','3803800217'],
- // ['CH','+4186043315'],
- // ['US','334-208-6177'],
- // ['AU','+61 404103617'],
- // ['US','1-9166705105'],
- // ['CA','819 384 3221'],
- // ['US','(409) 239-9482'],
- // ['US','1-(651) 421-2762'],
- // ['CH', '1-792930242'],
- // ['US', '+1 4570438892'],
- // ['US', '1-+1 (404) 276-1068'],
- // ['CH', '+49 015164340021'], // 国家与phonecode不一致情况
- // ];
- // arr.forEach((item) => {
- // normalizePhoneForForm(item[1], item[0]);
- // });
- // }
- return (
- <section className="flex flex-col items-start justify-between lg:flex-row lg:justify-between">
- {/* <button className="w-25 h-8 bg-amber-500 flex items-center justify-center" onClick={tt}>test</button> */}
- {loadingData ? <AddressResultDisplayLoading />
- :<AddressResultDisplay shippingAddress={serverShippingAddress}
- onAddressModalOpenClick={openAddressModal}
- />}
- <CheckoutAddressModal
- isOpen={addressModalOpen}
- onClose={closeAddressModal}
- body={
- <>
- <FormProvider {...addressForm}>
- <form>
- <ShippingAddressCheckout countries={countries} />
- <BillingAddressCheckout countries={countries} />
- </form>
- </FormProvider>
- </>
- }
- footer={
- <button className="block flex justify-center items-center w-full h-12 bg-ly-green rounded-3xl text-white text-ly-16 font-bold"
- onClick={addressForm.handleSubmit(addressFormOnSubmit)}
- >
- {addressSaving ? <LoadingSpinner /> : 'Save'}
- </button>
- }
- />
-
- </section>
-
- );
- };
|