| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- "use client";
- import {useMemo,useEffect, useState} from "react";
- import { useQuery } from "@apollo/client/react";
- import {
- useFormContext,
- useWatch,
- Controller
- } from "react-hook-form";
- import {
- formatFinalPhoneValue,
- getPhoneCodeBycountryCode
- } from "@/components/theme/ui/PhoneNumberInput/phoneCodeMetaData";
- import { GET_COUNTRY_STATES } from "@/graphql";
- import { EMAIL_REGEX, IS_VALID_INPUT, IS_VALID_FULL_PHONE, IS_VALID_PHONECODE } from "@utils/constants";
- import { Country } from "@/types/checkout/type";
- import InputText from "@/components/theme/ui/InputText";
- import Select from "@/components/theme/ui/Select";
- import PhoneNumberInput from "@/components/theme/ui/PhoneNumberInput/PhoneNumberInput";
- import { LoadingSpinner } from "@components/common/LoadingSpinner";
- export default function ShippingAddressCheckout ({
- countries,
- }:{
- countries: Country[];
- }) {
- const {
- getValues,
- setValue,
- register,
- formState: { errors },
- control
- } = useFormContext() // retrieve all hook methods
- const countriesOptions = useMemo(() => {
- return countries.map((country) => ({
- value: country.code,
- label: country.name,
- id: String(country._id),
- }))
- },[countries]);
- const selectedCountryCode = useWatch({
- control,
- name: 'shippingCountry',
- });
- const selectedCountry = useMemo(() => {
- return countries.find( (country) => country.code === selectedCountryCode)
- }, [countries, selectedCountryCode]);
- const { data: statesData, loading: statesLoading } = useQuery(GET_COUNTRY_STATES, {
- variables: {
- countryId: selectedCountry?._id
- },
- skip: !selectedCountryCode,
- });
- let statesOptions: { value: string; label: string, id: string }[] = [];
- if(statesData) {
- statesOptions = statesData.countryStates.map((state) => ({
- value: state.code,
- label: state.defaultName,
- id: String(state._id),
- }));
- }
- // 是否用户手动选择过区号
- const [hasUserSelectedPhoneCode, setHasUserSelectedPhoneCode] = useState(false);
- // 国家变更后 state字段的值需要清空
- // 选择国家时同步电话区号
- useEffect(() => {
- const preShippingPhoneNum = getValues('shippingPhoneNumber');
- const regPhoneCode = IS_VALID_PHONECODE;
-
- if(preShippingPhoneNum && !hasUserSelectedPhoneCode) {
- const newPhoneCode = getPhoneCodeBycountryCode(selectedCountryCode);
- const codePart = regPhoneCode.exec(preShippingPhoneNum);
- let phone = preShippingPhoneNum;
- if(codePart) {
- phone = preShippingPhoneNum.replace(codePart[0], '');
- }
- setValue('shippingPhoneNumber',formatFinalPhoneValue(newPhoneCode, phone))
- }
-
- },[selectedCountryCode,hasUserSelectedPhoneCode,getValues,setValue]);
- return (
- <div className="box-border w-full">
- <div className="w-full">
- <label className="text-ly-12 block mb-4 font-semibold">
- Email *
- </label>
- <InputText
- type="email"
- placeholder="Enter your email address"
- {...register("shippingEmail", {
- required: "Email is required",
- pattern: {
- value: EMAIL_REGEX,
- message: "Please enter a valid email address",
- }
- })}
- error={errors.shippingEmail && errors.shippingEmail.message as string}
- />
- </div>
- <div className="w-full mt-4">
- <label className="text-ly-12 block mb-4 font-semibold">
- Name *
- </label>
- <div className="w-full flex justify-between">
- <div className="w-41.25 flex-initial">
- <InputText
- type="text"
- placeholder="First name"
- {...register("shippingFirstName", {
- required: "First name is required",
- pattern: {
- value: IS_VALID_INPUT,
- message: "Invalid First Name",
- }
- })}
- error={errors.shippingFirstName && errors.shippingFirstName.message as string}
- />
- </div>
- <div className="w-41.25 flex-initial">
- <InputText
- type="text"
- placeholder="Last name"
- {...register("shippingLastName", {
- required: "Last name is required",
- pattern: {
- value: IS_VALID_INPUT,
- message: "Invalid Last Name",
- }
- })}
- error={errors.shippingLastName && errors.shippingLastName.message as string}
- />
- </div>
- </div>
- </div>
- <div className="w-full mt-4">
- <label className="text-ly-12 block mb-4 font-semibold">Address *</label>
- <InputText
- type="text"
- placeholder="Please Input Your Detailed Address With Apt. No"
- {...register("shippingAddress", {
- required: "Address is required",
- })}
- error={errors.shippingAddress && errors.shippingAddress.message as string}
- />
-
- </div>
- <div className="w-full mt-4">
- <label className="text-ly-12 block mb-4 font-semibold">Country and State/Province *</label>
- <div className="w-full">
- <div className="w-full flex justify-between">
- <div className="w-41.25 flex-initial">
- <Select placeholder="Country/Region"
- {...register("shippingCountry", {
- required: "Country/Region field is required",
- onChange: () => {
- // 清空 state/city
- setValue('shippingState','',{
- shouldDirty: true,
- shouldValidate: true,
- });
- setValue('shippingCity','',{
- shouldDirty: true,
- shouldValidate: true,
- });
- }
- })}
- options={countriesOptions}
- error={errors.shippingCountry && errors.shippingCountry.message as string}
- />
- </div>
- <div className="w-41.25 flex-initial relative">
- {statesLoading && <LoadingSpinner className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />}
- {statesOptions.length > 0 ?
- (
- <Select placeholder="State/Province"
- {...register("shippingState", {
- required: "State/Province field is required"
- })}
- options={statesOptions}
- error={errors.shippingState && errors.shippingState.message as string}
- />
- )
- : (
- <InputText
- type="text"
- placeholder="State/Province"
- {...register("shippingState", {
- required: "State/Province field is required"
- })}
- error={errors.shippingState && errors.shippingState.message as string}
- />
- )}
-
- </div>
-
- </div>
- <div className="w-full flex justify-between mt-4">
- <div className="w-41.25 flex-initial">
- <InputText
- type="text"
- placeholder="Zip/Postal Code"
- {...register("shippingPostcode", {
- required: "Postcode field is required",
- pattern: {
- value: IS_VALID_INPUT,
- message: "Invalid Postcode",
- },
- })}
- error={errors.shippingPostcode&& errors.shippingPostcode.message as string}
- />
- </div>
- <div className="w-41.25 flex-initial">
- <InputText
- type="text"
- placeholder="City"
- {...register("shippingCity", {
- required: "City field is required",
- pattern: {
- value: IS_VALID_INPUT,
- message: "Invalid City",
- },
- })}
- error={errors.shippingCity && errors.shippingCity.message as string}
- />
- </div>
- </div>
-
- </div>
- </div>
- <div className="w-full mt-4">
- <label className="text-ly-12 block mb-4 font-semibold">Phone number *</label>
- <Controller
- name="shippingPhoneNumber"
- control={control}
- rules={{
- required: "Phone Number is required",
- pattern: {
- value: IS_VALID_FULL_PHONE,
- message: "Invalid Phone Number",
- }
- }}
- render={({ field, fieldState }) => {
- return (
- <PhoneNumberInput
- placeholder="Please Input Your Phone Number"
- value={field.value}
- onChange={field.onChange}
- onBlur={field.onBlur}
- name={field.name}
- ref={field.ref}
- error={fieldState.error?.message}
- countryCode={selectedCountryCode}
- hasUserSelectedPhoneCode={hasUserSelectedPhoneCode}
- onHasUserSelectedPhoneCode={() => setHasUserSelectedPhoneCode(true)}
- />
- );
- }}
- />
-
- </div>
- </div>
- );
- };
|