From 1a3032bbd36f91aac386cba0c5efb0472a7e479a Mon Sep 17 00:00:00 2001 From: barsa Date: Tue, 6 Jan 2026 16:13:59 +0900 Subject: [PATCH] Refactor Address Handling in Signup and Profile Components - Updated address handling in Salesforce account and contact services to remove unnecessary address fields during signup, improving data management. - Refactored address input fields in the SignupForm and AddressForm components to ensure proper labeling and validation for Japanese address formats. - Enhanced address display logic in ProfileContainer and AddressConfirmation components for better user experience. - Standardized address field names and improved placeholder texts for clarity and consistency across the application. --- .../services/salesforce-account.service.ts | 32 +--- .../workflows/signup-workflow.service.ts | 18 +- .../signup/signup-account-resolver.service.ts | 16 -- .../workflows/signup/signup-whmcs.service.ts | 2 +- .../account/components/AddressCard.tsx | 8 +- .../account/views/ProfileContainer.tsx | 10 +- .../auth/components/LoginForm/LoginForm.tsx | 9 + .../auth/components/SignupForm/SignupForm.tsx | 158 ++++++++++++++++-- .../SignupForm/steps/AccountStep.tsx | 34 ++-- .../SignupForm/steps/AddressStep.tsx | 104 +++++++----- .../SignupForm/steps/PasswordStep.tsx | 8 +- .../SignupForm/steps/ReviewStep.tsx | 4 +- .../components/base/AddressConfirmation.tsx | 46 ++--- .../services/components/base/AddressForm.tsx | 16 +- pnpm-lock.yaml | 105 ------------ 15 files changed, 290 insertions(+), 280 deletions(-) diff --git a/apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts b/apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts index 7ac32b58..cc2bb1ae 100644 --- a/apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts +++ b/apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts @@ -175,14 +175,8 @@ export class SalesforceAccountService { ): Promise<{ accountId: string; accountNumber: string }> { this.logger.log("Creating new Salesforce Account", { email: data.email }); - const accountPayload = { + const accountPayload: Record = { Name: `${data.firstName} ${data.lastName}`, - BillingStreet: - data.address.address1 + (data.address.address2 ? `\n${data.address.address2}` : ""), - BillingCity: data.address.city, - BillingState: data.address.state, - BillingPostalCode: data.address.postcode, - BillingCountry: data.address.country, Phone: data.phone, // Portal tracking fields [this.portalStatusField]: "Active", @@ -242,18 +236,12 @@ export class SalesforceAccountService { email: data.email, }); - const contactPayload = { + const contactPayload: Record = { AccountId: data.accountId, FirstName: data.firstName, LastName: data.lastName, Email: data.email, Phone: data.phone, - MailingStreet: - data.address.address1 + (data.address.address2 ? `\n${data.address.address2}` : ""), - MailingCity: data.address.city, - MailingState: data.address.state, - MailingPostalCode: data.address.postcode, - MailingCountry: data.address.country, }; try { @@ -351,14 +339,6 @@ export interface CreateSalesforceAccountRequest { lastName: string; email: string; phone: string; - address: { - address1: string; - address2?: string; - city: string; - state: string; - postcode: string; - country: string; - }; } /** @@ -370,12 +350,4 @@ export interface CreateSalesforceContactRequest { lastName: string; email: string; phone: string; - address: { - address1: string; - address2?: string; - city: string; - state: string; - postcode: string; - country: string; - }; } diff --git a/apps/bff/src/modules/auth/infra/workflows/signup-workflow.service.ts b/apps/bff/src/modules/auth/infra/workflows/signup-workflow.service.ts index c70baf30..6bbc9d5f 100644 --- a/apps/bff/src/modules/auth/infra/workflows/signup-workflow.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/signup-workflow.service.ts @@ -271,14 +271,7 @@ export class SignupWorkflowService { lastName, email: normalizedEmail, phone, - address: { - address1: address.address1, - address2: address.address2 || undefined, - city: address.city, - state: address.state, - postcode: address.postcode, - country: address.country, - }, + // Address not added to Salesforce during signup }); await this.salesforceAccountService.createContact({ @@ -287,14 +280,7 @@ export class SignupWorkflowService { lastName, email: normalizedEmail, phone, - address: { - address1: address.address1, - address2: address.address2 || undefined, - city: address.city, - state: address.state, - postcode: address.postcode, - country: address.country, - }, + // Address not added to Salesforce during signup }); return { diff --git a/apps/bff/src/modules/auth/infra/workflows/signup/signup-account-resolver.service.ts b/apps/bff/src/modules/auth/infra/workflows/signup/signup-account-resolver.service.ts index 04e76992..09e8f6b8 100644 --- a/apps/bff/src/modules/auth/infra/workflows/signup/signup-account-resolver.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/signup/signup-account-resolver.service.ts @@ -78,14 +78,6 @@ export class SignupAccountResolverService { lastName, email: normalizedEmail, phone, - address: { - address1: address.address1, - address2: address.address2 || undefined, - city: address.city, - state: address.state, - postcode: address.postcode, - country: address.country, - }, }); await this.salesforceAccountService.createContact({ @@ -94,14 +86,6 @@ export class SignupAccountResolverService { lastName, email: normalizedEmail, phone, - address: { - address1: address.address1, - address2: address.address2 || undefined, - city: address.city, - state: address.state, - postcode: address.postcode, - country: address.country, - }, }); const snapshot: SignupAccountSnapshot = { diff --git a/apps/bff/src/modules/auth/infra/workflows/signup/signup-whmcs.service.ts b/apps/bff/src/modules/auth/infra/workflows/signup/signup-whmcs.service.ts index f1dc8b2c..7c11521d 100644 --- a/apps/bff/src/modules/auth/infra/workflows/signup/signup-whmcs.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/signup/signup-whmcs.service.ts @@ -105,7 +105,7 @@ export class SignupWhmcsService { companyname: params.company || "", phonenumber: params.phone, address1: params.address.address1, - address2: params.address.address2 || "", + address2: params.address.address2 ?? "", city: params.address.city, state: params.address.state, postcode: params.address.postcode, diff --git a/apps/portal/src/features/account/components/AddressCard.tsx b/apps/portal/src/features/account/components/AddressCard.tsx index d5f93be6..b512c87e 100644 --- a/apps/portal/src/features/account/components/AddressCard.tsx +++ b/apps/portal/src/features/account/components/AddressCard.tsx @@ -88,8 +88,12 @@ export function AddressCard({ ) : (
- {address.address1 &&

{address.address1}

} - {address.address2 &&

{address.address2}

} + {(address.address2 || address.address1) && ( +

{address.address2 || address.address1}

+ )} + {address.address2 && address.address1 && ( +

{address.address1}

+ )} {(address.city || address.state || address.postcode) && (

{[address.city, address.state, address.postcode].filter(Boolean).join(", ")} diff --git a/apps/portal/src/features/account/views/ProfileContainer.tsx b/apps/portal/src/features/account/views/ProfileContainer.tsx index 05802661..b8382c71 100644 --- a/apps/portal/src/features/account/views/ProfileContainer.tsx +++ b/apps/portal/src/features/account/views/ProfileContainer.tsx @@ -460,11 +460,13 @@ export default function ProfileContainer() { {address.values.address1 || address.values.city ? (

- {address.values.address1 && ( -

{address.values.address1}

+ {(address.values.address2 || address.values.address1) && ( +

+ {address.values.address2 || address.values.address1} +

)} - {address.values.address2 && ( -

{address.values.address2}

+ {address.values.address2 && address.values.address1 && ( +

{address.values.address1}

)}

{[address.values.city, address.values.state, address.values.postcode] diff --git a/apps/portal/src/features/auth/components/LoginForm/LoginForm.tsx b/apps/portal/src/features/auth/components/LoginForm/LoginForm.tsx index d0c6bfd3..281a1a25 100644 --- a/apps/portal/src/features/auth/components/LoginForm/LoginForm.tsx +++ b/apps/portal/src/features/auth/components/LoginForm/LoginForm.tsx @@ -160,6 +160,15 @@ export function LoginForm({ Sign up

+

+ Existing customer?{" "} + + Migrate your account + +

)} diff --git a/apps/portal/src/features/auth/components/SignupForm/SignupForm.tsx b/apps/portal/src/features/auth/components/SignupForm/SignupForm.tsx index 269d7800..d8104db7 100644 --- a/apps/portal/src/features/auth/components/SignupForm/SignupForm.tsx +++ b/apps/portal/src/features/auth/components/SignupForm/SignupForm.tsx @@ -5,16 +5,19 @@ "use client"; -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect, useRef } from "react"; +import { flushSync } from "react-dom"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; import { ErrorMessage } from "@/components/atoms"; import { useSignupWithRedirect } from "../../hooks/use-auth"; import { signupInputSchema, buildSignupRequest } from "@customer-portal/domain/auth"; +import { genderEnum } from "@customer-portal/domain/common"; import { addressFormSchema } from "@customer-portal/domain/customer"; import { useZodForm } from "@/shared/hooks"; import { z } from "zod"; import { getSafeRedirect } from "@/features/auth/utils/route-protection"; +import { formatJapanesePostalCode } from "@/shared/constants"; import { MultiStepForm } from "./MultiStepForm"; import { AccountStep } from "./steps/AccountStep"; @@ -30,12 +33,20 @@ import { PasswordStep } from "./steps/PasswordStep"; * - dateOfBirth: Required for signup (domain schema makes it optional) * - gender: Required for signup (domain schema makes it optional) */ -const genderSchema = z.enum(["male", "female", "other"]); +const genderSchema = genderEnum; + +const signupAddressSchema = addressFormSchema.extend({ + address2: z + .string() + .min(1, "Address line 2 is required") + .max(200, "Address line 2 is too long") + .trim(), +}); const signupFormBaseSchema = signupInputSchema.omit({ sfNumber: true }).extend({ confirmPassword: z.string().min(1, "Please confirm your password"), phoneCountryCode: z.string().regex(/^\+\d{1,4}$/, "Enter a valid country code (e.g., +81)"), - address: addressFormSchema, + address: signupAddressSchema, dateOfBirth: z.string().min(1, "Date of birth is required"), gender: genderSchema, }); @@ -51,6 +62,7 @@ const signupFormSchema = signupFormBaseSchema }); type SignupFormData = z.infer; +type SignupAddress = SignupFormData["address"]; interface SignupFormProps { onSuccess?: () => void; @@ -122,6 +134,7 @@ export function SignupForm({ initialEmail, showFooterLinks = true, }: SignupFormProps) { + const formRef = useRef(null); const searchParams = useSearchParams(); const { signup, loading, error, clearError } = useSignupWithRedirect({ redirectTo }); const [step, setStep] = useState(0); @@ -163,8 +176,10 @@ export function SignupForm({ const formattedPhone = `+${countryDigits}.${phoneDigits}`; // Build request with normalized address and phone + // Exclude UI-only fields (confirmPassword) from the request + const { confirmPassword: _confirmPassword, ...requestData } = data; const request = buildSignupRequest({ - ...data, + ...requestData, phone: formattedPhone, dateOfBirth: data.dateOfBirth || undefined, gender: data.gender || undefined, @@ -193,6 +208,105 @@ export function SignupForm({ isSubmitting, } = form; + const normalizeAutofillValue = useCallback( + (field: string, value: string) => { + switch (field) { + case "phoneCountryCode": { + let normalized = value.replace(/[^\d+]/g, ""); + if (!normalized.startsWith("+")) normalized = "+" + normalized.replace(/\+/g, ""); + return normalized.slice(0, 5); + } + case "phone": + return value.replace(/\D/g, ""); + case "address.postcode": + return formatJapanesePostalCode(value); + default: + return value; + } + }, + [formatJapanesePostalCode] + ); + + const syncStepValues = useCallback( + (shouldFlush = true) => { + const formNode = formRef.current; + if (!formNode) { + return values; + } + + const nextValues: SignupFormData = { + ...values, + address: { ...values.address }, + }; + + const fields = formNode.querySelectorAll( + "[data-field]" + ); + fields.forEach(field => { + const key = field.dataset.field; + if (!key) { + return; + } + const normalized = normalizeAutofillValue(key, field.value); + if (key.startsWith("address.")) { + const addressKey = key.replace("address.", "") as keyof SignupAddress; + nextValues.address[addressKey] = normalized; + } else if (key === "acceptTerms" || key === "marketingConsent") { + // Handle boolean fields separately + const boolValue = + field.type === "checkbox" ? (field as HTMLInputElement).checked : normalized === "true"; + (nextValues as Record)[key] = boolValue; + } else { + // Only assign to string fields + const stringKey = key as keyof Pick< + SignupFormData, + Exclude + >; + (nextValues as Record)[stringKey] = normalized; + } + }); + + const applySyncedValues = () => { + (Object.keys(nextValues) as Array).forEach(key => { + if (key === "address") { + return; + } + if (nextValues[key] !== values[key]) { + setFormValue(key, nextValues[key]); + } + }); + + const addressChanged = (Object.keys(nextValues.address) as Array).some( + key => nextValues.address[key] !== values.address[key] + ); + if (addressChanged) { + setFormValue("address", nextValues.address); + } + }; + + if (shouldFlush) { + flushSync(() => { + applySyncedValues(); + }); + } else { + applySyncedValues(); + } + + return nextValues; + }, + [normalizeAutofillValue, setFormValue, values] + ); + + useEffect(() => { + const syncTimer = window.setTimeout(() => { + syncStepValues(false); + }, 0); + + return () => { + window.clearTimeout(syncTimer); + }; + }, [step, syncStepValues]); + const isLastStep = step === STEPS.length - 1; const markStepTouched = useCallback( @@ -208,7 +322,7 @@ export function SignupForm({ ); const isStepValid = useCallback( - (stepIndex: number) => { + (stepIndex: number, data: SignupFormData = values) => { const stepKey = STEPS[stepIndex]?.key; if (!stepKey) { return true; @@ -217,12 +331,13 @@ export function SignupForm({ if (!schema) { return true; } - return schema.safeParse(values).success; + return schema.safeParse(data).success; }, [values] ); const handleNext = useCallback(() => { + const syncedValues = syncStepValues(); markStepTouched(step); if (isLastStep) { @@ -230,12 +345,12 @@ export function SignupForm({ return; } - if (!isStepValid(step)) { + if (!isStepValid(step, syncedValues)) { return; } setStep(s => Math.min(s + 1, STEPS.length - 1)); - }, [handleSubmit, isLastStep, isStepValid, markStepTouched, step]); + }, [handleSubmit, isLastStep, isStepValid, markStepTouched, step, syncStepValues]); const handlePrevious = useCallback(() => { setStep(s => Math.max(0, s - 1)); @@ -264,15 +379,24 @@ export function SignupForm({ return (
- +
{ + event.preventDefault(); + handleNext(); + }} + > + + {error && ( diff --git a/apps/portal/src/features/auth/components/SignupForm/steps/AccountStep.tsx b/apps/portal/src/features/auth/components/SignupForm/steps/AccountStep.tsx index 4cb589bc..0905a26f 100644 --- a/apps/portal/src/features/auth/components/SignupForm/steps/AccountStep.tsx +++ b/apps/portal/src/features/auth/components/SignupForm/steps/AccountStep.tsx @@ -6,6 +6,7 @@ import { Input } from "@/components/atoms"; import { FormField } from "@/components/molecules/FormField/FormField"; +import { genderEnum } from "@customer-portal/domain/common"; interface AccountStepProps { form: { @@ -29,6 +30,8 @@ interface AccountStepProps { export function AccountStep({ form }: AccountStepProps) { const { values, errors, touched, setValue, setTouchedField } = form; const getError = (field: string) => (touched[field] ? errors[field] : undefined); + const genderOptions = genderEnum.options; + const formatGender = (value: string) => value.charAt(0).toUpperCase() + value.slice(1); return (
@@ -41,8 +44,9 @@ export function AccountStep({ form }: AccountStepProps) { onChange={e => setValue("firstName", e.target.value)} onBlur={() => setTouchedField("firstName")} placeholder="Taro" - autoComplete="given-name" + autoComplete="section-signup given-name" autoFocus + data-field="firstName" /> @@ -52,7 +56,8 @@ export function AccountStep({ form }: AccountStepProps) { onChange={e => setValue("lastName", e.target.value)} onBlur={() => setTouchedField("lastName")} placeholder="Yamada" - autoComplete="family-name" + autoComplete="section-signup family-name" + data-field="lastName" />
@@ -66,7 +71,8 @@ export function AccountStep({ form }: AccountStepProps) { onChange={e => setValue("email", e.target.value)} onBlur={() => setTouchedField("email")} placeholder="taro.yamada@example.com" - autoComplete="email" + autoComplete="section-signup email" + data-field="email" /> @@ -90,8 +96,9 @@ export function AccountStep({ form }: AccountStepProps) { }} onBlur={() => setTouchedField("phoneCountryCode")} placeholder="+81" - autoComplete="tel-country-code" + autoComplete="section-signup tel-country-code" className="w-20 text-center" + data-field="phoneCountryCode" /> setTouchedField("phone")} placeholder="9012345678" - autoComplete="tel-national" + autoComplete="section-signup tel-national" className="flex-1" + data-field="phone" />
@@ -119,7 +127,8 @@ export function AccountStep({ form }: AccountStepProps) { value={values.dateOfBirth ?? ""} onChange={e => setValue("dateOfBirth", e.target.value || undefined)} onBlur={() => setTouchedField("dateOfBirth")} - autoComplete="bday" + autoComplete="section-signup bday" + data-field="dateOfBirth" /> @@ -129,6 +138,8 @@ export function AccountStep({ form }: AccountStepProps) { value={values.gender ?? ""} onChange={e => setValue("gender", e.target.value || undefined)} onBlur={() => setTouchedField("gender")} + autoComplete="section-signup sex" + data-field="gender" className={[ "flex h-10 w-full rounded-md border border-input bg-background text-foreground px-3 py-2 text-sm", "ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none", @@ -141,9 +152,11 @@ export function AccountStep({ form }: AccountStepProps) { aria-invalid={Boolean(getError("gender")) || undefined} > - - - + {genderOptions.map(option => ( + + ))}
@@ -156,7 +169,8 @@ export function AccountStep({ form }: AccountStepProps) { onChange={e => setValue("company", e.target.value)} onBlur={() => setTouchedField("company")} placeholder="Company name" - autoComplete="organization" + autoComplete="section-signup organization" + data-field="company" />
diff --git a/apps/portal/src/features/auth/components/SignupForm/steps/AddressStep.tsx b/apps/portal/src/features/auth/components/SignupForm/steps/AddressStep.tsx index aa76d5ba..9151d047 100644 --- a/apps/portal/src/features/auth/components/SignupForm/steps/AddressStep.tsx +++ b/apps/portal/src/features/auth/components/SignupForm/steps/AddressStep.tsx @@ -5,21 +5,22 @@ * - postcode → postcode * - state → state (prefecture) * - city → city - * - address1 → address1 (street/block address) - * - address2 → address2 (building/room - optional) + * - address1 → address1 (building/room) + * - address2 → address2 (street/block) * - country → "JP" */ "use client"; -import { useCallback } from "react"; +import { useCallback, useEffect } from "react"; +import { ChevronDown } from "lucide-react"; import { Input } from "@/components/atoms"; import { FormField } from "@/components/molecules/FormField/FormField"; import { JAPAN_PREFECTURES, formatJapanesePostalCode } from "@/shared/constants"; interface AddressData { address1: string; - address2?: string; + address2: string; city: string; state: string; postcode: string; @@ -64,9 +65,11 @@ export function AddressStep({ form }: AddressStepProps) { const markTouched = () => setTouchedField("address"); // Set Japan as default country on mount if empty - if (!address.country) { - setValue("address", { ...address, country: "JP", countryCode: "JP" }); - } + useEffect(() => { + if (!address.country) { + setValue("address", { ...address, country: "JP", countryCode: "JP" }); + } + }, [address, setValue]); return (
@@ -79,33 +82,42 @@ export function AddressStep({ form }: AddressStepProps) { > {/* Prefecture Selection */} - +
+ +
+ +
+
{/* City/Ward */} @@ -121,46 +133,50 @@ export function AddressStep({ form }: AddressStepProps) { onChange={e => updateAddress("city", e.target.value)} onBlur={markTouched} placeholder="Shibuya-ku" - autoComplete="address-level2" + autoComplete="section-signup address-level2" + data-field="address.city" /> - {/* Street Address */} + {/* Street / Block (Address 2) */} updateAddress("address1", e.target.value)} + type="text" + value={address.address2} + onChange={e => updateAddress("address2", e.target.value)} onBlur={markTouched} - placeholder="3-8-2 Higashi Azabu" - autoComplete="address-line1" + placeholder="2-20-9 Wakabayashi" + autoComplete="section-signup address-line1" + required + data-field="address.address2" /> - {/* Building & Room (Optional - for apartments) */} + {/* Building / Room (Address 1) */} updateAddress("address2", e.target.value)} + type="text" + value={address.address1} + onChange={e => updateAddress("address1", e.target.value)} onBlur={markTouched} - placeholder="3F Azabu Maruka Bldg" - autoComplete="address-line2" + placeholder="Gramercy 201" + autoComplete="section-signup address-line2" + required + data-field="address.address1" /> - -

- Please input your address in Japan. This will be used for service delivery and setup. -

); } diff --git a/apps/portal/src/features/auth/components/SignupForm/steps/PasswordStep.tsx b/apps/portal/src/features/auth/components/SignupForm/steps/PasswordStep.tsx index cf79d8a1..c5132368 100644 --- a/apps/portal/src/features/auth/components/SignupForm/steps/PasswordStep.tsx +++ b/apps/portal/src/features/auth/components/SignupForm/steps/PasswordStep.tsx @@ -38,7 +38,7 @@ export function PasswordStep({ form }: PasswordStepProps) { type="email" name="email" value={values.email} - autoComplete="username email" + autoComplete="section-signup username" readOnly className="sr-only" tabIndex={-1} @@ -53,7 +53,8 @@ export function PasswordStep({ form }: PasswordStepProps) { onChange={e => setValue("password", e.target.value)} onBlur={() => setTouchedField("password")} placeholder="Create a secure password" - autoComplete="new-password" + autoComplete="section-signup new-password" + data-field="password" /> @@ -97,7 +98,8 @@ export function PasswordStep({ form }: PasswordStepProps) { onChange={e => setValue("confirmPassword", e.target.value)} onBlur={() => setTouchedField("confirmPassword")} placeholder="Re-enter your password" - autoComplete="new-password" + autoComplete="section-signup new-password" + data-field="confirmPassword" /> diff --git a/apps/portal/src/features/auth/components/SignupForm/steps/ReviewStep.tsx b/apps/portal/src/features/auth/components/SignupForm/steps/ReviewStep.tsx index 2811b7ad..1131608c 100644 --- a/apps/portal/src/features/auth/components/SignupForm/steps/ReviewStep.tsx +++ b/apps/portal/src/features/auth/components/SignupForm/steps/ReviewStep.tsx @@ -73,7 +73,7 @@ interface ReviewStepProps { gender?: "male" | "female" | "other"; address: { address1: string; - address2?: string; + address2: string; city: string; state: string; postcode: string; @@ -95,8 +95,8 @@ export function ReviewStep({ form }: ReviewStepProps) { // Format address for display const formattedAddress = [ - address.address1, address.address2, + address.address1, address.city, address.state, address.postcode, diff --git a/apps/portal/src/features/services/components/base/AddressConfirmation.tsx b/apps/portal/src/features/services/components/base/AddressConfirmation.tsx index ce1a614b..43ecd91f 100644 --- a/apps/portal/src/features/services/components/base/AddressConfirmation.tsx +++ b/apps/portal/src/features/services/components/base/AddressConfirmation.tsx @@ -305,24 +305,7 @@ export function AddressConfirmation({
- { - setError(null); // Clear error on input - setEditedAddress(prev => (prev ? { ...prev, address1: e.target.value } : null)); - }} - className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-colors" - placeholder="123 Main Street" - required - /> -
- -
- (prev ? { ...prev, address2: e.target.value } : null)); }} className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-colors" - placeholder="Apartment, suite, etc. (optional)" + placeholder="2-20-9 Wakabayashi" + /> +
+ +
+ + { + setError(null); // Clear error on input + setEditedAddress(prev => (prev ? { ...prev, address1: e.target.value } : null)); + }} + className="w-full px-3 py-2 border border-input rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-colors" + placeholder="Gramercy 201" + required />
@@ -427,9 +427,11 @@ export function AddressConfirmation({ {address?.address1 ? (
-

{address.address1}

- {address.address2 ? ( -

{address.address2}

+ {(address.address2 || address.address1) && ( +

{address.address2 || address.address1}

+ )} + {address.address2 && address.address1 ? ( +

{address.address1}

) : null}

{[address.city, address.state].filter(Boolean).join(", ")} diff --git a/apps/portal/src/features/services/components/base/AddressForm.tsx b/apps/portal/src/features/services/components/base/AddressForm.tsx index 1b1d332f..0cef444e 100644 --- a/apps/portal/src/features/services/components/base/AddressForm.tsx +++ b/apps/portal/src/features/services/components/base/AddressForm.tsx @@ -45,8 +45,8 @@ const normalizeCountryValue = (value?: string | null) => { }; const DEFAULT_LABELS: Partial> = { - address1: "Address Line 1", - address2: "Address Line 2", + address1: "Building / Room (Address 1)", + address2: "Street / Block (Address 2)", city: "City", state: "State/Prefecture", postcode: "Postcode", @@ -57,8 +57,8 @@ const DEFAULT_LABELS: Partial> = { }; const DEFAULT_PLACEHOLDERS: Partial> = { - address1: "123 Main Street", - address2: "Apartment, suite, etc. (optional)", + address1: "Gramercy 201", + address2: "2-20-9 Wakabayashi", city: "Tokyo", state: "Tokyo", postcode: "100-0001", @@ -308,12 +308,12 @@ export function AddressForm({ )}

- {/* Street Address */} - {renderField("address1")} - - {/* Street Address Line 2 */} + {/* Street / Block (Address 2) */} {renderField("address2")} + {/* Building / Room (Address 1) */} + {renderField("address1")} + {/* City, State, Postal Code Row */}
=21.1.0 } - "@googlemaps/js-api-loader@1.16.8": - resolution: - { - integrity: sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==, - } - - "@googlemaps/markerclusterer@2.5.3": - resolution: - { - integrity: sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==, - } - "@grpc/grpc-js@1.14.2": resolution: { @@ -2078,27 +2063,6 @@ packages: integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==, } - "@react-google-maps/api@2.20.8": - resolution: - { - integrity: sha512-wtLYFtCGXK3qbIz1H5to3JxbosPnKsvjDKhqGylXUb859EskhzR7OpuNt0LqdLarXUtZCJTKzPn3BNaekNIahg==, - } - peerDependencies: - react: ^16.8 || ^17 || ^18 || ^19 - react-dom: ^16.8 || ^17 || ^18 || ^19 - - "@react-google-maps/infobox@2.20.0": - resolution: - { - integrity: sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ==, - } - - "@react-google-maps/marker-clusterer@2.20.0": - resolution: - { - integrity: sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw==, - } - "@scarf/scarf@1.4.0": resolution: { @@ -2519,12 +2483,6 @@ packages: integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==, } - "@types/google.maps@3.58.1": - resolution: - { - integrity: sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==, - } - "@types/http-cache-semantics@4.0.4": resolution: { @@ -4803,12 +4761,6 @@ packages: integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==, } - invariant@2.2.4: - resolution: - { - integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==, - } - ioredis@5.8.2: resolution: { @@ -5052,12 +5004,6 @@ packages: integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==, } - kdbush@4.0.2: - resolution: - { - integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==, - } - keyv@4.5.4: resolution: { @@ -5289,13 +5235,6 @@ packages: integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==, } - loose-envify@1.4.0: - resolution: - { - integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, - } - hasBin: true - lowercase-keys@3.0.0: resolution: { @@ -6915,12 +6854,6 @@ packages: babel-plugin-macros: optional: true - supercluster@8.0.1: - resolution: - { - integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==, - } - supports-color@7.2.0: resolution: { @@ -7919,13 +7852,6 @@ snapshots: "@eslint/core": 0.17.0 levn: 0.4.1 - "@googlemaps/js-api-loader@1.16.8": {} - - "@googlemaps/markerclusterer@2.5.3": - dependencies: - fast-deep-equal: 3.1.3 - supercluster: 8.0.1 - "@grpc/grpc-js@1.14.2": dependencies: "@grpc/proto-loader": 0.8.0 @@ -8636,21 +8562,6 @@ snapshots: "@protobufjs/utf8@1.1.0": {} - "@react-google-maps/api@2.20.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)": - dependencies: - "@googlemaps/js-api-loader": 1.16.8 - "@googlemaps/markerclusterer": 2.5.3 - "@react-google-maps/infobox": 2.20.0 - "@react-google-maps/marker-clusterer": 2.20.0 - "@types/google.maps": 3.58.1 - invariant: 2.2.4 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - - "@react-google-maps/infobox@2.20.0": {} - - "@react-google-maps/marker-clusterer@2.20.0": {} - "@scarf/scarf@1.4.0": {} "@sendgrid/client@8.1.6": @@ -8903,8 +8814,6 @@ snapshots: "@types/express-serve-static-core": 5.1.0 "@types/serve-static": 2.2.0 - "@types/google.maps@3.58.1": {} - "@types/http-cache-semantics@4.0.4": optional: true @@ -10412,10 +10321,6 @@ snapshots: kind-of: 6.0.3 optional: true - invariant@2.2.4: - dependencies: - loose-envify: 1.4.0 - ioredis@5.8.2: dependencies: "@ioredis/commands": 1.4.0 @@ -10541,8 +10446,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - kdbush@4.0.2: {} - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -10663,10 +10566,6 @@ snapshots: long@5.3.2: {} - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - lowercase-keys@3.0.0: optional: true @@ -11619,10 +11518,6 @@ snapshots: optionalDependencies: "@babel/core": 7.28.5 - supercluster@8.0.1: - dependencies: - kdbush: 4.0.2 - supports-color@7.2.0: dependencies: has-flag: 4.0.0