diff --git a/core/app/[locale]/(default)/(auth)/account-status-provider.tsx b/core/app/[locale]/(default)/(auth)/account-status-provider.tsx deleted file mode 100644 index 9dac5c871..000000000 --- a/core/app/[locale]/(default)/(auth)/account-status-provider.tsx +++ /dev/null @@ -1,42 +0,0 @@ -'use client'; - -import { createContext, ReactNode, useContext, useEffect, useState } from 'react'; - -import { State as AccountState } from './account/[tab]/_actions/submit-customer-change-password-form'; -import { usePathname } from 'next/navigation'; - -const defaultState: AccountState = { status: 'idle', message: '' }; - -export const AccountStatusContext = createContext<{ - accountState: AccountState; - setAccountState: (state: AccountState | ((prevState: AccountState) => AccountState)) => void; -} | null>(null); - -export const AccountStatusProvider = ({ children }: { children: ReactNode; }) => { - - const [accountState, setAccountState] = useState(defaultState); - const pathname = usePathname(); - - useEffect(() => { - // Reset account state when changing the route except the Account Page - if (pathname !== "/account/") { - setAccountState(defaultState); - } - }, [pathname]) - - return ( - - {children} - - ); -}; - -export function useAccountStatusContext() { - const context = useContext(AccountStatusContext); - - if (!context) { - throw new Error('useAccountStatusContext must be used within a AccountStatusProvider'); - } - - return context; -} diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/add-address.ts b/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/add-address.ts deleted file mode 100644 index 9b48c0828..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/add-address.ts +++ /dev/null @@ -1,75 +0,0 @@ -'use server'; - -import { revalidatePath } from 'next/cache'; - -import { - addCustomerAddress, - AddCustomerAddressInput, -} from '~/client/mutations/add-customer-address'; - -const isAddCustomerAddressInput = (data: unknown): data is AddCustomerAddressInput => { - if (typeof data === 'object' && data !== null && 'address1' in data) { - return true; - } - - return false; -}; - -export const addAddress = async ({ - formData, - reCaptchaToken, -}: { - formData: FormData; - reCaptchaToken?: string; -}) => { - try { - const parsed: unknown = [...formData.entries()].reduce< - Record> - >((parsedData, [name, value]) => { - const key = name.split('-').at(-1) ?? ''; - const sections = name.split('-').slice(0, -1); - - if (sections.includes('customer')) { - parsedData[key] = value; - } - - if (sections.includes('address')) { - parsedData[key] = value; - } - - return parsedData; - }, {}); - - if (!isAddCustomerAddressInput(parsed)) { - return { - status: 'error', - error: 'Something went wrong with proccessing user input.', - }; - } - - const response = await addCustomerAddress({ - input: parsed, - reCaptchaToken, - }); - - revalidatePath('/account/addresses', 'page'); - - if (response.errors.length === 0) { - return { status: 'success', message: 'The address has been added.' }; - } - - return { - status: 'error', - message: response.errors.map((error) => error.message).join('\n'), - }; - } catch (error: unknown) { - if (error instanceof Error) { - return { - status: 'error', - message: error.message, - }; - } - - return { status: 'error', message: 'Unknown error.' }; - } -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/delete-address.ts b/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/delete-address.ts deleted file mode 100644 index 272cc26a1..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/delete-address.ts +++ /dev/null @@ -1,33 +0,0 @@ -'use server'; - -import { revalidatePath } from 'next/cache'; - -import { deleteCustomerAddress } from '~/client/mutations/delete-customer-address'; - -import { State } from './submit-customer-change-password-form'; - -export const deleteAddress = async (addressId: number): Promise => { - try { - const response = await deleteCustomerAddress(addressId); - - revalidatePath('/account/addresses', 'page'); - - if (response.errors.length === 0) { - return { status: 'success', message: 'Address deleted from your account.' }; - } - - return { - status: 'error', - message: response.errors.map((error) => error.message).join('\n'), - }; - } catch (error: unknown) { - if (error instanceof Error) { - return { - status: 'error', - message: error.message, - }; - } - - return { status: 'error', message: 'Unknown error.' }; - } -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/submit-customer-change-password-form.ts b/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/submit-customer-change-password-form.ts deleted file mode 100644 index 8965d86c3..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/submit-customer-change-password-form.ts +++ /dev/null @@ -1,61 +0,0 @@ -'use server'; - -import { z } from 'zod'; - -import { ChangePasswordFieldsSchema } from '~/client/mutations/submit-change-password'; -import { submitCustomerChangePassword } from '~/client/mutations/submit-customer-change-password'; - -export const CustomerChangePasswordSchema = ChangePasswordFieldsSchema.omit({ - customerId: true, - customerToken: true, -}); - -export interface State { - status: 'idle' | 'error' | 'success'; - message?: string; -} - -export const submitCustomerChangePasswordForm = async ( - _previousState: unknown, - formData: FormData, -) => { - try { - const parsedData = CustomerChangePasswordSchema.parse({ - newPassword: formData.get('new-password'), - currentPassword: formData.get('current-password'), - confirmPassword: formData.get('confirm-password'), - }); - - const response = await submitCustomerChangePassword({ - newPassword: parsedData.newPassword, - currentPassword: parsedData.currentPassword, - }); - - if (response.errors.length === 0) { - return { status: 'success', message: 'Password has been updated successfully.' }; - } - - return { - status: 'error', - message: response.errors.map((error) => error.message).join('\n'), - }; - } catch (error: unknown) { - if (error instanceof z.ZodError) { - return { - status: 'error', - message: error.issues - .map(({ path, message }) => `${path.toString()}: ${message}.`) - .join('\n'), - }; - } - - if (error instanceof Error) { - return { - status: 'error', - message: error.message, - }; - } - - return { status: 'error', message: 'Unknown error.' }; - } -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/update-address.ts b/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/update-address.ts deleted file mode 100644 index 87076524d..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_actions/update-address.ts +++ /dev/null @@ -1,90 +0,0 @@ -'use server'; - -import { revalidatePath } from 'next/cache'; - -import { - updateCustomerAddress, - UpdateCustomerAddressInput, -} from '~/client/mutations/update-customer-address'; - -const isUpdateCustomerAddressInput = ( - data: unknown, -): data is UpdateCustomerAddressInput['data'] => { - if (typeof data === 'object' && data !== null && 'address1' in data) { - return true; - } - - return false; -}; - -export const updateAddress = async ({ - addressId, - formData, - reCaptchaToken, -}: { - addressId: number; - formData: FormData; - reCaptchaToken?: string; -}) => { - try { - const parsed: unknown = [...formData.entries()].reduce< - Record> - >((parsedData, [name, value]) => { - const key = name.split('-').at(-1) ?? ''; - const sections = name.split('-').slice(0, -1); - - if (sections.includes('customer')) { - parsedData[key] = value; - } - - if (sections.includes('address')) { - parsedData[key] = value; - } - - return parsedData; - }, {}); - - if (!isUpdateCustomerAddressInput(parsed)) { - return { - status: 'error', - error: 'Something went wrong with proccessing user input.', - }; - } - - const response = await updateCustomerAddress({ - input: { - addressEntityId: addressId, - data: parsed, - }, - reCaptchaToken, - }); - - revalidatePath('/account/addresses', 'page'); - - if (response.errors.length === 0) { - return { status: 'success', message: 'The address has been updated.' }; - } - - return { - status: 'error', - message: response.errors - .map((error) => { - if (error.__typename === 'AddressDoesNotExistError') { - return 'Address does not exist.'; - } - - return error.message; - }) - .join('\n'), - }; - } catch (error: unknown) { - if (error instanceof Error) { - return { - status: 'error', - message: error.message, - }; - } - - return { status: 'error', message: 'Unknown error.' }; - } -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/add-address.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/add-address.tsx deleted file mode 100644 index a1d7b377f..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/add-address.tsx +++ /dev/null @@ -1,247 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { useTranslations } from 'next-intl'; -import { useEffect, useRef, useState } from 'react'; -import { useFormStatus } from 'react-dom'; -import ReCaptcha from 'react-google-recaptcha'; - -import { - createFieldName, - FieldNameToFieldId, - FieldWrapper, - Picklist, - PicklistOrText, - Text, -} from '~/app/[locale]/(default)/(auth)/login/register-customer/_components/register-customer-form/fields'; -import { Link } from '~/components/link'; -import { Button } from '~/components/ui/button'; -import { Field, Form, FormSubmit } from '~/components/ui/form'; -import { Message } from '~/components/ui/message'; - -import { addAddress } from '../../_actions/add-address'; -import { useAccountStatusContext } from '../../../../account-status-provider'; - -import { - createCountryChangeHandler, - createTextInputValidationHandler, -} from './address-field-handlers'; -import { NewAddressQueryResponseType } from './customer-new-address'; - -interface FormStatus { - status: 'success' | 'error'; - message: string; -} - -export type AddressFields = NonNullable< - NewAddressQueryResponseType['site']['settings'] ->['formFields']['shippingAddress']; - -export type Countries = NonNullable; -type CountryCode = Countries[number]['code']; -type CountryStates = Countries[number]['statesOrProvinces']; - -interface SumbitMessages { - messages: { - submit: string; - submitting: string; - }; -} - -const SubmitButton = ({ messages }: SumbitMessages) => { - const { pending } = useFormStatus(); - - return ( - - ); -}; - -interface AddAddressProps { - addressFields: AddressFields; - countries: Countries; - defaultCountry: { - id: number; - code: CountryCode; - states: CountryStates; - }; - reCaptchaSettings?: { - isEnabledOnStorefront: boolean; - siteKey: string; - }; -} - -export const AddAddress = ({ - addressFields, - countries, - defaultCountry, - reCaptchaSettings, -}: AddAddressProps) => { - const form = useRef(null); - const [formStatus, setFormStatus] = useState(null); - - const reCaptchaRef = useRef(null); - const router = useRouter(); - const t = useTranslations('Account.Addresses'); - const [reCaptchaToken, setReCaptchaToken] = useState(''); - const [isReCaptchaValid, setReCaptchaValid] = useState(true); - - const [textInputValid, setTextInputValid] = useState>({}); - const [countryStates, setCountryStates] = useState(defaultCountry.states); - - const { setAccountState } = useAccountStatusContext(); - - useEffect(() => { - setAccountState({ status: 'idle' }); - }, [setAccountState]); - - const handleTextInputValidation = createTextInputValidationHandler( - setTextInputValid, - textInputValid, - ); - const handleCountryChange = createCountryChangeHandler(setCountryStates, countries); - - const onReCaptchaChange = (token: string | null) => { - if (!token) { - setReCaptchaValid(false); - - return; - } - - setReCaptchaToken(token); - setReCaptchaValid(true); - }; - const onSubmit = async (formData: FormData) => { - if (reCaptchaSettings?.isEnabledOnStorefront && !reCaptchaToken) { - setReCaptchaValid(false); - } - - setReCaptchaValid(true); - - const submit = await addAddress({ formData, reCaptchaToken }); - - if (submit.status === 'success') { - setAccountState({ - status: 'success', - message: t('addNewAddressSuccessMessage'), - }); - - router.push('/account/addresses'); - - return; - } - - if (submit.status === 'error') { - setFormStatus({ status: 'error', message: submit.message || '' }); - } - - window.scrollTo({ - top: 0, - behavior: 'smooth', - }); - }; - - return ( - <> - {formStatus && ( - -

{formStatus.message}

-
- )} -
-
- {addressFields.map((field) => { - switch (field.__typename) { - case 'TextFormField': { - return ( - - - - ); - } - - case 'PicklistFormField': - return ( - - { - return { label: name, entityId: code }; - })} - /> - - ); - - case 'PicklistOrTextFormField': - return ( - - { - return { entityId: name, label: name }; - })} - /> - - ); - - default: - return null; - } - })} - - {reCaptchaSettings?.isEnabledOnStorefront && ( - - - {!isReCaptchaValid && ( - - {t('recaptchaText')} - - )} - - )} -
- -
- - - - -
-
- - ); -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/address-book.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/address-book.tsx deleted file mode 100644 index 16f59cc53..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/address-book.tsx +++ /dev/null @@ -1,139 +0,0 @@ -'use client'; - -import { useTranslations } from 'next-intl'; -import { PropsWithChildren, useState } from 'react'; - -import { getCustomerAddresses } from '~/client/queries/get-customer-addresses'; -import { Link } from '~/components/link'; -import { Button } from '~/components/ui/button'; -import { Message } from '~/components/ui/message'; - -import { deleteAddress } from '../../_actions/delete-address'; -import { useAccountStatusContext } from '../../../../account-status-provider'; -import { Modal } from '../modal'; - -export type Addresses = NonNullable>>['addresses']; - -interface AddressChangeProps { - addressId: number; - isAddressRemovable: boolean; - onDelete: (state: Addresses | ((prevState: Addresses) => Addresses)) => void; -} - -const AddressChangeButtons = ({ addressId, isAddressRemovable, onDelete }: AddressChangeProps) => { - const { setAccountState } = useAccountStatusContext(); - const t = useTranslations('Account.Addresses'); - - const handleDeleteAddress = async () => { - const { status } = await deleteAddress(addressId); - - if (status === 'success') { - onDelete((prevAddressBook) => - prevAddressBook.filter(({ entityId }) => entityId !== addressId), - ); - - setAccountState({ - status: 'success', - message: t('deleteAddress'), - }); - } - }; - - return ( -
- - - - -
- ); -}; - -interface AddressBookProps { - customerAddresses: Addresses; - addressesCount: number; -} - -export const AddressBook = ({ - children, - addressesCount, - customerAddresses, -}: PropsWithChildren) => { - const t = useTranslations('Account.Addresses'); - const [addressBook, setAddressBook] = useState(customerAddresses); - const { accountState } = useAccountStatusContext(); - - return ( - <> - {(accountState.status === 'error' || accountState.status === 'success') && ( - -

{accountState.message}

-
- )} -
    - {addressBook.map( - ({ - entityId, - firstName, - lastName, - address1, - address2, - city, - stateOrProvince, - postalCode, - countryCode, - }) => ( -
  • -
    -

    - {firstName} {lastName} -

    -

    {address1}

    - {Boolean(address2) &&

    {address2}

    } -

    - {city}, {stateOrProvince} {postalCode} -

    -

    {countryCode}

    -
    - 1} - onDelete={setAddressBook} - /> -
  • - ), - )} -
  • - - {children} -
  • -
- - ); -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/address-field-handlers.ts b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/address-field-handlers.ts deleted file mode 100644 index f67ac517a..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/address-field-handlers.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { ChangeEvent } from 'react'; - -import { - createFieldName, - FieldNameToFieldId, -} from '~/app/[locale]/(default)/(auth)/login/register-customer/_components/register-customer-form/fields'; - -import { Countries } from './add-address'; - -type FieldState = Record; - -type StateOrProvince = Countries[number]['statesOrProvinces']; -type FieldStateSetFn = (state: Type | ((prevState: Type) => Type)) => void; - -const createTextInputValidationHandler = - (textInputStateSetter: FieldStateSetFn, textInputState: FieldState) => - (e: ChangeEvent) => { - const fieldId = Number(e.target.id.split('-')[1]); - - const validityState = e.target.validity; - const validationStatus = validityState.valueMissing || validityState.typeMismatch; - - textInputStateSetter({ ...textInputState, [fieldId]: !validationStatus }); - }; - -const createPasswordValidationHandler = - (passwordStateSetter: FieldStateSetFn) => (e: ChangeEvent) => { - const fieldId = e.target.id.split('-')[1] ?? ''; - - switch (FieldNameToFieldId[Number(fieldId)]) { - case 'password': { - passwordStateSetter((prevState) => ({ - ...prevState, - [fieldId]: !e.target.validity.valueMissing, - })); - - return; - } - - case 'confirmPassword': { - const confirmPassword = e.target.value; - - const passwordFieldName = createFieldName('customer', +fieldId); - const password = new FormData(e.target.form ?? undefined).get(passwordFieldName); - - passwordStateSetter((prevState) => ({ - ...prevState, - [fieldId]: password === confirmPassword && !e.target.validity.valueMissing, - })); - - return; - } - - default: { - passwordStateSetter((prevState) => ({ - ...prevState, - [fieldId]: !e.target.validity.valueMissing, - })); - } - } - }; - -const createPicklistOrTextValidationHandler = - (picklistWithTextStateSetter: FieldStateSetFn, picklistWithTextState: FieldState) => - (e: ChangeEvent) => { - const fieldId = Number(e.target.id.split('-')[1]); - - const validationStatus = e.target.validity.valueMissing; - - picklistWithTextStateSetter({ ...picklistWithTextState, [fieldId]: !validationStatus }); - }; - -const createCountryChangeHandler = - (provinceSetter: FieldStateSetFn, countries: Countries) => (value: string) => { - const states = countries.find(({ code }) => code === value)?.statesOrProvinces; - - provinceSetter(states ?? []); - }; - -export { - createTextInputValidationHandler, - createPicklistOrTextValidationHandler, - createPasswordValidationHandler, - createCountryChangeHandler, -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/customer-edit-address.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/customer-edit-address.tsx deleted file mode 100644 index 900ac2a0f..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/customer-edit-address.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { NextIntlClientProvider } from 'next-intl'; -import { getLocale, getMessages } from 'next-intl/server'; - -import { getSessionCustomerId } from '~/auth'; -import { client } from '~/client'; -import { FORM_FIELDS_FRAGMENT } from '~/client/fragments/form-fields'; -import { graphql, ResultOf } from '~/client/graphql'; -import { getCustomerAddresses } from '~/client/queries/get-customer-addresses'; - -import { EditAddress as EditAddressForm } from './edit-address'; - -export const CustomerEditAdressQuery = graphql( - ` - query customerNewAddress( - $countryCode: String - $shippingFilters: FormFieldFiltersInput - $shippingSorting: FormFieldSortInput - ) { - site { - settings { - contact { - country - } - reCaptcha { - isEnabledOnStorefront - siteKey - } - formFields { - shippingAddress(filters: $shippingFilters, sortBy: $shippingSorting) { - ...FormFields - } - } - } - } - geography { - countries(filters: { code: $countryCode }) { - __typename - name - entityId - code - statesOrProvinces { - __typename - entityId - name - abbreviation - } - } - } - } - `, - [FORM_FIELDS_FRAGMENT], -); - -export type EditAddressQueryResponseType = ResultOf; - -type CustomerAddresses = NonNullable>>; - -export type Address = CustomerAddresses['addresses'][number]; - -export async function CustomerEditAddress({ - address, - isAddressRemovable, -}: { - address: Address; - isAddressRemovable: boolean; -}) { - const customerId = await getSessionCustomerId(); - const locale = await getLocale(); - const messages = await getMessages(); - - const { data } = await client.fetch({ - document: CustomerEditAdressQuery, - customerId, - fetchOptions: { cache: 'no-store' }, - variables: { - countryCode: null, - shippingSorting: 'SORT_ORDER', - }, - }); - const reCaptchaSettings = data.site.settings?.reCaptcha; - const countries = data.geography.countries; - const addressFields = [...(data.site.settings?.formFields.shippingAddress ?? [])]; - - return ( - - - - ); -} diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/customer-new-address.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/customer-new-address.tsx deleted file mode 100644 index ca9ef35d1..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/customer-new-address.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { NextIntlClientProvider } from 'next-intl'; -import { getLocale, getMessages } from 'next-intl/server'; - -import { getSessionCustomerId } from '~/auth'; -import { client } from '~/client'; -import { FORM_FIELDS_FRAGMENT } from '~/client/fragments/form-fields'; -import { graphql, ResultOf } from '~/client/graphql'; - -import { AddAddress as AddAddressForm } from './add-address'; - -const CustomerNewAdressQuery = graphql( - ` - query customerNewAddress( - $countryCode: String - $shippingFilters: FormFieldFiltersInput - $shippingSorting: FormFieldSortInput - ) { - site { - settings { - contact { - country - } - reCaptcha { - isEnabledOnStorefront - siteKey - } - formFields { - shippingAddress(filters: $shippingFilters, sortBy: $shippingSorting) { - ...FormFields - } - } - } - } - geography { - countries(filters: { code: $countryCode }) { - __typename - name - entityId - code - statesOrProvinces { - __typename - entityId - name - abbreviation - } - } - } - } - `, - [FORM_FIELDS_FRAGMENT], -); - -export type NewAddressQueryResponseType = ResultOf; - -const FALLBACK_COUNTRY = { - entityId: 226, - name: 'United States', - code: 'US', - states: [], -}; - -export async function CustomerNewAddress() { - const customerId = await getSessionCustomerId(); - const locale = await getLocale(); - const messages = await getMessages(); - const { data } = await client.fetch({ - document: CustomerNewAdressQuery, - customerId, - fetchOptions: { cache: 'no-store' }, - variables: { - shippingSorting: 'SORT_ORDER', - }, - }); - - const addressFields = [...(data.site.settings?.formFields.shippingAddress ?? [])]; - const reCaptchaSettings = data.site.settings?.reCaptcha; - const countries = data.geography.countries; - const defaultCountry = data.site.settings?.contact?.country || FALLBACK_COUNTRY.name; - - const { - code = FALLBACK_COUNTRY.code, - entityId = FALLBACK_COUNTRY.entityId, - statesOrProvinces: defaultCountryStates = FALLBACK_COUNTRY.states, - } = countries?.find(({ name: country }) => country === defaultCountry) || {}; - - return ( - - - - ); -} diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/edit-address.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/edit-address.tsx deleted file mode 100644 index 9056bf91a..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/edit-address.tsx +++ /dev/null @@ -1,296 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { useTranslations } from 'next-intl'; -import { useEffect, useRef, useState } from 'react'; -import { useFormStatus } from 'react-dom'; -import ReCaptcha from 'react-google-recaptcha'; - -import { - createFieldName, - FieldNameToFieldId, - FieldWrapper, - Picklist, - PicklistOrText, - Text, -} from '~/app/[locale]/(default)/(auth)/login/register-customer/_components/register-customer-form/fields'; -import { Link } from '~/components/link'; -import { Button } from '~/components/ui/button'; -import { Field, Form, FormSubmit } from '~/components/ui/form'; -import { Message } from '~/components/ui/message'; - -import { deleteAddress } from '../../_actions/delete-address'; -import { updateAddress } from '../../_actions/update-address'; -import { useAccountStatusContext } from '../../../../account-status-provider'; -import { Modal } from '../modal'; - -import { AddressFields, Countries } from './add-address'; -import { - createCountryChangeHandler, - createTextInputValidationHandler, -} from './address-field-handlers'; -import { Address } from './customer-edit-address'; - -interface FormStatus { - status: 'success' | 'error'; - message: string; -} - -type CountryStates = Countries[number]['statesOrProvinces']; -type FieldUnionType = Exclude< - keyof typeof FieldNameToFieldId, - 'email' | 'password' | 'confirmPassword' | 'exclusiveOffers' | 'currentPassword' ->; - -const isExistedField = (name: unknown): name is FieldUnionType => { - if (typeof name === 'string' && name in FieldNameToFieldId) { - return true; - } - - return false; -}; - -interface SumbitMessages { - messages: { - submit: string; - submitting: string; - }; -} - -const SubmitButton = ({ messages }: SumbitMessages) => { - const { pending } = useFormStatus(); - - return ( - - ); -}; - -interface EditAddressProps { - address: Address; - addressFields: AddressFields; - canBeDeleted: boolean; - countries: Countries; - reCaptchaSettings?: { - isEnabledOnStorefront: boolean; - siteKey: string; - }; -} - -export const EditAddress = ({ - address, - addressFields, - canBeDeleted, - countries, - reCaptchaSettings, -}: EditAddressProps) => { - const form = useRef(null); - const [formStatus, setFormStatus] = useState(null); - - const t = useTranslations('Account.EditAddress'); - - const reCaptchaRef = useRef(null); - const router = useRouter(); - const [reCaptchaToken, setReCaptchaToken] = useState(''); - const [isReCaptchaValid, setReCaptchaValid] = useState(true); - const { setAccountState } = useAccountStatusContext(); - - useEffect(() => { - setAccountState({ status: 'idle' }); - }, [setAccountState]); - - const [textInputValid, setTextInputValid] = useState>({}); - - const defaultStates = countries - .filter((country) => country.code === address.countryCode) - .flatMap((country) => country.statesOrProvinces); - const [countryStates, setCountryStates] = useState(defaultStates); - - const handleTextInputValidation = createTextInputValidationHandler( - setTextInputValid, - textInputValid, - ); - const handleCountryChange = createCountryChangeHandler(setCountryStates, countries); - - const onReCaptchaChange = (token: string | null) => { - if (!token) { - setReCaptchaValid(false); - - return; - } - - setReCaptchaToken(token); - setReCaptchaValid(true); - }; - - const onSubmit = async (formData: FormData) => { - if (reCaptchaSettings?.isEnabledOnStorefront && !reCaptchaToken) { - setReCaptchaValid(false); - - return; - } - - setReCaptchaValid(true); - - const submit = await updateAddress({ addressId: address.entityId, formData }); - - if (submit.status === 'success') { - setAccountState({ - status: 'success', - message: t('successMessage'), - }); - - router.push('/account/addresses'); - - return; - } - - if (submit.status === 'error') { - setFormStatus({ status: 'error', message: submit.message || '' }); - } - - window.scrollTo({ - top: 0, - behavior: 'smooth', - }); - }; - const onDeleteAddress = async () => { - const { status, message } = await deleteAddress(address.entityId); - - if (status === 'success') { - setAccountState({ status, message: t('deleteAddress') }); - } - - if (status === 'error') { - setAccountState({ status, message }); - } - - router.push('/account/addresses'); - }; - - return ( - <> - {formStatus && ( - -

{formStatus.message}

-
- )} -
-
- {addressFields.map((field) => { - const key = FieldNameToFieldId[field.entityId]; - - if (!isExistedField(key)) { - return null; - } - - const defaultValue = address[key] || undefined; - - switch (field.__typename) { - case 'TextFormField': { - return ( - - - - ); - } - - case 'PicklistFormField': - return ( - - { - return { label: name, entityId: code }; - })} - /> - - ); - - case 'PicklistOrTextFormField': { - return ( - - { - return { entityId: name, label: name }; - })} - /> - - ); - } - - default: - return null; - } - })} - - {reCaptchaSettings?.isEnabledOnStorefront && ( - - - {!isReCaptchaValid && ( - - {t('recaptchaText')} - - )} - - )} -
- -
- - - - - - - -
-
- - ); -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/index.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/index.tsx deleted file mode 100644 index 46cff0eb4..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/addresses-content/index.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { getLocale, getTranslations } from 'next-intl/server'; - -import { getCustomerAddresses } from '~/client/queries/get-customer-addresses'; -import { Pagination } from '~/components/ui/pagination'; - -import { TabHeading } from '../tab-heading'; - -import { AddressBook } from './address-book'; -import { CustomerEditAddress } from './customer-edit-address'; -import { CustomerNewAddress } from './customer-new-address'; - -type CustomerAddresses = NonNullable>>; - -interface Props { - addresses: CustomerAddresses['addresses']; - activeAddressId?: string; - addressesCount: number; - customerAction?: 'add-new-address' | 'edit-address'; - pageInfo: CustomerAddresses['pageInfo']; -} - -export const AddressesContent = async ({ - addresses, - addressesCount, - activeAddressId, - customerAction, - pageInfo, -}: Props) => { - const locale = await getLocale(); - const t = await getTranslations({ locale, namespace: 'Account.Addresses' }); - const { hasNextPage, hasPreviousPage, startCursor, endCursor } = pageInfo; - - if (customerAction === 'add-new-address') { - return ( -
-

{t('newAddress')}

- -
- ); - } - - if (customerAction === 'edit-address' && activeAddressId) { - const activeAddress = addresses - .filter(({ entityId }) => entityId.toString() === activeAddressId) - .shift(); - - return ( -
-

{t('editAddress')}

- {activeAddress && Object.keys(activeAddress).length > 0 ? ( - 1} /> - ) : null} -
- ); - } - - return ( - <> - - - - - - ); -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/change-password-form.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/change-password-form.tsx deleted file mode 100644 index 58974bcbc..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/change-password-form.tsx +++ /dev/null @@ -1,272 +0,0 @@ -'use client'; - -import { useTranslations } from 'next-intl'; -import { ChangeEvent, useEffect, useRef, useState } from 'react'; -import { useFormState, useFormStatus } from 'react-dom'; -import { z } from 'zod'; - -import { logout } from '~/components/header/_actions/logout'; -import { Link } from '~/components/link'; -import { Button } from '~/components/ui/button'; -import { - Field, - FieldControl, - FieldLabel, - FieldMessage, - Form, - FormSubmit, - Input, -} from '~/components/ui/form'; -import { Message } from '~/components/ui/message'; -import { useRouter } from '~/navigation'; - -import { - CustomerChangePasswordSchema, - submitCustomerChangePasswordForm, -} from '../_actions/submit-customer-change-password-form'; - -type Passwords = z.infer; - -const validateAgainstConfirmPassword = ({ - newPassword, - confirmPassword, -}: { - newPassword: Passwords['newPassword']; - confirmPassword: Passwords['confirmPassword']; -}): boolean => newPassword === confirmPassword; - -const validateAgainstCurrentPassword = ({ - newPassword, - currentPassword, -}: { - newPassword: Passwords['newPassword']; - currentPassword: Passwords['currentPassword']; -}): boolean => newPassword !== currentPassword; - -const validatePasswords = ( - validationField: 'new-password' | 'confirm-password', - formData?: FormData, -) => { - if (!formData) { - return false; - } - - if (validationField === 'new-password') { - return CustomerChangePasswordSchema.omit({ confirmPassword: true }) - .refine(validateAgainstCurrentPassword) - .safeParse({ - currentPassword: formData.get('current-password'), - newPassword: formData.get('new-password'), - }).success; - } - - return CustomerChangePasswordSchema.refine(validateAgainstConfirmPassword).safeParse({ - currentPassword: formData.get('current-password'), - newPassword: formData.get('new-password'), - confirmPassword: formData.get('confirm-password'), - }).success; -}; - -const SubmitButton = () => { - const { pending } = useFormStatus(); - const t = useTranslations('Account.SubmitChangePassword'); - - return ( - - ); -}; - -export const ChangePasswordForm = () => { - const router = useRouter(); - const form = useRef(null); - const t = useTranslations('Account.ChangePassword'); - const [state, formAction] = useFormState(submitCustomerChangePasswordForm, { - status: 'idle', - message: '', - }); - - const [isCurrentPasswordValid, setIsCurrentPasswordValid] = useState(true); - const [isNewPasswordValid, setIsNewPasswordValid] = useState(true); - const [isConfirmPasswordValid, setIsConfirmPasswordValid] = useState(true); - - useEffect(() => { - if (state.status === 'success') { - setTimeout(() => { - void logout(); - router.push('/login'); - }, 2000); - } - }, [state, router]); - - let messageText = ''; - - if (state.status === 'error') { - messageText = state.message; - } - - if (state.status === 'success') { - messageText = t('successMessage'); - } - - const handleCurrentPasswordChange = (e: ChangeEvent) => - setIsCurrentPasswordValid(!e.target.validity.valueMissing); - const handleNewPasswordChange = (e: ChangeEvent) => { - let formData; - - if (e.target.form) { - formData = new FormData(e.target.form); - } - - const confirmPassword = formData?.get('confirm-password'); - const isValid = confirmPassword - ? validatePasswords('new-password', formData) && - validatePasswords('confirm-password', formData) - : validatePasswords('new-password', formData); - - setIsNewPasswordValid(isValid); - }; - const handleConfirmPasswordValidation = (e: ChangeEvent) => { - let formData; - - if (e.target.form) { - formData = new FormData(e.target.form); - } - - const isValid = validatePasswords('confirm-password', formData); - - setIsConfirmPasswordValid(isValid); - }; - - return ( - <> - {(state.status === 'error' || state.status === 'success') && ( - -

{messageText}

-
- )} - -
- - - {t('currentPasswordLabel')} - - - - - - {t('notEmptyMessage')} - - - - - {t('newPasswordLabel')} - - - - - - {t('notEmptyMessage')} - - { - const currentPasswordValue = formData.get('current-password'); - const confirmPassword = formData.get('confirm-password'); - let isMatched; - - if (confirmPassword) { - isMatched = - newPasswordValue !== currentPasswordValue && newPasswordValue === confirmPassword; - - setIsNewPasswordValid(isMatched); - - return !isMatched; - } - - isMatched = currentPasswordValue === newPasswordValue; - - setIsNewPasswordValid(!isMatched); - - return isMatched; - }} - > - {t('newPasswordValidationMessage')} - - - - - {t('confirmPasswordLabel')} - - - - - - {t('notEmptyMessage')} - - { - const newPassword = formData.get('new-password'); - const isMatched = confirmPassword === newPassword; - - setIsConfirmPasswordValid(isMatched); - - return !isMatched; - }} - > - {t('confirmPasswordValidationMessage')} - - -
- - - - -
-
- - ); -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/modal.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/modal.tsx deleted file mode 100644 index 17b91235d..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/modal.tsx +++ /dev/null @@ -1,70 +0,0 @@ -'use client'; - -import * as DialogPrimitive from '@radix-ui/react-alert-dialog'; -import { X } from 'lucide-react'; -import { MouseEventHandler, PropsWithChildren, useId } from 'react'; - -import { Button } from '~/components/ui/button'; - -interface Props extends PropsWithChildren { - actionHandler?: MouseEventHandler; - title: string; - descriptionText?: string; - confirmationText?: string; - abortText?: string; -} - -export const Modal = ({ - abortText = 'Cancel', - actionHandler, - confirmationText = 'OK', - descriptionText, - title, - children, -}: Props) => { - const id = useId(); - - return ( - - - {children} - - - - - -
- - {title} - - - - -
- {Boolean(descriptionText) && ( - {descriptionText} - )} -
- - - - - - -
-
-
-
- ); -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/settings-content.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/settings-content.tsx deleted file mode 100644 index 28a1dff8f..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/settings-content.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { NextIntlClientProvider } from 'next-intl'; -import { getLocale, getMessages } from 'next-intl/server'; - -import { getCustomerSettingsQuery } from '../page-data'; - -import { ChangePasswordForm } from './change-password-form'; -import { TabHeading } from './tab-heading'; -import { UpdateSettingsForm } from './update-settings-form'; - -interface Props { - action?: string | string[]; - customerSettings: CustomerSettings; -} - -type CustomerSettings = NonNullable>>; - -export const SettingsContent = async ({ action, customerSettings }: Props) => { - const locale = await getLocale(); - const messages = await getMessages({ locale }); - - if (action === 'change_password') { - return ( -
- - - - -
- ); - } - - return ( -
- - -
- ); -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/tab-heading.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/tab-heading.tsx deleted file mode 100644 index 6b77ecf1e..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/tab-heading.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { getLocale, getTranslations } from 'next-intl/server'; - -import { TabType } from '../layout'; - -export const TabHeading = async ({ heading }: { heading: TabType | 'change_password' }) => { - const locale = await getLocale(); - const t = await getTranslations({ locale, namespace: 'Account.Home' }); - const tab = heading === 'recently-viewed' ? 'recentlyViewed' : heading; - const title = tab === 'change_password' ? 'changePassword' : tab; - - return

{t(title)}

; -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/update-settings-form/_actions/update-customer.ts b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/update-settings-form/_actions/update-customer.ts deleted file mode 100644 index 151e8deaf..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/update-settings-form/_actions/update-customer.ts +++ /dev/null @@ -1,36 +0,0 @@ -'use server'; - -import { updateCustomer as updateCustomerClient } from '~/client/mutations/update-customer'; - -interface UpdateCustomerForm { - formData: FormData; - reCaptchaToken?: string; -} - -export const updateCustomer = async ({ formData, reCaptchaToken }: UpdateCustomerForm) => { - formData.delete('g-recaptcha-response'); - - const formFields = Object.fromEntries(formData.entries()); - - const response = await updateCustomerClient({ formFields, reCaptchaToken }); - - if (response.errors.length === 0) { - const { customer } = response; - - if (!customer) { - return { - status: 'error', - error: 'Customer does not exist', - }; - } - - const { firstName, lastName } = customer; - - return { status: 'success', data: { firstName, lastName } }; - } - - return { - status: 'error', - error: response.errors.map((error) => error.message).join('\n'), - }; -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/update-settings-form/fields/text.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/update-settings-form/fields/text.tsx deleted file mode 100644 index 5ab8a112f..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/update-settings-form/fields/text.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useTranslations } from 'next-intl'; -import { ChangeEvent } from 'react'; - -import { Field, FieldControl, FieldLabel, FieldMessage, Input } from '~/components/ui/form'; - -import { FieldNameToFieldId } from '..'; - -interface TextProps { - defaultValue?: string; - entityId: number; - isRequired?: boolean; - isValid?: boolean; - label: string; - onChange: (e: ChangeEvent) => void; - type?: 'text' | 'email'; -} - -export const Text = ({ - defaultValue, - entityId, - isRequired = false, - isValid, - label, - onChange, - type = 'text', -}: TextProps) => { - const t = useTranslations('Account.Settings.validationMessages'); - const fieldName = FieldNameToFieldId[entityId]; - const name = fieldName ?? `field-${entityId}`; - - return ( - - - {label} - - - - - {isRequired && ( - - {t(fieldName ?? 'empty')} - - )} - - ); -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/update-settings-form/index.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/_components/update-settings-form/index.tsx deleted file mode 100644 index 31ba61ca7..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/update-settings-form/index.tsx +++ /dev/null @@ -1,223 +0,0 @@ -'use client'; - -import { useTranslations } from 'next-intl'; -import { ChangeEvent, useRef, useState } from 'react'; -import { useFormStatus } from 'react-dom'; -import ReCaptcha from 'react-google-recaptcha'; - -import { Link } from '~/components/link'; -import { Button } from '~/components/ui/button'; -import { Field, Form, FormSubmit } from '~/components/ui/form'; -import { Message } from '~/components/ui/message'; - -import { getCustomerSettingsQuery } from '../../page-data'; - -import { updateCustomer } from './_actions/update-customer'; -import { Text } from './fields/text'; - -type CustomerInfo = NonNullable< - Awaited> ->['customerInfo']; -type CustomerFields = NonNullable< - Awaited> ->['customerFields']; -type AddressFields = NonNullable< - Awaited> ->['addressFields']; - -interface FormProps { - addressFields: AddressFields; - customerInfo: CustomerInfo; - customerFields: CustomerFields; - reCaptchaSettings?: { - isEnabledOnStorefront: boolean; - siteKey: string; - }; -} - -interface FormStatus { - status: 'success' | 'error'; - message: string; -} - -interface SumbitMessages { - messages: { - submit: string; - submitting: string; - }; -} - -export enum FieldNameToFieldId { - email = 1, - firstName = 4, - lastName, - company, - phone, -} - -type FieldUnionType = keyof typeof FieldNameToFieldId; - -const isExistedField = (name: unknown): name is FieldUnionType => { - if (typeof name === 'string' && name in FieldNameToFieldId) { - return true; - } - - return false; -}; - -const SubmitButton = ({ messages }: SumbitMessages) => { - const { pending } = useFormStatus(); - - return ( - - ); -}; - -export const UpdateSettingsForm = ({ - addressFields, - customerFields, - customerInfo, - reCaptchaSettings, -}: FormProps) => { - const form = useRef(null); - const [formStatus, setFormStatus] = useState(null); - - const [textInputValid, setTextInputValid] = useState>({}); - - const reCaptchaRef = useRef(null); - const [reCaptchaToken, setReCaptchaToken] = useState(''); - const [isReCaptchaValid, setReCaptchaValid] = useState(true); - - const t = useTranslations('Account.Settings'); - - const handleTextInputValidation = (e: ChangeEvent) => { - const fieldId = Number(e.target.id.split('-')[1]); - - const validityState = e.target.validity; - const validationStatus = validityState.valueMissing || validityState.typeMismatch; - - setTextInputValid({ ...textInputValid, [fieldId]: !validationStatus }); - }; - - const onReCaptchaChange = (token: string | null) => { - if (!token) { - setReCaptchaValid(false); - - return; - } - - setReCaptchaToken(token); - setReCaptchaValid(true); - }; - - const onSubmit = async (formData: FormData) => { - if (reCaptchaSettings?.isEnabledOnStorefront && !reCaptchaToken) { - setReCaptchaValid(false); - - return; - } - - setReCaptchaValid(true); - - const submit = await updateCustomer({ formData, reCaptchaToken }); - - if (submit.status === 'success') { - setFormStatus({ - status: 'success', - message: t('successMessage', { - firstName: submit.data?.firstName, - lastName: submit.data?.lastName, - }), - }); - } - - if (submit.status === 'error') { - setFormStatus({ status: 'error', message: submit.error ?? '' }); - } - }; - - return ( - <> - {formStatus && ( - -

{formStatus.message}

-
- )} -
-
- {addressFields.map((field) => { - const fieldName = FieldNameToFieldId[field.entityId] ?? ''; - - if (!isExistedField(fieldName)) { - return null; - } - - return ( - - ); - })} -
- field.entityId === FieldNameToFieldId.email) - ?.label ?? '' - } - onChange={handleTextInputValidation} - type="email" - /> -
- {reCaptchaSettings?.isEnabledOnStorefront && ( - - - {!isReCaptchaValid && ( - - {t('recaptchaText')} - - )} - - )} -
- - - - - - {t('changePassword')} - -
-
-
- - ); -}; diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/layout.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/layout.tsx deleted file mode 100644 index 5abcde080..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/layout.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { NextIntlClientProvider } from 'next-intl'; -import { getMessages, getTranslations, unstable_setRequestLocale } from 'next-intl/server'; -import { PropsWithChildren } from 'react'; - -import { Link } from '~/components/link'; -import { LocaleType } from '~/i18n'; -import { cn } from '~/lib/utils'; - -const tabList = [ - 'orders', - 'messages', - 'addresses', - 'wishlists', - 'recently-viewed', - 'settings', -] as const; - -export type TabType = (typeof tabList)[number]; - -interface Props extends PropsWithChildren { - params: { locale: LocaleType; tab?: TabType }; -} - -export default async function AccountTabLayout({ - children, - params: { locale, tab: activeTab }, -}: Props) { - unstable_setRequestLocale(locale); - - const t = await getTranslations({ locale, namespace: 'Account.Home' }); - - const messages = await getMessages(); - - const tabsTitles = { - orders: t('orders'), - messages: t('messages'), - addresses: t('addresses'), - wishlists: t('wishlists'), - 'recently-viewed': t('recentlyViewed'), - settings: t('settings'), - }; - - return ( - -

{t('heading')}

- - {children} -
- ); -} diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/page-data.ts b/core/app/[locale]/(default)/(auth)/account/[tab]/page-data.ts deleted file mode 100644 index aa19539f5..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/page-data.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { cache } from 'react'; - -import { getSessionCustomerId } from '~/auth'; -import { client } from '~/client'; -import { FORM_FIELDS_FRAGMENT } from '~/client/fragments/form-fields'; -import { graphql, VariablesOf } from '~/client/graphql'; - -const CustomerSettingsQuery = graphql( - ` - query CustomerSettingsQuery( - $customerFilters: FormFieldFiltersInput - $customerSortBy: FormFieldSortInput - $addressFilters: FormFieldFiltersInput - $addressSortBy: FormFieldSortInput - ) { - customer { - entityId - company - email - firstName - lastName - phone - formFields { - entityId - name - __typename - ... on CheckboxesFormFieldValue { - valueEntityIds - values - } - ... on DateFormFieldValue { - date { - utc - } - } - ... on MultipleChoiceFormFieldValue { - valueEntityId - value - } - ... on NumberFormFieldValue { - number - } - ... on PasswordFormFieldValue { - password - } - ... on TextFormFieldValue { - text - } - } - } - site { - settings { - formFields { - customer(filters: $customerFilters, sortBy: $customerSortBy) { - ...FormFields - } - shippingAddress(filters: $addressFilters, sortBy: $addressSortBy) { - ...FormFields - } - } - reCaptcha { - isEnabledOnStorefront - siteKey - } - } - } - } - `, - [FORM_FIELDS_FRAGMENT], -); - -type Variables = VariablesOf; - -interface Props { - address?: { - filters?: Variables['addressFilters']; - sortBy?: Variables['addressSortBy']; - }; - - customer?: { - filters?: Variables['customerFilters']; - sortBy?: Variables['customerSortBy']; - }; -} - -export const getCustomerSettingsQuery = cache(async ({ address, customer }: Props = {}) => { - const customerId = await getSessionCustomerId(); - - const response = await client.fetch({ - document: CustomerSettingsQuery, - variables: { - addressFilters: address?.filters, - addressSortBy: address?.sortBy, - customerFilters: customer?.filters, - customerSortBy: customer?.sortBy, - }, - fetchOptions: { cache: 'no-store' }, - customerId, - }); - - const addressFields = response.data.site.settings?.formFields.shippingAddress; - const customerFields = response.data.site.settings?.formFields.customer; - const customerInfo = response.data.customer; - - const reCaptchaSettings = response.data.site.settings?.reCaptcha; - - if (!addressFields || !customerFields || !customerInfo) { - return null; - } - - return { - addressFields, - customerFields, - customerInfo, - reCaptchaSettings, - }; -}); diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/page.tsx b/core/app/[locale]/(default)/(auth)/account/[tab]/page.tsx deleted file mode 100644 index 635f64e49..000000000 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/page.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import type { Metadata } from 'next'; -import { notFound } from 'next/navigation'; -import { getLocale, getTranslations } from 'next-intl/server'; - -import { getCustomerAddresses } from '~/client/queries/get-customer-addresses'; - -import { AddressesContent } from './_components/addresses-content'; -import { SettingsContent } from './_components/settings-content'; -import { TabHeading } from './_components/tab-heading'; -import { TabType } from './layout'; -import { getCustomerSettingsQuery } from './page-data'; - -interface Props { - params: { - tab: TabType; - }; - searchParams: { - [key: string]: string | string[] | undefined; - action?: 'add-new-address' | 'edit-address'; - before?: string; - after?: string; - }; -} - -export async function generateMetadata({ params: { tab } }: Props): Promise { - const locale = await getLocale(); - const t = await getTranslations({ locale, namespace: 'Account.Home' }); - const title = t(tab === 'recently-viewed' ? 'recentlyViewed' : tab); - - return { - title, - }; -} - -export default async function AccountTabPage({ params: { tab }, searchParams }: Props) { - switch (tab) { - case 'orders': - return ; - - case 'messages': - return ; - - case 'addresses': { - const { before, after, action } = searchParams; - const customerAddressesDetails = await getCustomerAddresses({ - ...(after && { after }), - ...(before && { before }), - limit: action === 'edit-address' ? undefined : 2, - }); - - if (!customerAddressesDetails) { - notFound(); - } - - const { addresses, pageInfo, addressesCount } = customerAddressesDetails; - - return ( - - ); - } - - case 'wishlists': - return ; - - case 'recently-viewed': - return ; - - case 'settings': { - const customerSettings = await getCustomerSettingsQuery({ - address: { filters: { entityIds: [4, 5, 6, 7] } }, - }); - - if (!customerSettings) { - notFound(); - } - - return ; - } - - default: - return notFound(); - } -} - -export const runtime = 'edge'; diff --git a/core/app/[locale]/(default)/(auth)/account/page-data.ts b/core/app/[locale]/(default)/(auth)/account/page-data.ts deleted file mode 100644 index 3f21fcbd4..000000000 --- a/core/app/[locale]/(default)/(auth)/account/page-data.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { cache } from 'react'; - -import { client } from '~/client'; -import { graphql } from '~/client/graphql'; -import { revalidate } from '~/client/revalidate-target'; - -const storeNameQuery = graphql(` - query storeNameQuery { - site { - settings { - storeName - } - } - } -`); - -export const getStoreName = cache(async () => { - const { data } = await client.fetch({ - document: storeNameQuery, - fetchOptions: { next: { revalidate } }, - }); - - return data.site.settings?.storeName; -}); diff --git a/core/app/[locale]/(default)/(auth)/account/page.tsx b/core/app/[locale]/(default)/(auth)/account/page.tsx deleted file mode 100644 index 14ae8cae7..000000000 --- a/core/app/[locale]/(default)/(auth)/account/page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { BookUser, Eye, Gift, Mail, Package, Settings } from 'lucide-react'; -import { getTranslations } from 'next-intl/server'; -import { ReactNode } from 'react'; - -import { Link } from '~/components/link'; -import { Message } from '~/components/ui/message'; -import { LocaleType } from '~/i18n'; - - -import { getStoreName } from './page-data'; -import { MessageWrapper } from './[tab]/_components/message-wrapper'; - -interface AccountItem { - children: ReactNode; - description?: string; - href: string; - title: string; -} - -const AccountItem = ({ children, title, description, href }: AccountItem) => { - return ( - - {children} - -

{title}

- {description ?

{description}

: null} -
- - ); -}; - -interface SearchParams { - register?: string; -} - -interface Props { - params: { - locale: LocaleType; - }; - searchParams?: SearchParams; -} - -export default async function AccountPage({ params: { locale }, searchParams }: Props) { - const t = await getTranslations({ locale, namespace: 'Account.Home' }); - const storeName = await getStoreName(); - - return ( -
-

{t('heading')}

- - - -
- - - - - - - - - - - - - - - - - - -
-
- ); -} - -export const runtime = 'edge'; diff --git a/core/app/[locale]/(default)/(auth)/login/layout.tsx b/core/app/[locale]/(default)/(auth)/login/layout.tsx deleted file mode 100644 index 616aea1b0..000000000 --- a/core/app/[locale]/(default)/(auth)/login/layout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export default function LoginLayout({ children }: PropsWithChildren) { - return children; -} diff --git a/core/app/[locale]/(default)/(auth)/login/register-customer/_components/register-customer-form/index.tsx b/core/app/[locale]/(default)/(auth)/login/register-customer/_components/register-customer-form/index.tsx deleted file mode 100644 index bc312a8df..000000000 --- a/core/app/[locale]/(default)/(auth)/login/register-customer/_components/register-customer-form/index.tsx +++ /dev/null @@ -1,336 +0,0 @@ -'use client'; - -import { useTranslations } from 'next-intl'; -import { ChangeEvent, useRef, useState } from 'react'; -import { useFormStatus } from 'react-dom'; -import ReCaptcha from 'react-google-recaptcha'; - -import { getRegisterCustomerQuery } from '~/app/[locale]/(default)/(auth)/login/register-customer/page-data'; -import { Button } from '~/components/ui/button'; -import { Field, Form, FormSubmit } from '~/components/ui/form'; -import { Message } from '~/components/ui/message'; - -import { login } from './_actions/login'; -import { registerCustomer } from './_actions/register-customer'; -import { - createFieldName, - CUSTOMER_FIELDS_TO_EXCLUDE, - FieldNameToFieldId, - FieldWrapper, - Password, - Picklist, - PicklistOrText, - Text, -} from './fields'; -import { useAccountStatusContext } from '../../../../account-status-provider'; - -interface FormStatus { - status: 'success' | 'error'; - message: string; -} - -export type CustomerFields = NonNullable< - Awaited> ->['customerFields']; -export type AddressFields = NonNullable< - Awaited> ->['addressFields']; -type Countries = NonNullable>>['countries']; -type CountryCode = Countries[number]['code']; -type CountryStates = Countries[number]['statesOrProvinces']; - -interface RegisterCustomerProps { - addressFields: AddressFields; - countries: Countries; - customerFields: CustomerFields; - defaultCountry: { - entityId: number; - code: CountryCode; - states: CountryStates; - }; - reCaptchaSettings?: { - isEnabledOnStorefront: boolean; - siteKey: string; - }; -} - -interface SumbitMessages { - messages: { - submit: string; - submitting: string; - }; -} - -const SubmitButton = ({ messages }: SumbitMessages) => { - const { pending } = useFormStatus(); - - return ( - - ); -}; - -export const RegisterCustomerForm = ({ - addressFields, - countries, - customerFields, - defaultCountry, - reCaptchaSettings, -}: RegisterCustomerProps) => { - const form = useRef(null); - const [formStatus, setFormStatus] = useState(null); - - const [textInputValid, setTextInputValid] = useState>({}); - const [passwordValid, setPassswordValid] = useState>({ - [FieldNameToFieldId.password]: true, - [FieldNameToFieldId.confirmPassword]: true, - }); - - const [countryStates, setCountryStates] = useState(defaultCountry.states); - - const reCaptchaRef = useRef(null); - const [reCaptchaToken, setReCaptchaToken] = useState(''); - const [isReCaptchaValid, setReCaptchaValid] = useState(true); - - const { setAccountState } = useAccountStatusContext(); - - const t = useTranslations('Account.Register'); - - const handleTextInputValidation = (e: ChangeEvent) => { - const fieldId = Number(e.target.id.split('-')[1]); - - const validityState = e.target.validity; - const validationStatus = validityState.valueMissing || validityState.typeMismatch; - - setTextInputValid({ ...textInputValid, [fieldId]: !validationStatus }); - }; - - const handlePasswordValidation = (e: ChangeEvent) => { - const fieldId = e.target.id.split('-')[1] ?? ''; - - switch (FieldNameToFieldId[Number(fieldId)]) { - case 'password': { - setPassswordValid((prevState) => ({ - ...prevState, - [fieldId]: !e.target.validity.valueMissing, - })); - - return; - } - - case 'confirmPassword': { - const confirmPassword = e.target.value; - - const passwordFieldName = createFieldName('customer', FieldNameToFieldId.password); - const password = new FormData(e.target.form ?? undefined).get(passwordFieldName); - - setPassswordValid((prevState) => ({ - ...prevState, - [fieldId]: password === confirmPassword && !e.target.validity.valueMissing, - })); - - return; - } - - default: { - setPassswordValid((prevState) => ({ - ...prevState, - [fieldId]: !e.target.validity.valueMissing, - })); - } - } - }; - - const handleCountryChange = (value: string) => { - const states = countries.find(({ code }) => code === value)?.statesOrProvinces; - - setCountryStates(states ?? []); - }; - - const onReCaptchaChange = (token: string | null) => { - if (!token) { - setReCaptchaValid(false); - - return; - } - - setReCaptchaToken(token); - setReCaptchaValid(true); - }; - - const onSubmit = async (formData: FormData) => { - if (formData.get('customer-password') !== formData.get('customer-confirmPassword')) { - setFormStatus({ - status: 'error', - message: t('equalPasswordValidatoinMessage'), - }); - - window.scrollTo({ - top: 0, - behavior: 'smooth', - }); - - return; - } - - if (reCaptchaSettings?.isEnabledOnStorefront && !reCaptchaToken) { - setReCaptchaValid(false); - - return; - } - - setReCaptchaValid(true); - - const submit = await registerCustomer({ formData, reCaptchaToken }); - - if (submit.status === 'success') { - form.current?.reset(); - - setAccountState({ status: 'success' }); - - await login(formData); - } - - if (submit.status === 'error') { - setFormStatus({ status: 'error', message: submit.error ?? '' }); - } - - window.scrollTo({ - top: 0, - behavior: 'smooth', - }); - }; - - return ( - <> - {formStatus && ( - -

{formStatus.message}

-
- )} -
-
- {customerFields - .filter((field) => !CUSTOMER_FIELDS_TO_EXCLUDE.includes(field.entityId)) - .map((field) => { - switch (field.__typename) { - case 'TextFormField': - return ( - - - - ); - - case 'PasswordFormField': { - return ( - - - - ); - } - - default: - return null; - } - })} -
-
- {addressFields.map((field) => { - switch (field.__typename) { - case 'TextFormField': - return ( - - - - ); - - case 'PicklistFormField': - return ( - - { - return { entityId: code, label: name }; - })} - /> - - ); - - case 'PicklistOrTextFormField': - return ( - - { - return { entityId: name, label: name }; - })} - /> - - ); - - default: - return null; - } - })} - {reCaptchaSettings?.isEnabledOnStorefront && ( - - - {!isReCaptchaValid && ( - - {t('recaptchaText')} - - )} - - )} -
- - - - -
- - ); -}; diff --git a/core/app/[locale]/(default)/account/(tabs)/_components/account-status-provider.tsx b/core/app/[locale]/(default)/account/(tabs)/_components/account-status-provider.tsx index b73426812..ab173dc1e 100644 --- a/core/app/[locale]/(default)/account/(tabs)/_components/account-status-provider.tsx +++ b/core/app/[locale]/(default)/account/(tabs)/_components/account-status-provider.tsx @@ -1,30 +1,27 @@ 'use client'; +import { usePathname } from 'next/navigation'; import { createContext, ReactNode, useContext, useEffect, useState } from 'react'; import { State as AccountState } from '../settings/_actions/submit-customer-change-password-form'; +const defaultState: AccountState = { status: 'idle', message: '' }; + export const AccountStatusContext = createContext<{ accountState: AccountState; setAccountState: (state: AccountState | ((prevState: AccountState) => AccountState)) => void; } | null>(null); -export const AccountStatusProvider = ({ - children, - isPermanentBanner = false, -}: { - children: ReactNode; - isPermanentBanner?: boolean; -}) => { - const [accountState, setAccountState] = useState({ status: 'idle', message: '' }); +export const AccountStatusProvider = ({ children }: { children: ReactNode }) => { + const [accountState, setAccountState] = useState(defaultState); + const pathname = usePathname(); useEffect(() => { - if (accountState.status !== 'idle' && !isPermanentBanner) { - setTimeout(() => { - setAccountState({ status: 'idle', message: '' }); - }, 3000); + // Reset account state when changing the route except the Account Page + if (pathname !== '/account/') { + setAccountState(defaultState); } - }, [accountState, setAccountState, isPermanentBanner]); + }, [pathname]); return ( diff --git a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/message-wrapper.tsx b/core/app/[locale]/(default)/account/(tabs)/_components/message-wrapper.tsx similarity index 85% rename from core/app/[locale]/(default)/(auth)/account/[tab]/_components/message-wrapper.tsx rename to core/app/[locale]/(default)/account/(tabs)/_components/message-wrapper.tsx index 783b33c3f..a9b613bb9 100644 --- a/core/app/[locale]/(default)/(auth)/account/[tab]/_components/message-wrapper.tsx +++ b/core/app/[locale]/(default)/account/(tabs)/_components/message-wrapper.tsx @@ -1,7 +1,8 @@ 'use client'; import { Message } from '~/components/ui/message'; -import { useAccountStatusContext } from '../../../account-status-provider'; + +import { useAccountStatusContext } from './account-status-provider'; interface Props { message?: string; diff --git a/core/app/[locale]/(default)/(auth)/account/layout.tsx b/core/app/[locale]/(default)/account/layout.tsx similarity index 100% rename from core/app/[locale]/(default)/(auth)/account/layout.tsx rename to core/app/[locale]/(default)/account/layout.tsx diff --git a/core/app/[locale]/(default)/account/page-data.ts b/core/app/[locale]/(default)/account/page-data.ts deleted file mode 100644 index 3f21fcbd4..000000000 --- a/core/app/[locale]/(default)/account/page-data.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { cache } from 'react'; - -import { client } from '~/client'; -import { graphql } from '~/client/graphql'; -import { revalidate } from '~/client/revalidate-target'; - -const storeNameQuery = graphql(` - query storeNameQuery { - site { - settings { - storeName - } - } - } -`); - -export const getStoreName = cache(async () => { - const { data } = await client.fetch({ - document: storeNameQuery, - fetchOptions: { next: { revalidate } }, - }); - - return data.site.settings?.storeName; -}); diff --git a/core/app/[locale]/(default)/account/page.tsx b/core/app/[locale]/(default)/account/page.tsx new file mode 100644 index 000000000..a296448be --- /dev/null +++ b/core/app/[locale]/(default)/account/page.tsx @@ -0,0 +1,52 @@ +import { BookUser, Settings } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import { ReactNode } from 'react'; + +import { Link } from '~/components/link'; + +import { MessageWrapper } from './(tabs)/_components/message-wrapper'; + +interface AccountItem { + children: ReactNode; + description?: string; + href: string; + title: string; +} + +const AccountItem = ({ children, title, description, href }: AccountItem) => { + return ( + + {children} + +

{title}

+ {description ?

{description}

: null} +
+ + ); +}; + +export default function Account() { + const t = useTranslations('Account.Home'); + + return ( +
+

{t('heading')}

+ + + +
+ + + + + + +
+
+ ); +} + +export const runtime = 'edge'; diff --git a/core/app/[locale]/(default)/(auth)/login/_actions/submit-change-password-form.ts b/core/app/[locale]/(default)/login/_actions/submit-change-password-form.ts similarity index 100% rename from core/app/[locale]/(default)/(auth)/login/_actions/submit-change-password-form.ts rename to core/app/[locale]/(default)/login/_actions/submit-change-password-form.ts diff --git a/core/app/[locale]/(default)/(auth)/login/_actions/submit-login-form.ts b/core/app/[locale]/(default)/login/_actions/submit-login-form.ts similarity index 100% rename from core/app/[locale]/(default)/(auth)/login/_actions/submit-login-form.ts rename to core/app/[locale]/(default)/login/_actions/submit-login-form.ts diff --git a/core/app/[locale]/(default)/(auth)/login/_actions/submit-reset-password-form.ts b/core/app/[locale]/(default)/login/_actions/submit-reset-password-form.ts similarity index 100% rename from core/app/[locale]/(default)/(auth)/login/_actions/submit-reset-password-form.ts rename to core/app/[locale]/(default)/login/_actions/submit-reset-password-form.ts diff --git a/core/app/[locale]/(default)/(auth)/login/_components/change-password-form.tsx b/core/app/[locale]/(default)/login/_components/change-password-form.tsx similarity index 92% rename from core/app/[locale]/(default)/(auth)/login/_components/change-password-form.tsx rename to core/app/[locale]/(default)/login/_components/change-password-form.tsx index bc37b15e5..c4f6e7b75 100644 --- a/core/app/[locale]/(default)/(auth)/login/_components/change-password-form.tsx +++ b/core/app/[locale]/(default)/login/_components/change-password-form.tsx @@ -12,14 +12,13 @@ import { FieldMessage, Form, FormSubmit, + Input, } from '~/components/ui/form'; - import { Message } from '~/components/ui/message'; -import { useRouter } from '~/navigation'; +import { useRouter } from '~/routing'; -import { useAccountStatusContext } from '../../account-status-provider'; +import { useAccountStatusContext } from '../../account/(tabs)/_components/account-status-provider'; import { submitChangePasswordForm } from '../_actions/submit-change-password-form'; -import { Input } from '~/components/ui/header/input'; interface Props { customerId: string; @@ -101,11 +100,11 @@ export const ChangePasswordForm = ({ customerId, customerToken }: Props) => { @@ -117,12 +116,12 @@ export const ChangePasswordForm = ({ customerId, customerToken }: Props) => { { const { pending } = useFormStatus(); - const t = useTranslations('Account.Login'); + const t = useTranslations('Login'); return ( + ); +}; + +export const RegisterCustomerForm = ({ + addressFields, + countries, + customerFields, + defaultCountry, + reCaptchaSettings, +}: RegisterCustomerProps) => { + const form = useRef(null); + const [formStatus, setFormStatus] = useState(null); + + const [textInputValid, setTextInputValid] = useState>({}); + const [passwordValid, setPassswordValid] = useState>({ + [FieldNameToFieldId.password]: true, + [FieldNameToFieldId.confirmPassword]: true, + }); + const [numbersInputValid, setNumbersInputValid] = useState>({}); + const [datesValid, setDatesValid] = useState>({}); + const [countryStates, setCountryStates] = useState(defaultCountry.states); + const [radioButtonsValid, setRadioButtonsValid] = useState>({}); + const [picklistValid, setPicklistValid] = useState>({}); + const [checkboxesValid, setCheckboxesValid] = useState>({}); + const [multiTextValid, setMultiTextValid] = useState>({}); + + const reCaptchaRef = useRef(null); + const [reCaptchaToken, setReCaptchaToken] = useState(''); + const [isReCaptchaValid, setReCaptchaValid] = useState(true); + + const { setAccountState } = useAccountStatusContext(); + + const t = useTranslations('Login.Register'); + + const handleTextInputValidation = (e: ChangeEvent) => { + const fieldId = Number(e.target.id.split('-')[1]); + + const validityState = e.target.validity; + const validationStatus = validityState.valueMissing || validityState.typeMismatch; + + setTextInputValid({ ...textInputValid, [fieldId]: !validationStatus }); + }; + const handleNumbersInputValidation = createNumbersInputValidationHandler( + setNumbersInputValid, + numbersInputValid, + ); + const handleMultiTextValidation = createMultilineTextValidationHandler( + setMultiTextValid, + multiTextValid, + ); + const handleDatesValidation = createDatesValidationHandler(setDatesValid, datesValid); + const handlePasswordValidation = (e: ChangeEvent) => { + const fieldId = e.target.id.split('-')[1] ?? ''; + + switch (FieldNameToFieldId[Number(fieldId)]) { + case 'password': { + setPassswordValid((prevState) => ({ + ...prevState, + [fieldId]: !e.target.validity.valueMissing, + })); + + return; + } + + case 'confirmPassword': { + const confirmPassword = e.target.value; + const field = customerFields.filter( + ({ entityId }) => entityId === Number(FieldNameToFieldId.password), + )[0]; + + if (!isAddressOrAccountFormField(field)) { + return; + } + + const passwordFieldName = createFieldName(field, 'customer'); + const password = new FormData(e.target.form ?? undefined).get(passwordFieldName); + + setPassswordValid((prevState) => ({ + ...prevState, + [fieldId]: password === confirmPassword && !e.target.validity.valueMissing, + })); + + return; + } + + default: { + setPassswordValid((prevState) => ({ + ...prevState, + [fieldId]: !e.target.validity.valueMissing, + })); + } + } + }; + + const handleCountryChange = (value: string) => { + const states = countries.find(({ code }) => code === value)?.statesOrProvinces; + + setCountryStates(states ?? []); + }; + const handleRadioButtonsChange = createRadioButtonsValidationHandler( + setRadioButtonsValid, + radioButtonsValid, + ); + const validatePicklistFields = createPreSubmitPicklistValidationHandler( + [...customerFields, ...addressFields], + setPicklistValid, + ); + const validateCheckboxFields = createPreSubmitCheckboxesValidationHandler( + [...customerFields, ...addressFields], + setCheckboxesValid, + ); + const preSubmitFieldsValidation = ( + e: MouseEvent & { target: HTMLButtonElement }, + ) => { + if (e.target.nodeName === 'BUTTON' && e.target.type === 'submit') { + validatePicklistFields(form.current); + validateCheckboxFields(form.current); + } + }; + + const onReCaptchaChange = (token: string | null) => { + if (!token) { + setReCaptchaValid(false); + + return; + } + + setReCaptchaToken(token); + setReCaptchaValid(true); + }; + + const onSubmit = async (formData: FormData) => { + if (formData.get('customer-password') !== formData.get('customer-confirmPassword')) { + setFormStatus({ + status: 'error', + message: t('equalPasswordValidatoinMessage'), + }); + + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + + return; + } + + if (reCaptchaSettings?.isEnabledOnStorefront && !reCaptchaToken) { + setReCaptchaValid(false); + + return; + } + + setReCaptchaValid(true); + + const submit = await registerCustomer({ formData, reCaptchaToken }); + + if (submit.status === 'success') { + setAccountState({ status: 'success' }); + + await login(formData); + } + + if (submit.status === 'error') { + setFormStatus({ status: 'error', message: submit.error ?? '' }); + } + + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + }; + + return ( + <> + {formStatus && ( + +

{formStatus.message}

+
+ )} + +
+ {customerFields + .filter((field) => !CUSTOMER_FIELDS_TO_EXCLUDE.includes(field.entityId)) + .map((field) => { + const fieldId = field.entityId; + const fieldName = createFieldName(field, 'customer'); + + switch (field.__typename) { + case 'TextFormField': + return ( + + + + ); + + case 'MultilineTextFormField': { + return ( + + + + ); + } + + case 'NumberFormField': { + return ( + + + + ); + } + + case 'DateFormField': { + return ( + + + + ); + } + + case 'RadioButtonsFormField': { + return ( + + + + ); + } + + case 'PicklistFormField': { + return ( + + + + ); + } + + case 'CheckboxesFormField': { + return ( + + + + ); + } + + case 'PasswordFormField': { + return ( + + + + ); + } + + default: + return null; + } + })} +
+
+ {addressFields.map((field) => { + const fieldId = field.entityId; + const fieldName = createFieldName(field, 'address'); + + switch (field.__typename) { + case 'TextFormField': { + return ( + + + + ); + } + + case 'MultilineTextFormField': { + return ( + + + + ); + } + + case 'NumberFormField': { + return ( + + + + ); + } + + case 'DateFormField': { + return ( + + + + ); + } + + case 'RadioButtonsFormField': { + return ( + + + + ); + } + + case 'PicklistFormField': { + const isCountrySelector = fieldId === FieldNameToFieldId.countryCode; + const picklistOptions = isCountrySelector + ? countries.map(({ name, code }) => ({ label: name, entityId: code })) + : field.options; + + return ( + + + + ); + } + + case 'CheckboxesFormField': { + return ( + + + + ); + } + + case 'PicklistOrTextFormField': { + return ( + + { + return { entityId: name, label: name }; + })} + /> + + ); + } + + case 'PasswordFormField': { + return ( + + + + ); + } + + default: + return null; + } + })} + {reCaptchaSettings?.isEnabledOnStorefront && ( + + + {!isReCaptchaValid && ( + + {t('recaptchaText')} + + )} + + )} +
+ + + + + + + ); +}; diff --git a/core/app/[locale]/(default)/(auth)/login/register-customer/page-data.ts b/core/app/[locale]/(default)/login/register-customer/page-data.ts similarity index 100% rename from core/app/[locale]/(default)/(auth)/login/register-customer/page-data.ts rename to core/app/[locale]/(default)/login/register-customer/page-data.ts diff --git a/core/app/[locale]/(default)/(auth)/login/register-customer/page.tsx b/core/app/[locale]/(default)/login/register-customer/page.tsx similarity index 100% rename from core/app/[locale]/(default)/(auth)/login/register-customer/page.tsx rename to core/app/[locale]/(default)/login/register-customer/page.tsx diff --git a/core/app/[locale]/layout.tsx b/core/app/[locale]/layout.tsx index 830e2b660..56afb5be8 100644 --- a/core/app/[locale]/layout.tsx +++ b/core/app/[locale]/layout.tsx @@ -14,7 +14,8 @@ import { revalidate } from '~/client/revalidate-target'; import { Notifications } from '../notifications'; import { Providers } from '../providers'; -import { AccountStatusProvider } from './(default)/(auth)/account-status-provider'; + +import { AccountStatusProvider } from './(default)/account/(tabs)/_components/account-status-provider'; const inter = Inter({ subsets: ['latin'], @@ -77,7 +78,9 @@ export default function RootLayout({ children, params: { locale } }: Props) { - {children} + + {children} + diff --git a/core/messages/en.json b/core/messages/en.json index 2b6c57b88..6d9adcb46 100644 --- a/core/messages/en.json +++ b/core/messages/en.json @@ -69,7 +69,8 @@ "wishlists": "Wish lists", "recentlyViewed": "Recently viewed", "settings": "Account settings", - "changePassword": "Change password" + "changePassword": "Change password", + "successMessage": "Your account has been successfully created" }, "Addresses": { "defaultAddress": "Default",