|
|
@@ -0,0 +1,448 @@
|
|
|
+"use client";
|
|
|
+
|
|
|
+import {Ref, useState, useEffect, useCallback, useImperativeHandle } from "react";
|
|
|
+
|
|
|
+import { useForm, FormProvider } from "react-hook-form";
|
|
|
+import { useCustomToast } from "@/utils/hooks/useToast";
|
|
|
+import {useCheckoutAddress} from "@/utils/hooks/useCheckoutAddress"
|
|
|
+
|
|
|
+import { useGetCustomerAddress } from "@utils/hooks/useGetCustomerAddress";
|
|
|
+import {
|
|
|
+ ShipAddressFormData,
|
|
|
+ FullAddressFormData,
|
|
|
+ CreateCheckoutAddressVariables,
|
|
|
+} from "@/types/checkout/type";
|
|
|
+import { CustomerAddressItem } from "@/types/customer/type";
|
|
|
+import { CartDetail,CartAddress } from "@/types/cart/type";
|
|
|
+
|
|
|
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
|
|
|
+
|
|
|
+import {normalizePhoneForForm} from "@/utils/phoneNumberTools";
|
|
|
+
|
|
|
+import AddressResultDisplay from "./AddressResultDisplay";
|
|
|
+import ShippingAddressCheckout from "./ShippingAddressCheckout";
|
|
|
+import BillingAddressCheckout from "./BillingAddressCheckout";
|
|
|
+import CommonModal from "@/components/theme/ui/CommonModal";
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+export interface RefCheckoutAddressHandle {
|
|
|
+ getAddressFormDate: () => void;
|
|
|
+ validateAddressForm: () => Promise<boolean>;
|
|
|
+}
|
|
|
+
|
|
|
+/***
|
|
|
+ * 不用useForShipping字段了
|
|
|
+ * 以shipping address 为准,根据shippingaddress 设置billing address
|
|
|
+ * 保存完地址之后要重新获取运输方式和支付方式
|
|
|
+ */
|
|
|
+
|
|
|
+/**
|
|
|
+ * 地址表单默认值
|
|
|
+ */
|
|
|
+function getBillingAddressFormData(billingAddress:CartAddress) {
|
|
|
+ const billingCountry = billingAddress.country || 'US';
|
|
|
+ const billingPhoneNumber = normalizePhoneForForm(billingAddress.phone, billingCountry) || "";
|
|
|
+ const res = {
|
|
|
+ "billingAddressId": billingAddress.id,
|
|
|
+ "billingEmail": billingAddress.email,
|
|
|
+ "billingFirstName": billingAddress.firstName,
|
|
|
+ "billingLastName": billingAddress.lastName,
|
|
|
+ "billingCompanyName": "",
|
|
|
+ "billingAddress": billingAddress.address,
|
|
|
+ "billingCountry": billingCountry,
|
|
|
+ "billingState": billingAddress.state,
|
|
|
+ "billingCity": billingAddress.city,
|
|
|
+ "billingPostcode": billingAddress.postcode,
|
|
|
+ "billingPhoneNumber": billingPhoneNumber,
|
|
|
+ };
|
|
|
+
|
|
|
+ return res;
|
|
|
+}
|
|
|
+function getShippingAddressFormData(shippingAddress:CartAddress) {
|
|
|
+ const shippingCountry = shippingAddress.country || 'US';
|
|
|
+ const shippingPhoneNumber = normalizePhoneForForm(shippingAddress.phone, shippingCountry) || "";
|
|
|
+ const res = {
|
|
|
+ "shippingAddressId": shippingAddress.id,
|
|
|
+ "shippingEmail": shippingAddress.email,
|
|
|
+ "shippingFirstName": shippingAddress.firstName,
|
|
|
+ "shippingLastName": shippingAddress.lastName,
|
|
|
+ "shippingCompanyName": "",
|
|
|
+ "shippingAddress": shippingAddress.address,
|
|
|
+ "shippingCountry": shippingCountry,
|
|
|
+ "shippingState": shippingAddress.state,
|
|
|
+ "shippingCity": shippingAddress.city,
|
|
|
+ "shippingPostcode": shippingAddress.postcode,
|
|
|
+ "shippingPhoneNumber": shippingPhoneNumber,
|
|
|
+ };
|
|
|
+
|
|
|
+ return res;
|
|
|
+}
|
|
|
+function getAddressFormDataFromCart(cartDetail:CartDetail, loginEmail?: string) {
|
|
|
+ const shippingAddress = cartDetail.shippingAddress;
|
|
|
+ const billingAddress = cartDetail.billingAddress;
|
|
|
+ const defaultValues = {
|
|
|
+ "shippingAddressId": "",
|
|
|
+ "shippingEmail": loginEmail ?? '',
|
|
|
+ "shippingFirstName": "",
|
|
|
+ "shippingLastName": "",
|
|
|
+ "shippingCompanyName": "",
|
|
|
+ "shippingAddress": "",
|
|
|
+ "shippingCountry": "US",
|
|
|
+ "shippingState": "",
|
|
|
+ "shippingCity": "",
|
|
|
+ "shippingPostcode": "",
|
|
|
+ "shippingPhoneNumber": "",
|
|
|
+ "billingAddressId": "",
|
|
|
+ "billingEmail": loginEmail ?? '',
|
|
|
+ "billingFirstName": "",
|
|
|
+ "billingLastName": "",
|
|
|
+ "billingCompanyName": "",
|
|
|
+ "billingAddress": "",
|
|
|
+ "billingCountry": "US",
|
|
|
+ "billingState": "",
|
|
|
+ "billingCity": "",
|
|
|
+ "billingPostcode": "",
|
|
|
+ "billingPhoneNumber": "",
|
|
|
+ "billingSameAsShipping": true
|
|
|
+ }
|
|
|
+ if(billingAddress) {
|
|
|
+ const bs = getBillingAddressFormData(billingAddress);
|
|
|
+ defaultValues.billingAddressId = bs.billingAddressId;
|
|
|
+ defaultValues.billingEmail = bs.billingEmail;
|
|
|
+ defaultValues.billingFirstName = bs.billingFirstName;
|
|
|
+ defaultValues.billingLastName = bs.billingLastName;
|
|
|
+ defaultValues.billingCompanyName = bs.billingCompanyName;
|
|
|
+ defaultValues.billingAddress = bs.billingAddress;
|
|
|
+ defaultValues.billingCountry = bs.billingCountry;
|
|
|
+ defaultValues.billingState = bs.billingState;
|
|
|
+ defaultValues.billingCity = bs.billingCity;
|
|
|
+ defaultValues.billingPostcode = bs.billingPostcode;
|
|
|
+ defaultValues.billingPhoneNumber = bs.billingPhoneNumber;
|
|
|
+ // defaultValues.billingSameAsShipping = true;
|
|
|
+ }
|
|
|
+ if(shippingAddress) {
|
|
|
+ const ss = getShippingAddressFormData(shippingAddress);
|
|
|
+ defaultValues.shippingAddressId = ss.shippingAddressId;
|
|
|
+ defaultValues.shippingEmail = ss.shippingEmail;
|
|
|
+ defaultValues.shippingFirstName = ss.shippingFirstName;
|
|
|
+ defaultValues.shippingLastName = ss.shippingLastName;
|
|
|
+ defaultValues.shippingCompanyName = ss.shippingCompanyName;
|
|
|
+ defaultValues.shippingAddress = ss.shippingAddress;
|
|
|
+ defaultValues.shippingCountry = ss.shippingCountry;
|
|
|
+ defaultValues.shippingState = ss.shippingState;
|
|
|
+ defaultValues.shippingCity = ss.shippingCity;
|
|
|
+ defaultValues.shippingPostcode = ss.shippingPostcode;
|
|
|
+ defaultValues.shippingPhoneNumber = ss.shippingPhoneNumber;
|
|
|
+ }
|
|
|
+ if( defaultValues.billingEmail === defaultValues.shippingEmail &&
|
|
|
+ defaultValues.billingFirstName === defaultValues.shippingFirstName &&
|
|
|
+ defaultValues.billingLastName === defaultValues.shippingLastName &&
|
|
|
+ defaultValues.billingCompanyName === defaultValues.shippingCompanyName &&
|
|
|
+ defaultValues.billingAddress === defaultValues.shippingAddress &&
|
|
|
+ defaultValues.billingCountry === defaultValues.shippingCountry &&
|
|
|
+ defaultValues.billingState === defaultValues.shippingState &&
|
|
|
+ defaultValues.billingCity === defaultValues.shippingCity &&
|
|
|
+ defaultValues.billingPostcode === defaultValues.shippingPostcode &&
|
|
|
+ defaultValues.billingPhoneNumber === defaultValues.shippingPhoneNumber
|
|
|
+ ) {
|
|
|
+ defaultValues.billingSameAsShipping = true;
|
|
|
+ }
|
|
|
+ return defaultValues;
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 生成createCheckoutAddress接口的参数
|
|
|
+ */
|
|
|
+function generateSaveCheckoutAddressParam(formData: FullAddressFormData):CreateCheckoutAddressVariables {
|
|
|
+ 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
|
|
|
+ };
|
|
|
+
|
|
|
+}
|
|
|
+const myAddressPerPage = 50; // 每页的个数
|
|
|
+export function CheckoutAddress({
|
|
|
+ ref,
|
|
|
+ loginEmail,
|
|
|
+ cartData,
|
|
|
+ onSaveAddress,
|
|
|
+}: {
|
|
|
+ ref: Ref<RefCheckoutAddressHandle>;
|
|
|
+ loginEmail: string;
|
|
|
+ cartData: CartDetail;
|
|
|
+ onSaveAddress: (saveRes: {billingAddress: CartAddress; shippingAddress: CartAddress;}) => void;
|
|
|
+}) {
|
|
|
+
|
|
|
+ const addressFormDefaultValues = getAddressFormDataFromCart(cartData,loginEmail);
|
|
|
+
|
|
|
+ const {getCustomerAddress} = useGetCustomerAddress();
|
|
|
+ const { showToast } = useCustomToast();
|
|
|
+ const { saveCheckoutAddress } = useCheckoutAddress(loginEmail);
|
|
|
+
|
|
|
+ // useForm() 只会在组件初始化时读取一次 defaultValues
|
|
|
+ const addressForm = useForm<FullAddressFormData>({
|
|
|
+ mode: "onChange", // 或 "onChange"
|
|
|
+ reValidateMode: "onChange",
|
|
|
+ defaultValues: addressFormDefaultValues
|
|
|
+ });
|
|
|
+ const [myAddressList, setMyAddressList] = useState<CustomerAddressItem[]>([]);
|
|
|
+ const [showShipFormField, setShowShipFormField] = useState(false);
|
|
|
+ const [showBillFormField, setShowBillFormField] = useState(false);
|
|
|
+ const [addressFormModalOpen, setAddressFormModalOpen] = useState(false);
|
|
|
+
|
|
|
+
|
|
|
+ useImperativeHandle(ref, () => {
|
|
|
+ return {
|
|
|
+ // 获取用户填写的地址
|
|
|
+ getAddressFormDate: () => {
|
|
|
+ return addressForm.getValues();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 触发校验
|
|
|
+ validateAddressForm: () => {
|
|
|
+ return addressForm.trigger();
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ },[addressForm]);
|
|
|
+
|
|
|
+
|
|
|
+ const showShipFormFieldChange = useCallback((e: boolean) => {
|
|
|
+ setShowShipFormField(e);
|
|
|
+ },[]);
|
|
|
+ const showBillFormFieldChange = useCallback((e: boolean) => {
|
|
|
+ setShowBillFormField(e);
|
|
|
+ },[]);
|
|
|
+
|
|
|
+
|
|
|
+ const openAddressFormModal = () => {
|
|
|
+ setAddressFormModalOpen(true);
|
|
|
+ setShowShipFormField(false);
|
|
|
+ setShowBillFormField(false);
|
|
|
+ if(cartData.billingAddress && cartData.shippingAddress) {
|
|
|
+ addressForm.reset({
|
|
|
+ ...getAddressFormDataFromCart(cartData)
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ };
|
|
|
+ const closeAddressFormModal = () => {
|
|
|
+ setAddressFormModalOpen(false);
|
|
|
+ setShowShipFormField(false);
|
|
|
+ setShowBillFormField(false);
|
|
|
+ // 重置表单
|
|
|
+ addressForm.reset();
|
|
|
+ };
|
|
|
+ const addressFormOnSubmit = async (formData: FullAddressFormData) => {
|
|
|
+ // console.log('addressFormOnSubmit ---- ',formData); return;
|
|
|
+ const saveAddressParam = generateSaveCheckoutAddressParam(formData);
|
|
|
+
|
|
|
+ overlayLoading.start();
|
|
|
+ try {
|
|
|
+
|
|
|
+ const saveRes = await saveCheckoutAddress(saveAddressParam);
|
|
|
+ console.log('CREATE_CHECKOUT_ADDRESS res ====== ',saveRes);
|
|
|
+ if(!saveRes.error) {
|
|
|
+ // 地址保存成功后重新获取运输方式和支付方式
|
|
|
+ // 保存完地址之后,把保存后的地址id同步到表单里;
|
|
|
+ const createAddressData = saveRes.data;
|
|
|
+ if(!createAddressData) {
|
|
|
+ // 提示用户出错了,刷新页面
|
|
|
+ showToast('Something wrong. Please refresh the page.', 'danger');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ addressForm.resetField('shippingAddressId',{
|
|
|
+ defaultValue: createAddressData.shippingAddressId
|
|
|
+ });
|
|
|
+ addressForm.resetField('billingAddressId',{
|
|
|
+ defaultValue: createAddressData.billingAddressId
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ // 同步地址到购物车详情
|
|
|
+ const newBillingAddress = {
|
|
|
+ id: String(createAddressData.billingAddressId),
|
|
|
+ firstName: createAddressData.billingFirstName,
|
|
|
+ lastName: createAddressData.billingLastName,
|
|
|
+ email: createAddressData.billingEmail,
|
|
|
+ address: createAddressData.billingAddress,
|
|
|
+ city: createAddressData.billingCity,
|
|
|
+ state: createAddressData.billingState,
|
|
|
+ country: createAddressData.billingCountry,
|
|
|
+ postcode: createAddressData.billingPostcode,
|
|
|
+ phone: createAddressData.billingPhoneNumber
|
|
|
+ };
|
|
|
+ const newShippingAddress = {
|
|
|
+ id: String(createAddressData.shippingAddressId),
|
|
|
+ firstName: createAddressData.shippingFirstName,
|
|
|
+ lastName: createAddressData.shippingLastName,
|
|
|
+ email: createAddressData.shippingEmail,
|
|
|
+ address: createAddressData.shippingAddress,
|
|
|
+ city: createAddressData.shippingCity,
|
|
|
+ state: createAddressData.shippingState,
|
|
|
+ country: createAddressData.shippingCountry,
|
|
|
+ postcode: createAddressData.shippingPostcode,
|
|
|
+ phone: createAddressData.shippingPhoneNumber,
|
|
|
+ };
|
|
|
+
|
|
|
+ onSaveAddress({
|
|
|
+ billingAddress: newBillingAddress,
|
|
|
+ shippingAddress: newShippingAddress
|
|
|
+ });
|
|
|
+
|
|
|
+ } else {
|
|
|
+ showToast(saveRes.msg, 'danger');
|
|
|
+ }
|
|
|
+ setAddressFormModalOpen(false);
|
|
|
+ } catch(err) {
|
|
|
+ // 错误处理
|
|
|
+ console.error("save address error", err);
|
|
|
+
|
|
|
+ showToast('Save address failed. Please try again.', 'danger');
|
|
|
+ } finally {
|
|
|
+ overlayLoading.stop();
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ const noShipAddress = cartData.shippingAddress === null;
|
|
|
+ const noBillAddress = cartData.billingAddress === null;
|
|
|
+
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if(cartData.isGuest) return;
|
|
|
+
|
|
|
+ async function loadAddress() {
|
|
|
+
|
|
|
+ overlayLoading.start();
|
|
|
+ const res = await getCustomerAddress({
|
|
|
+ first: myAddressPerPage
|
|
|
+ });
|
|
|
+ overlayLoading.stop();
|
|
|
+ if(!res.error && res.data !== null) {
|
|
|
+ setMyAddressList(res.data.list);
|
|
|
+ } else {
|
|
|
+ showToast(res.msg,"danger");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ loadAddress();
|
|
|
+ },[showToast,cartData.isGuest]);
|
|
|
+
|
|
|
+ // 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 (<>
|
|
|
+ <AddressResultDisplay shippingAddress={cartData.shippingAddress || null}
|
|
|
+ onAddressModalOpenClick={openAddressFormModal}
|
|
|
+ />
|
|
|
+ <CommonModal
|
|
|
+ isOpen={addressFormModalOpen}
|
|
|
+ onClose={closeAddressFormModal}
|
|
|
+ header={
|
|
|
+ <div className="w-full box-border p-3.75 h-14.5 flex justify-between items-center">
|
|
|
+ <span>Shipping Address</span>
|
|
|
+ <button className="w-6 h-6" onClick={closeAddressFormModal}>
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon" className="h-6 transition-all ease-in-out hover:scale-110"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12"></path></svg>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ body={
|
|
|
+ <div className="box-border w-full h-full p-3.75">
|
|
|
+ <div>
|
|
|
+ <FormProvider {...addressForm}>
|
|
|
+ <form>
|
|
|
+ <ShippingAddressCheckout
|
|
|
+ noAddress={noShipAddress}
|
|
|
+ isGuest={cartData.isGuest}
|
|
|
+ customerAddressList={myAddressList}
|
|
|
+ showFormField={showShipFormField}
|
|
|
+ onShowFormFieldChange={showShipFormFieldChange}
|
|
|
+ />
|
|
|
+ <BillingAddressCheckout
|
|
|
+ noAddress={noBillAddress}
|
|
|
+ isGuest={cartData.isGuest}
|
|
|
+ customerAddressList={myAddressList}
|
|
|
+ showFormField={showBillFormField}
|
|
|
+ onShowFormFieldChange={showBillFormFieldChange}
|
|
|
+ />
|
|
|
+ </form>
|
|
|
+ </FormProvider>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ footer={
|
|
|
+ <div className="box-border w-full p-3.75">
|
|
|
+ <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)}
|
|
|
+ >
|
|
|
+ Save
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ />
|
|
|
+
|
|
|
+
|
|
|
+ </>);
|
|
|
+};
|