| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- "use client";
- import {type Ref, useState, useEffect, useCallback, useImperativeHandle, useMemo } from "react";
- import { useRouter } from 'next/navigation';
- import { useForm, FormProvider } from "react-hook-form";
- import { useCustomToast } from "@/utils/hooks/useToast";
- import {useCheckoutAddress} from "@/utils/hooks/useCheckoutAddress"
- import {useLoginModal} from "@/providers/LoginModalProvider";
- import { useGetCustomerAddress } from "@utils/hooks/useGetCustomerAddress";
- import { EMAIL_REGEX } from "@utils/constants";
- import type {
- ShipAddressFormData,
- FullAddressFormData,
- CreateCheckoutAddressVariables,
- } from "@/types/checkout/type";
- import type { CustomerAddressItem,CheckEmailRegisteredData } from "@/types/customer/type";
- import type { CartDetail,CartAddress } from "@/types/cart/type";
- import { formatCartDetail } from "@/utils/cartDetailTools";
- import {clientFetch} from "@/lib/restApiClient";
- import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
- 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,
- onCartChange
- }: {
- ref: Ref<RefCheckoutAddressHandle>;
- loginEmail: string;
- cartData: CartDetail;
- onSaveAddress: (saveRes: {billingAddress: CartAddress; shippingAddress: CartAddress;}) => void;
- onCartChange: (data: CartDetail,dispatch: boolean) => void;
- }) {
- const router = useRouter();
- const {openLoginModal} = useLoginModal();
- const addressFormDefaultValues = useMemo(() => {
- return getAddressFormDataFromCart(cartData,loginEmail)
- },[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]);
- useEffect(() => {
- if(cartData.isGuest) return;
- const timer = window.requestAnimationFrame(() => {
- addressForm.reset(addressFormDefaultValues);
- });
- return () => window.cancelAnimationFrame(timer)
- },[cartData.isGuest,addressFormDefaultValues]);
- const verifyEmailIsRegistered = async (value: string) => {
- if(!cartData.isGuest) return;
- if(!EMAIL_REGEX.test(value)) return;
- const param = new URLSearchParams({ email: value });
- const res = await clientFetch<CheckEmailRegisteredData>(`/api/customer/check-email?${param.toString()}`,{
- method: 'GET',
- });
- if(res.success && res.data.exists) { // 注册过
- openLoginModal({
- defaultEmail: value,
- onFinalResult: async (res) => {
- if(res.mergeCartSuccess) { // 购物车合并成功
- if(res.cartAfterMerge){
- const newCartDetail = formatCartDetail(res.cartAfterMerge);
- onCartChange(newCartDetail,false);
- }
-
- } else { // 购物车合并失败
- // 三种 情况: 登录成功; 注册成功;
- if(res.loginSuccess) {
- // 登录成功 需要刷新页面
- showToast('You have logged in successfully. But something wrong','danger');
- await confirmDialog({
- title: "Warning",
- content: "You have logged in successfully. But something wrong. You must refresh the page.",
- noCancel: true,
- });
- window.location.reload();
- }
- if(res.registerSuccess && !res.loginSuccess) {
- // 注册成功,去登录
- await confirmDialog({
- title: "Warning",
- content: "You have registered successfully. But something wrong. You have to login.",
- noCancel: true,
- });
- router.replace('/customer/login');
- }
- }
- }
- });
- }
-
- };
- // 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}
- onEmailChange={verifyEmailIsRegistered}
- />
- <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>
- }
- />
-
- </>);
- };
|