-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): add new address for customer
- Loading branch information
1 parent
cd51301
commit db6fa78
Showing
17 changed files
with
966 additions
and
18 deletions.
There are no files selected for viewing
75 changes: 75 additions & 0 deletions
75
apps/core/app/[locale]/(default)/account/[tab]/_actions/add-address.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
'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<{ | ||
[key: string]: FormDataEntryValue | { [key: string]: FormDataEntryValue }; | ||
}>((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' }; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
...ore/app/[locale]/(default)/account/[tab]/_components/add-address/fields/field-wrapper.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { PropsWithChildren } from 'react'; | ||
|
||
export enum FieldNameToFieldId { | ||
email = 1, | ||
password, | ||
confirmPassword, | ||
firstName, | ||
lastName, | ||
company, | ||
phone, | ||
address1, | ||
address2, | ||
city, | ||
countryCode, | ||
stateOrProvince, | ||
postalCode, | ||
currentPassword = 24, | ||
exclusiveOffers = 25, | ||
} | ||
|
||
const LAYOUT_SINGLE_LINE_FIELDS = [ | ||
FieldNameToFieldId.email, | ||
FieldNameToFieldId.company, | ||
FieldNameToFieldId.phone, | ||
]; | ||
|
||
export const FieldWrapper = ({ children, fieldId }: { fieldId: number } & PropsWithChildren) => { | ||
if (LAYOUT_SINGLE_LINE_FIELDS.includes(fieldId)) { | ||
return ( | ||
<div className="grid grid-cols-1 gap-y-6 lg:col-span-2 lg:grid-cols-2 lg:gap-x-6 lg:gap-y-2"> | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
return children; | ||
}; |
7 changes: 7 additions & 0 deletions
7
apps/core/app/[locale]/(default)/account/[tab]/_components/add-address/fields/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use client'; | ||
|
||
export * from './text'; | ||
export * from './password'; | ||
export * from './picklist'; | ||
export * from './picklist-or-text'; | ||
export * from './field-wrapper'; |
61 changes: 61 additions & 0 deletions
61
apps/core/app/[locale]/(default)/account/[tab]/_components/add-address/fields/password.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { Field, FieldControl, FieldLabel, FieldMessage } from '@bigcommerce/components/form'; | ||
import { Input } from '@bigcommerce/components/input'; | ||
import { useTranslations } from 'next-intl'; | ||
import { ChangeEvent } from 'react'; | ||
|
||
import { AddressFields, FieldNameToFieldId } from '..'; | ||
|
||
type PasswordType = Extract< | ||
NonNullable<AddressFields>[number], | ||
{ __typename: 'PasswordFormField' } | ||
>; | ||
|
||
interface PasswordProps { | ||
field: PasswordType; | ||
isValid?: boolean; | ||
onChange: (e: ChangeEvent<HTMLInputElement>) => void; | ||
name: string; | ||
variant?: 'error'; | ||
} | ||
|
||
export const Password = ({ field, isValid, name, onChange, variant }: PasswordProps) => { | ||
const t = useTranslations('Account.Register'); | ||
|
||
return ( | ||
<Field className="relative space-y-2 pb-7" name={name}> | ||
<FieldLabel htmlFor={`field-${field.entityId}`} isRequired={field.isRequired}> | ||
{field.label} | ||
</FieldLabel> | ||
<FieldControl asChild> | ||
<Input | ||
defaultValue={field.defaultText ?? undefined} | ||
id={`field-${field.entityId}`} | ||
maxLength={field.maxLength ?? undefined} | ||
onChange={onChange} | ||
onInvalid={onChange} | ||
required={field.isRequired} | ||
type="text" | ||
variant={variant} | ||
/> | ||
</FieldControl> | ||
{field.isRequired && ( | ||
<FieldMessage | ||
className="absolute inset-x-0 bottom-0 inline-flex w-full text-xs font-normal text-red-200" | ||
match="valueMissing" | ||
> | ||
{t('emptyPasswordValidatoinMessage')} | ||
</FieldMessage> | ||
)} | ||
{FieldNameToFieldId[field.entityId] === 'confirmPassword' && ( | ||
<FieldMessage | ||
className="absolute inset-x-0 bottom-0 inline-flex w-full text-xs font-normal text-red-200" | ||
match={() => { | ||
return !isValid; | ||
}} | ||
> | ||
{t('equalPasswordValidatoinMessage')} | ||
</FieldMessage> | ||
)} | ||
</Field> | ||
); | ||
}; |
93 changes: 93 additions & 0 deletions
93
.../app/[locale]/(default)/account/[tab]/_components/add-address/fields/picklist-or-text.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { Field, FieldControl, FieldLabel, FieldMessage } from '@bigcommerce/components/form'; | ||
import { Input } from '@bigcommerce/components/input'; | ||
import { Select, SelectContent, SelectItem } from '@bigcommerce/components/select'; | ||
import { Loader2 as Spinner } from 'lucide-react'; | ||
import { useTranslations } from 'next-intl'; | ||
import { ChangeEvent } from 'react'; | ||
|
||
import { AddressFields, FieldNameToFieldId } from '..'; | ||
|
||
type PicklistOrTextType = Extract< | ||
NonNullable<AddressFields>[number], | ||
{ __typename: 'PicklistOrTextFormField' } | ||
>; | ||
|
||
interface PicklistOrTextProps { | ||
defaultValue?: string; | ||
field: PicklistOrTextType; | ||
name: string; | ||
onChange?: (e: ChangeEvent<HTMLInputElement>) => void; | ||
options: Array<{ label: string; entityId: string | number }>; | ||
pending?: boolean; | ||
variant?: 'error'; | ||
} | ||
|
||
export const PicklistOrText = ({ | ||
defaultValue, | ||
field, | ||
name, | ||
onChange, | ||
options, | ||
pending, | ||
variant, | ||
}: PicklistOrTextProps) => { | ||
const t = useTranslations('Account.Register'); | ||
|
||
return ( | ||
<Field className="relative space-y-2 pb-7" name={name}> | ||
<FieldLabel htmlFor={`field-${field.entityId}`} isRequired={field.isRequired}> | ||
<span className="flex justify-start"> | ||
{field.label} | ||
{pending && field.entityId === FieldNameToFieldId.stateOrProvince && ( | ||
<span className="ms-1 text-primary"> | ||
<Spinner aria-hidden="true" className="animate-spin" /> | ||
<span className="sr-only">{t('loadingStates')}</span> | ||
</span> | ||
)} | ||
</span> | ||
</FieldLabel> | ||
<FieldControl asChild> | ||
{field.entityId === FieldNameToFieldId.stateOrProvince && options.length === 0 ? ( | ||
<Input | ||
disabled={pending} | ||
id={`field-${field.entityId}`} | ||
onChange={field.isRequired ? onChange : undefined} | ||
onInvalid={field.isRequired ? onChange : undefined} | ||
required={field.isRequired} | ||
type="text" | ||
variant={variant} | ||
/> | ||
) : ( | ||
<Select | ||
aria-label={field.label} | ||
defaultValue={defaultValue} | ||
disabled={pending} | ||
id={`field-${field.entityId}`} | ||
key={defaultValue} | ||
placeholder={field.label} | ||
required={field.isRequired} | ||
> | ||
<SelectContent> | ||
{field.entityId === FieldNameToFieldId.stateOrProvince && | ||
options.map(({ entityId, label }) => { | ||
return ( | ||
<SelectItem key={entityId} value={entityId.toString()}> | ||
{label} | ||
</SelectItem> | ||
); | ||
})} | ||
</SelectContent> | ||
</Select> | ||
)} | ||
</FieldControl> | ||
{field.isRequired && options.length === 0 && ( | ||
<FieldMessage | ||
className="absolute inset-x-0 bottom-0 inline-flex w-full text-xs font-normal text-red-200" | ||
match="valueMissing" | ||
> | ||
{t('emptyTextValidatoinMessage')} | ||
</FieldMessage> | ||
)} | ||
</Field> | ||
); | ||
}; |
46 changes: 46 additions & 0 deletions
46
apps/core/app/[locale]/(default)/account/[tab]/_components/add-address/fields/picklist.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { Field, FieldControl, FieldLabel } from '@bigcommerce/components/form'; | ||
import { Select, SelectContent, SelectItem } from '@bigcommerce/components/select'; | ||
|
||
import { AddressFields, FieldNameToFieldId } from '..'; | ||
|
||
type PicklistType = Extract< | ||
NonNullable<AddressFields>[number], | ||
{ __typename: 'PicklistFormField' } | ||
>; | ||
|
||
interface PicklistProps { | ||
defaultValue?: string; | ||
field: PicklistType; | ||
name: string; | ||
onChange?: (value: string) => Promise<void>; | ||
options: Array<{ label: string; entityId: string | number }>; | ||
} | ||
|
||
export const Picklist = ({ defaultValue, field, name, onChange, options }: PicklistProps) => { | ||
return ( | ||
<Field className="relative space-y-2 pb-7" name={name}> | ||
<FieldLabel htmlFor={`field-${field.entityId}`} isRequired={field.isRequired}> | ||
{field.label} | ||
</FieldLabel> | ||
<FieldControl asChild> | ||
<Select | ||
aria-label={field.choosePrefix} | ||
defaultValue={defaultValue} | ||
id={`field-${field.entityId}`} | ||
onValueChange={field.entityId === FieldNameToFieldId.countryCode ? onChange : undefined} | ||
placeholder={field.choosePrefix} | ||
required={field.isRequired} | ||
> | ||
<SelectContent> | ||
{field.entityId === FieldNameToFieldId.countryCode && | ||
options.map(({ label, entityId }) => ( | ||
<SelectItem key={entityId} value={entityId.toString()}> | ||
{label} | ||
</SelectItem> | ||
))} | ||
</SelectContent> | ||
</Select> | ||
</FieldControl> | ||
</Field> | ||
); | ||
}; |
56 changes: 56 additions & 0 deletions
56
apps/core/app/[locale]/(default)/account/[tab]/_components/add-address/fields/text.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { Field, FieldControl, FieldLabel, FieldMessage } from '@bigcommerce/components/form'; | ||
import { Input } from '@bigcommerce/components/input'; | ||
import { useTranslations } from 'next-intl'; | ||
import { ChangeEvent } from 'react'; | ||
|
||
import { AddressFields, FieldNameToFieldId } from '..'; | ||
|
||
type TextType = Extract<NonNullable<AddressFields>[number], { __typename: 'TextFormField' }>; | ||
|
||
interface TextProps { | ||
field: TextType; | ||
isValid?: boolean; | ||
name: string; | ||
onChange: (e: ChangeEvent<HTMLInputElement>) => void; | ||
type?: string; | ||
} | ||
|
||
export const Text = ({ field, isValid, name, onChange, type }: TextProps) => { | ||
const t = useTranslations('Account.Register'); // TODO: update later | ||
|
||
return ( | ||
<Field className="relative space-y-2 pb-7" name={name}> | ||
<FieldLabel htmlFor={`field-${field.entityId}`} isRequired={field.isRequired}> | ||
{field.label} | ||
</FieldLabel> | ||
<FieldControl asChild> | ||
<Input | ||
defaultValue={field.defaultText ?? undefined} | ||
id={`field-${field.entityId}`} | ||
maxLength={field.maxLength ?? undefined} | ||
onChange={field.isRequired ? onChange : undefined} | ||
onInvalid={field.isRequired ? onChange : undefined} | ||
required={field.isRequired} | ||
type={type ?? 'text'} | ||
variant={isValid === false ? 'error' : undefined} | ||
/> | ||
</FieldControl> | ||
{field.isRequired && ( | ||
<FieldMessage | ||
className="absolute inset-x-0 bottom-0 inline-flex w-full text-xs font-normal text-red-200" | ||
match="valueMissing" | ||
> | ||
{t('emptyTextValidatoinMessage')} | ||
</FieldMessage> | ||
)} | ||
{FieldNameToFieldId[field.entityId] === 'email' && ( | ||
<FieldMessage | ||
className="absolute inset-x-0 bottom-0 inline-flex w-full text-xs font-normal text-red-200" | ||
match="typeMismatch" | ||
> | ||
{t('emailValidationMessage')} | ||
</FieldMessage> | ||
)} | ||
</Field> | ||
); | ||
}; |
Oops, something went wrong.