From 589b042e7cebad0a8224606e5f7c523c137d2d4b Mon Sep 17 00:00:00 2001 From: Asis Ferrer Date: Fri, 12 Jun 2026 09:51:19 +0200 Subject: [PATCH] feat(i18n): localize submit button states --- .../admin/users/[userId]/edit/page.tsx | 12 +++- .../users/_components/edit.user.form.tsx | 14 ++++- .../admin/users/_components/new.user.form.tsx | 12 +++- .../_components/reset.user.password.form.tsx | 14 ++++- src/app/(dashboard)/admin/users/new/page.tsx | 8 ++- .../assignments/[assignmentId]/edit/page.tsx | 3 + .../_components/edit.assignment.form.tsx | 8 ++- .../_components/new.assignment.form.tsx | 8 ++- src/app/(dashboard)/assignments/new/page.tsx | 9 ++- .../import/_components/import.form.tsx | 8 ++- src/app/(dashboard)/import/page.tsx | 7 ++- .../inventory/assets/[assetId]/edit/page.tsx | 3 + .../assets/_components/edit.asset.form.tsx | 8 ++- .../assets/_components/new.asset.form.tsx | 13 +++- .../(dashboard)/inventory/assets/new/page.tsx | 8 ++- .../categories/[categoryId]/edit/page.tsx | 7 ++- .../_components/edit.category.form.tsx | 8 ++- .../_components/new.category.form.tsx | 12 +++- .../inventory/categories/new/page.tsx | 8 ++- .../inventory/items/[itemId]/edit/page.tsx | 8 ++- .../items/_components/new.item.form.tsx | 8 ++- .../items/_components/update.item.form.tsx | 8 ++- .../(dashboard)/inventory/items/new/page.tsx | 7 ++- .../recipients/[recipientId]/edit/page.tsx | 8 ++- .../recipients/_components/recipient.form.tsx | 8 ++- src/app/(dashboard)/recipients/new/page.tsx | 11 +++- src/components/forms/submitButton.tsx | 13 ++-- src/i18n/dictionaries/en.ts | 5 ++ src/i18n/dictionaries/es.ts | 5 ++ .../components/forms/submitButton.test.ts | 60 +++++++++++++++++++ tests/unit/i18n/dictionaries.test.ts | 12 ++++ 31 files changed, 286 insertions(+), 37 deletions(-) create mode 100644 tests/unit/components/forms/submitButton.test.ts diff --git a/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx b/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx index 6c960a5..754f197 100644 --- a/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx +++ b/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx @@ -1,5 +1,6 @@ import { notFound } from "next/navigation" +import { getI18n } from "@/i18n/server" import { getUserProfileById } from "@/services/user.service" import EditUserForm from "../../_components/edit.user.form" @@ -12,6 +13,7 @@ export default async function EditUserPage({ }) { const { userId } = await params const user = await getUserProfileById(userId) + const { dictionary } = await getI18n() if (!user) { notFound() @@ -22,10 +24,16 @@ export default async function EditUserPage({

Edit User

- +

Reset password

- +
) diff --git a/src/app/(dashboard)/admin/users/_components/edit.user.form.tsx b/src/app/(dashboard)/admin/users/_components/edit.user.form.tsx index 2b7ce4c..f8b46df 100644 --- a/src/app/(dashboard)/admin/users/_components/edit.user.form.tsx +++ b/src/app/(dashboard)/admin/users/_components/edit.user.form.tsx @@ -6,14 +6,23 @@ import type { UseFormRegisterReturn } from "react-hook-form" import { useForm } from "react-hook-form" import { toast } from "sonner" import { updateUserAction } from "@/actions/user.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type UpdateUserFormType, updateUserSchema, } from "@/schemas/user.schema" import type { UserWithoutPassword } from "@/services/user.service" -export default function EditUserForm({ user }: { user: UserWithoutPassword }) { +export default function EditUserForm({ + submitButtonCopy, + user, +}: { + submitButtonCopy: SubmitButtonCopy + user: UserWithoutPassword +}) { const router = useRouter() const { register, @@ -99,6 +108,7 @@ export default function EditUserForm({ user }: { user: UserWithoutPassword }) { Active user diff --git a/src/app/(dashboard)/admin/users/_components/new.user.form.tsx b/src/app/(dashboard)/admin/users/_components/new.user.form.tsx index 111145c..b33b608 100644 --- a/src/app/(dashboard)/admin/users/_components/new.user.form.tsx +++ b/src/app/(dashboard)/admin/users/_components/new.user.form.tsx @@ -6,13 +6,20 @@ import type { UseFormRegisterReturn } from "react-hook-form" import { useForm } from "react-hook-form" import { toast } from "sonner" import { createUserAction } from "@/actions/user.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type CreateUserFormType, createUserSchema, } from "@/schemas/user.schema" -export default function NewUserForm() { +export default function NewUserForm({ + submitButtonCopy, +}: { + submitButtonCopy: SubmitButtonCopy +}) { const router = useRouter() const { register, @@ -83,6 +90,7 @@ export default function NewUserForm() { /> diff --git a/src/app/(dashboard)/admin/users/_components/reset.user.password.form.tsx b/src/app/(dashboard)/admin/users/_components/reset.user.password.form.tsx index 5ca5d56..7cd1050 100644 --- a/src/app/(dashboard)/admin/users/_components/reset.user.password.form.tsx +++ b/src/app/(dashboard)/admin/users/_components/reset.user.password.form.tsx @@ -4,13 +4,22 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { toast } from "sonner" import { resetUserPasswordAction } from "@/actions/user.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type ResetUserPasswordFormType, resetUserPasswordSchema, } from "@/schemas/user.schema" -export default function ResetUserPasswordForm({ userId }: { userId: string }) { +export default function ResetUserPasswordForm({ + submitButtonCopy, + userId, +}: { + submitButtonCopy: SubmitButtonCopy + userId: string +}) { const { register, handleSubmit, @@ -65,6 +74,7 @@ export default function ResetUserPasswordForm({ userId }: { userId: string }) { )} diff --git a/src/app/(dashboard)/admin/users/new/page.tsx b/src/app/(dashboard)/admin/users/new/page.tsx index 2083520..d132df1 100644 --- a/src/app/(dashboard)/admin/users/new/page.tsx +++ b/src/app/(dashboard)/admin/users/new/page.tsx @@ -1,12 +1,16 @@ +import { getI18n } from "@/i18n/server" + import NewUserForm from "../_components/new.user.form" -export default function NewUserPage() { +export default async function NewUserPage() { + const { dictionary } = await getI18n() + return (

New User

- +
) } diff --git a/src/app/(dashboard)/assignments/[assignmentId]/edit/page.tsx b/src/app/(dashboard)/assignments/[assignmentId]/edit/page.tsx index 6604092..9549158 100644 --- a/src/app/(dashboard)/assignments/[assignmentId]/edit/page.tsx +++ b/src/app/(dashboard)/assignments/[assignmentId]/edit/page.tsx @@ -1,3 +1,4 @@ +import { getI18n } from "@/i18n/server" import type { UpdateAssignmentFormType } from "@/schemas/assignment.schema" import { AssetService } from "@/services/asset.service" import { AssignmentService } from "@/services/assignment.service" @@ -16,6 +17,7 @@ export default async function EditAssignmentPage({ const recipients = await RecipientService.findAll() const items = await ItemService.findAllWithStock() const assets = await AssetService.findAll() + const { dictionary } = await getI18n() if (!assignment) { return
Assignment not found
@@ -35,6 +37,7 @@ export default async function EditAssignmentPage({ items={items} assets={assets} initialData={assignment as UpdateAssignmentFormType} + submitButtonCopy={dictionary.common.submitButton} /> ) diff --git a/src/app/(dashboard)/assignments/_components/edit.assignment.form.tsx b/src/app/(dashboard)/assignments/_components/edit.assignment.form.tsx index 0707874..3c4f841 100644 --- a/src/app/(dashboard)/assignments/_components/edit.assignment.form.tsx +++ b/src/app/(dashboard)/assignments/_components/edit.assignment.form.tsx @@ -5,7 +5,10 @@ import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { toast } from "sonner" import { updateAssignment } from "@/actions/assignment.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type UpdateAssignmentFormType, updateAssignmentSchema, @@ -17,6 +20,7 @@ interface Props { items: Item[] assets: Asset[] initialData: UpdateAssignmentFormType + submitButtonCopy: SubmitButtonCopy } export default function EditAssignmentForm({ @@ -24,6 +28,7 @@ export default function EditAssignmentForm({ items, assets, initialData, + submitButtonCopy, }: Props) { const router = useRouter() @@ -149,6 +154,7 @@ export default function EditAssignmentForm({ )} 0 && !assetId)} diff --git a/src/app/(dashboard)/assignments/_components/new.assignment.form.tsx b/src/app/(dashboard)/assignments/_components/new.assignment.form.tsx index 9da973a..dee973e 100644 --- a/src/app/(dashboard)/assignments/_components/new.assignment.form.tsx +++ b/src/app/(dashboard)/assignments/_components/new.assignment.form.tsx @@ -6,7 +6,10 @@ import { useMemo } from "react" import { useForm } from "react-hook-form" import { toast } from "sonner" import { createAssignment } from "@/actions/assignment.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type CreateAssignmentFormType, createAssignmentSchema, @@ -17,12 +20,14 @@ interface Props { recipients: Recipient[] items: Item[] assets: Asset[] + submitButtonCopy: SubmitButtonCopy } export default function CreateAssignmentForm({ recipients, items, assets, + submitButtonCopy, }: Props) { const router = useRouter() @@ -156,6 +161,7 @@ export default function CreateAssignmentForm({ )} 0 && !assetId)} diff --git a/src/app/(dashboard)/assignments/new/page.tsx b/src/app/(dashboard)/assignments/new/page.tsx index a5fb924..fd76a94 100644 --- a/src/app/(dashboard)/assignments/new/page.tsx +++ b/src/app/(dashboard)/assignments/new/page.tsx @@ -1,3 +1,4 @@ +import { getI18n } from "@/i18n/server" import { AssetService } from "@/services/asset.service" import { ItemService } from "@/services/item.service" import { RecipientService } from "@/services/recipient.service" @@ -8,8 +9,14 @@ export default async function NewAssignmentPage() { const recipients = await RecipientService.findAll() const items = await ItemService.findAllWithStock() const assets = await AssetService.findAllAvailable() + const { dictionary } = await getI18n() return ( - + ) } diff --git a/src/app/(dashboard)/import/_components/import.form.tsx b/src/app/(dashboard)/import/_components/import.form.tsx index 042e083..4d895c8 100644 --- a/src/app/(dashboard)/import/_components/import.form.tsx +++ b/src/app/(dashboard)/import/_components/import.form.tsx @@ -6,14 +6,19 @@ import type { ChangeEvent } from "react" import { useForm } from "react-hook-form" import { toast } from "sonner" import { importItems } from "@/actions/import.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type ImportFormType, importSchema } from "@/schemas/import.schema" import type { CategorySummary } from "@/types" export default function ImportForm({ categories, + submitButtonCopy, }: { categories: CategorySummary[] + submitButtonCopy: SubmitButtonCopy }) { const router = useRouter() @@ -95,6 +100,7 @@ export default function ImportForm({ )}
@@ -30,7 +32,10 @@ export default async function ImportPage() {
- + ) } diff --git a/src/app/(dashboard)/inventory/assets/[assetId]/edit/page.tsx b/src/app/(dashboard)/inventory/assets/[assetId]/edit/page.tsx index 40444da..1252912 100644 --- a/src/app/(dashboard)/inventory/assets/[assetId]/edit/page.tsx +++ b/src/app/(dashboard)/inventory/assets/[assetId]/edit/page.tsx @@ -1,5 +1,6 @@ "use server" +import { getI18n } from "@/i18n/server" import { AssetService } from "@/services/asset.service" import { ItemService } from "@/services/item.service" import { RecipientService } from "@/services/recipient.service" @@ -16,6 +17,7 @@ export default async function EditAssetPage({ const items = await ItemService.findAll() const recipients = await RecipientService.findAll() const asset = await AssetService.findById(assetId) + const { dictionary } = await getI18n() if (!asset) { return
Asset not found
@@ -30,6 +32,7 @@ export default async function EditAssetPage({ items={items} recipients={recipients} asset={asset as unknown as AssetWithAssignment} + submitButtonCopy={dictionary.common.submitButton} /> ) diff --git a/src/app/(dashboard)/inventory/assets/_components/edit.asset.form.tsx b/src/app/(dashboard)/inventory/assets/_components/edit.asset.form.tsx index 32d88b2..10251cc 100644 --- a/src/app/(dashboard)/inventory/assets/_components/edit.asset.form.tsx +++ b/src/app/(dashboard)/inventory/assets/_components/edit.asset.form.tsx @@ -5,7 +5,10 @@ import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { toast } from "sonner" import { updateAssetAction } from "@/actions/asset.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { ITEM_STATUS } from "@/lib/constants" import { type UpdateAssetFormType, @@ -22,12 +25,14 @@ interface EditAssetFormProps { asset: AssetWithAssignment items: Item[] recipients: Recipient[] + submitButtonCopy: SubmitButtonCopy } export default function EditAssetForm({ asset, items, recipients, + submitButtonCopy, }: EditAssetFormProps) { const router = useRouter() @@ -170,6 +175,7 @@ export default function EditAssetForm({ )} diff --git a/src/app/(dashboard)/inventory/assets/_components/new.asset.form.tsx b/src/app/(dashboard)/inventory/assets/_components/new.asset.form.tsx index 0f029dc..db7ce89 100644 --- a/src/app/(dashboard)/inventory/assets/_components/new.asset.form.tsx +++ b/src/app/(dashboard)/inventory/assets/_components/new.asset.form.tsx @@ -5,7 +5,10 @@ import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { toast } from "sonner" import { createAssetAction } from "@/actions/asset.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { ITEM_STATUS } from "@/lib/constants" import { type CreateAssetFormType, @@ -16,9 +19,14 @@ import type { ItemWithoutStock, Recipient } from "@/types" interface NewAssetFormProps { items: ItemWithoutStock[] recipients: Recipient[] + submitButtonCopy: SubmitButtonCopy } -export default function NewAssetForm({ items, recipients }: NewAssetFormProps) { +export default function NewAssetForm({ + items, + recipients, + submitButtonCopy, +}: NewAssetFormProps) { const router = useRouter() const { @@ -155,6 +163,7 @@ export default function NewAssetForm({ items, recipients }: NewAssetFormProps) { )} diff --git a/src/app/(dashboard)/inventory/assets/new/page.tsx b/src/app/(dashboard)/inventory/assets/new/page.tsx index 55916ec..323d9c5 100644 --- a/src/app/(dashboard)/inventory/assets/new/page.tsx +++ b/src/app/(dashboard)/inventory/assets/new/page.tsx @@ -1,5 +1,6 @@ "use server" +import { getI18n } from "@/i18n/server" import { ItemService } from "@/services/item.service" import { RecipientService } from "@/services/recipient.service" @@ -8,13 +9,18 @@ import NewAssetForm from "../_components/new.asset.form" export default async function NewAssetPage() { const items = await ItemService.findAllAssignable() const recipients = await RecipientService.findAll() + const { dictionary } = await getI18n() return (

New Asset

- +
) } diff --git a/src/app/(dashboard)/inventory/categories/[categoryId]/edit/page.tsx b/src/app/(dashboard)/inventory/categories/[categoryId]/edit/page.tsx index fb2713e..ea2812e 100644 --- a/src/app/(dashboard)/inventory/categories/[categoryId]/edit/page.tsx +++ b/src/app/(dashboard)/inventory/categories/[categoryId]/edit/page.tsx @@ -1,5 +1,6 @@ import { notFound } from "next/navigation" +import { getI18n } from "@/i18n/server" import { CategoryService } from "@/services/category.service" import EditCategoryForm from "../../_components/edit.category.form" @@ -11,6 +12,7 @@ export default async function EditCategoryPage({ }) { const { categoryId } = await params const category = await CategoryService.findById(categoryId) + const { dictionary } = await getI18n() if (!category) { notFound() @@ -21,7 +23,10 @@ export default async function EditCategoryPage({

Edit Category

- + ) } diff --git a/src/app/(dashboard)/inventory/categories/_components/edit.category.form.tsx b/src/app/(dashboard)/inventory/categories/_components/edit.category.form.tsx index f9489db..14970f0 100644 --- a/src/app/(dashboard)/inventory/categories/_components/edit.category.form.tsx +++ b/src/app/(dashboard)/inventory/categories/_components/edit.category.form.tsx @@ -5,7 +5,10 @@ import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { toast } from "sonner" import { updateCategoryAction } from "@/actions/category.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type UpdateCategoryFormType, updateCategorySchema, @@ -14,8 +17,10 @@ import type { CategorySummary } from "@/types" export default function EditCategoryForm({ category, + submitButtonCopy, }: { category: CategorySummary + submitButtonCopy: SubmitButtonCopy }) { const router = useRouter() @@ -73,6 +78,7 @@ export default function EditCategoryForm({ {errors.name &&

{errors.name.message}

} diff --git a/src/app/(dashboard)/inventory/categories/_components/new.category.form.tsx b/src/app/(dashboard)/inventory/categories/_components/new.category.form.tsx index c9dea5d..372d684 100644 --- a/src/app/(dashboard)/inventory/categories/_components/new.category.form.tsx +++ b/src/app/(dashboard)/inventory/categories/_components/new.category.form.tsx @@ -5,13 +5,20 @@ import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { toast } from "sonner" import { createCategoryAction } from "@/actions/category.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type CreateCategoryFormType, createCategorySchema, } from "@/schemas/category.schema" -export default function NewCategoryForm() { +export default function NewCategoryForm({ + submitButtonCopy, +}: { + submitButtonCopy: SubmitButtonCopy +}) { const router = useRouter() const { @@ -63,6 +70,7 @@ export default function NewCategoryForm() { {errors.name &&

{errors.name.message}

} diff --git a/src/app/(dashboard)/inventory/categories/new/page.tsx b/src/app/(dashboard)/inventory/categories/new/page.tsx index 920d5ac..fe02f44 100644 --- a/src/app/(dashboard)/inventory/categories/new/page.tsx +++ b/src/app/(dashboard)/inventory/categories/new/page.tsx @@ -1,12 +1,16 @@ +import { getI18n } from "@/i18n/server" + import NewCategoryForm from "../_components/new.category.form" -export default function NewCategoryPage() { +export default async function NewCategoryPage() { + const { dictionary } = await getI18n() + return (

New Category

- +
) } diff --git a/src/app/(dashboard)/inventory/items/[itemId]/edit/page.tsx b/src/app/(dashboard)/inventory/items/[itemId]/edit/page.tsx index 4814a2c..616260c 100644 --- a/src/app/(dashboard)/inventory/items/[itemId]/edit/page.tsx +++ b/src/app/(dashboard)/inventory/items/[itemId]/edit/page.tsx @@ -1,3 +1,4 @@ +import { getI18n } from "@/i18n/server" import { CategoryService } from "@/services/category.service" import { ItemService } from "@/services/item.service" @@ -11,6 +12,7 @@ export default async function AddItem({ const { itemId } = await params const categories = await CategoryService.findAll() const item = await ItemService.findByIdWithAssetCount(itemId) + const { dictionary } = await getI18n() if (!item) { return
Item not found
@@ -26,7 +28,11 @@ export default async function AddItem({

Edit Item

- + ) } diff --git a/src/app/(dashboard)/inventory/items/_components/new.item.form.tsx b/src/app/(dashboard)/inventory/items/_components/new.item.form.tsx index 6ae9f53..284a312 100644 --- a/src/app/(dashboard)/inventory/items/_components/new.item.form.tsx +++ b/src/app/(dashboard)/inventory/items/_components/new.item.form.tsx @@ -5,7 +5,10 @@ import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { toast } from "sonner" import { createItemAction } from "@/actions/item.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type CreateItemFormType, createItemSchema, @@ -14,8 +17,10 @@ import type { CategorySummary } from "@/types" export default function NewItemForm({ categories, + submitButtonCopy, }: { categories: CategorySummary[] + submitButtonCopy: SubmitButtonCopy }) { const router = useRouter() @@ -115,6 +120,7 @@ export default function NewItemForm({ {errors?.stock &&

{errors.stock.message}

} diff --git a/src/app/(dashboard)/inventory/items/_components/update.item.form.tsx b/src/app/(dashboard)/inventory/items/_components/update.item.form.tsx index 11a3a8e..9c12cbe 100644 --- a/src/app/(dashboard)/inventory/items/_components/update.item.form.tsx +++ b/src/app/(dashboard)/inventory/items/_components/update.item.form.tsx @@ -5,7 +5,10 @@ import { useRouter } from "next/navigation" import { useForm } from "react-hook-form" import { toast } from "sonner" import { updateItemAction } from "@/actions/item.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { type UpdateItemFormType, updateItemSchema, @@ -15,9 +18,11 @@ import type { CategorySummary, ItemWithAssetCount } from "@/types" export default function UpdateItemForm({ categories, item, + submitButtonCopy, }: { categories: CategorySummary[] item: ItemWithAssetCount + submitButtonCopy: SubmitButtonCopy }) { const router = useRouter() @@ -130,6 +135,7 @@ export default function UpdateItemForm({ {errors?.stock &&

{errors.stock.message}

} diff --git a/src/app/(dashboard)/inventory/items/new/page.tsx b/src/app/(dashboard)/inventory/items/new/page.tsx index 1f7e6f8..f53aa3b 100644 --- a/src/app/(dashboard)/inventory/items/new/page.tsx +++ b/src/app/(dashboard)/inventory/items/new/page.tsx @@ -1,16 +1,21 @@ +import { getI18n } from "@/i18n/server" import { CategoryService } from "@/services/category.service" import NewItemForm from "../_components/new.item.form" export default async function NewItemPage() { const categories = await CategoryService.findAll() + const { dictionary } = await getI18n() return (

New Item

- +
) } diff --git a/src/app/(dashboard)/recipients/[recipientId]/edit/page.tsx b/src/app/(dashboard)/recipients/[recipientId]/edit/page.tsx index 4fe3378..81f76dd 100644 --- a/src/app/(dashboard)/recipients/[recipientId]/edit/page.tsx +++ b/src/app/(dashboard)/recipients/[recipientId]/edit/page.tsx @@ -1,3 +1,4 @@ +import { getI18n } from "@/i18n/server" import { RecipientService } from "@/services/recipient.service" import RecipientForm from "../../_components/recipient.form" @@ -9,6 +10,7 @@ export default async function RecipientEditPage({ }) { const { recipientId } = await params const recipient = await RecipientService.findById(recipientId) + const { dictionary } = await getI18n() if (!recipient) { return
Recipient not found
@@ -19,7 +21,11 @@ export default async function RecipientEditPage({

Edit Recipient

- + ) } diff --git a/src/app/(dashboard)/recipients/_components/recipient.form.tsx b/src/app/(dashboard)/recipients/_components/recipient.form.tsx index 20e0b75..bdd89de 100644 --- a/src/app/(dashboard)/recipients/_components/recipient.form.tsx +++ b/src/app/(dashboard)/recipients/_components/recipient.form.tsx @@ -8,7 +8,10 @@ import { createNewRecipient, updateRecipient, } from "@/actions/recipient.actions" -import { SubmitButton } from "@/components/forms/submitButton" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" import { RECIPIENT_DEPARTMENTS } from "@/lib/constants" import { type CreateRecipientFormType, @@ -20,11 +23,13 @@ import type { Recipient } from "@/types" interface RecipientFormProps { initialData?: Recipient mode?: "create" | "edit" + submitButtonCopy: SubmitButtonCopy } export default function RecipientForm({ initialData, mode = "create", + submitButtonCopy, }: RecipientFormProps) { const router = useRouter() @@ -166,6 +171,7 @@ export default function RecipientForm({ {errors?.phone &&

{errors.phone.message}

} diff --git a/src/app/(dashboard)/recipients/new/page.tsx b/src/app/(dashboard)/recipients/new/page.tsx index 931c1c3..b6ea5ad 100644 --- a/src/app/(dashboard)/recipients/new/page.tsx +++ b/src/app/(dashboard)/recipients/new/page.tsx @@ -1,12 +1,19 @@ +import { getI18n } from "@/i18n/server" + import RecipientForm from "../_components/recipient.form" -export default function NewRecipientPage() { +export default async function NewRecipientPage() { + const { dictionary } = await getI18n() + return (

Add Recipient

- +
) } diff --git a/src/components/forms/submitButton.tsx b/src/components/forms/submitButton.tsx index fc7ecaf..8f76e6b 100644 --- a/src/components/forms/submitButton.tsx +++ b/src/components/forms/submitButton.tsx @@ -1,8 +1,12 @@ import { Check, Loader2 } from "lucide-react" import { Button } from "@/components/ui/button" +import type { Dictionary } from "@/i18n/dictionaries" + +export type SubmitButtonCopy = Dictionary["common"]["submitButton"] interface SubmitButtonProps { + copy: SubmitButtonCopy isSubmitting: boolean isSubmitSuccessful: boolean isSubmitError?: boolean @@ -11,10 +15,11 @@ interface SubmitButtonProps { } export function SubmitButton({ + copy, isSubmitting, isSubmitSuccessful, disabled, - children = "Submit", + children, ...props }: SubmitButtonProps) { return ( @@ -24,16 +29,16 @@ export function SubmitButton({ disabled={disabled || isSubmitting || isSubmitSuccessful} {...props} > - {!isSubmitting && !isSubmitSuccessful && children} + {!isSubmitting && !isSubmitSuccessful && (children ?? copy.defaultLabel)} {isSubmitting && ( <> - Processing + {copy.processing} )} {isSubmitSuccessful && ( <> - Success + {copy.success} )} diff --git a/src/i18n/dictionaries/en.ts b/src/i18n/dictionaries/en.ts index 93a7a74..171170f 100644 --- a/src/i18n/dictionaries/en.ts +++ b/src/i18n/dictionaries/en.ts @@ -18,6 +18,11 @@ export const en = { previous: "Previous", next: "Next", }, + submitButton: { + defaultLabel: "Submit", + processing: "Processing", + success: "Success", + }, forbidden: { title: "Access denied", description: "You do not have permission to access this section.", diff --git a/src/i18n/dictionaries/es.ts b/src/i18n/dictionaries/es.ts index 5c45777..113cf36 100644 --- a/src/i18n/dictionaries/es.ts +++ b/src/i18n/dictionaries/es.ts @@ -20,6 +20,11 @@ export const es = { previous: "Anterior", next: "Siguiente", }, + submitButton: { + defaultLabel: "Enviar", + processing: "Procesando", + success: "Completado", + }, forbidden: { title: "Acceso denegado", description: "No tienes permisos para acceder a esta sección.", diff --git a/tests/unit/components/forms/submitButton.test.ts b/tests/unit/components/forms/submitButton.test.ts new file mode 100644 index 0000000..b7d67b0 --- /dev/null +++ b/tests/unit/components/forms/submitButton.test.ts @@ -0,0 +1,60 @@ +import { createElement } from "react" +import { renderToStaticMarkup } from "react-dom/server" +import { describe, expect, it } from "vitest" + +import { SubmitButton } from "@/components/forms/submitButton" + +const submitButtonCopy = { + defaultLabel: "Enviar", + processing: "Procesando", + success: "Completado", +} + +function renderSubmitButton( + props: Partial[0]> = {}, +) { + return renderToStaticMarkup( + createElement(SubmitButton, { + copy: submitButtonCopy, + isSubmitting: false, + isSubmitSuccessful: false, + ...props, + }), + ) +} + +describe("SubmitButton", () => { + it("uses localized default copy when children are absent", () => { + const html = renderSubmitButton() + + expect(html).toContain(submitButtonCopy.defaultLabel) + expect(html).not.toContain("Submit") + }) + + it("uses localized processing copy", () => { + const html = renderSubmitButton({ isSubmitting: true }) + + expect(html).toContain(submitButtonCopy.processing) + expect(html).not.toContain("Processing") + }) + + it("uses localized success copy", () => { + const html = renderSubmitButton({ isSubmitSuccessful: true }) + + expect(html).toContain(submitButtonCopy.success) + expect(html).not.toContain("Success") + }) + + it("keeps caller-provided children as the idle label only", () => { + const idleHtml = renderSubmitButton({ children: "Create Item" }) + const processingHtml = renderSubmitButton({ + children: "Create Item", + isSubmitting: true, + }) + + expect(idleHtml).toContain("Create Item") + expect(idleHtml).not.toContain(submitButtonCopy.defaultLabel) + expect(processingHtml).toContain(submitButtonCopy.processing) + expect(processingHtml).not.toContain("Create Item") + }) +}) diff --git a/tests/unit/i18n/dictionaries.test.ts b/tests/unit/i18n/dictionaries.test.ts index 4472e2f..06027fb 100644 --- a/tests/unit/i18n/dictionaries.test.ts +++ b/tests/unit/i18n/dictionaries.test.ts @@ -139,11 +139,23 @@ describe("i18n dictionaries", () => { next: "Siguiente", }) + expect(getDictionary("en").common.submitButton).toEqual({ + defaultLabel: "Submit", + processing: "Processing", + success: "Success", + }) + expect(getDictionary("en").common.forbidden).toEqual({ title: "Access denied", description: "You do not have permission to access this section.", homeLink: "Back to home", }) + expect(getDictionary("es").common.submitButton).toEqual({ + defaultLabel: "Enviar", + processing: "Procesando", + success: "Completado", + }) + expect(getDictionary("es").common.forbidden).toEqual({ title: "Acceso denegado", description: "No tienes permisos para acceder a esta sección.",