feat(i18n): localize submit button states

This commit is contained in:
2026-06-12 09:51:19 +02:00
parent 2fa6611719
commit 589b042e7c
31 changed files with 286 additions and 37 deletions
@@ -1,5 +1,6 @@
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { getI18n } from "@/i18n/server"
import { getUserProfileById } from "@/services/user.service" import { getUserProfileById } from "@/services/user.service"
import EditUserForm from "../../_components/edit.user.form" import EditUserForm from "../../_components/edit.user.form"
@@ -12,6 +13,7 @@ export default async function EditUserPage({
}) { }) {
const { userId } = await params const { userId } = await params
const user = await getUserProfileById(userId) const user = await getUserProfileById(userId)
const { dictionary } = await getI18n()
if (!user) { if (!user) {
notFound() notFound()
@@ -22,10 +24,16 @@ export default async function EditUserPage({
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">Edit User</h1> <h1 className="text-2xl font-bold">Edit User</h1>
</div> </div>
<EditUserForm user={user} /> <EditUserForm
submitButtonCopy={dictionary.common.submitButton}
user={user}
/>
<section className="flex flex-col gap-4 border-t pt-6"> <section className="flex flex-col gap-4 border-t pt-6">
<h2 className="text-xl font-semibold">Reset password</h2> <h2 className="text-xl font-semibold">Reset password</h2>
<ResetUserPasswordForm userId={user.id} /> <ResetUserPasswordForm
submitButtonCopy={dictionary.common.submitButton}
userId={user.id}
/>
</section> </section>
</div> </div>
) )
@@ -6,14 +6,23 @@ import type { UseFormRegisterReturn } from "react-hook-form"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { updateUserAction } from "@/actions/user.actions" import { updateUserAction } from "@/actions/user.actions"
import { SubmitButton } from "@/components/forms/submitButton" import {
SubmitButton,
type SubmitButtonCopy,
} from "@/components/forms/submitButton"
import { import {
type UpdateUserFormType, type UpdateUserFormType,
updateUserSchema, updateUserSchema,
} from "@/schemas/user.schema" } from "@/schemas/user.schema"
import type { UserWithoutPassword } from "@/services/user.service" 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 router = useRouter()
const { const {
register, register,
@@ -99,6 +108,7 @@ export default function EditUserForm({ user }: { user: UserWithoutPassword }) {
Active user Active user
</label> </label>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
@@ -6,13 +6,20 @@ import type { UseFormRegisterReturn } from "react-hook-form"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { createUserAction } from "@/actions/user.actions" import { createUserAction } from "@/actions/user.actions"
import { SubmitButton } from "@/components/forms/submitButton" import {
SubmitButton,
type SubmitButtonCopy,
} from "@/components/forms/submitButton"
import { import {
type CreateUserFormType, type CreateUserFormType,
createUserSchema, createUserSchema,
} from "@/schemas/user.schema" } from "@/schemas/user.schema"
export default function NewUserForm() { export default function NewUserForm({
submitButtonCopy,
}: {
submitButtonCopy: SubmitButtonCopy
}) {
const router = useRouter() const router = useRouter()
const { const {
register, register,
@@ -83,6 +90,7 @@ export default function NewUserForm() {
/> />
<RoleSelect register={register("role")} /> <RoleSelect register={register("role")} />
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
@@ -4,13 +4,22 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { resetUserPasswordAction } from "@/actions/user.actions" import { resetUserPasswordAction } from "@/actions/user.actions"
import { SubmitButton } from "@/components/forms/submitButton" import {
SubmitButton,
type SubmitButtonCopy,
} from "@/components/forms/submitButton"
import { import {
type ResetUserPasswordFormType, type ResetUserPasswordFormType,
resetUserPasswordSchema, resetUserPasswordSchema,
} from "@/schemas/user.schema" } from "@/schemas/user.schema"
export default function ResetUserPasswordForm({ userId }: { userId: string }) { export default function ResetUserPasswordForm({
submitButtonCopy,
userId,
}: {
submitButtonCopy: SubmitButtonCopy
userId: string
}) {
const { const {
register, register,
handleSubmit, handleSubmit,
@@ -65,6 +74,7 @@ export default function ResetUserPasswordForm({ userId }: { userId: string }) {
)} )}
</div> </div>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
+6 -2
View File
@@ -1,12 +1,16 @@
import { getI18n } from "@/i18n/server"
import NewUserForm from "../_components/new.user.form" import NewUserForm from "../_components/new.user.form"
export default function NewUserPage() { export default async function NewUserPage() {
const { dictionary } = await getI18n()
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">New User</h1> <h1 className="text-2xl font-bold">New User</h1>
</div> </div>
<NewUserForm /> <NewUserForm submitButtonCopy={dictionary.common.submitButton} />
</div> </div>
) )
} }
@@ -1,3 +1,4 @@
import { getI18n } from "@/i18n/server"
import type { UpdateAssignmentFormType } from "@/schemas/assignment.schema" import type { UpdateAssignmentFormType } from "@/schemas/assignment.schema"
import { AssetService } from "@/services/asset.service" import { AssetService } from "@/services/asset.service"
import { AssignmentService } from "@/services/assignment.service" import { AssignmentService } from "@/services/assignment.service"
@@ -16,6 +17,7 @@ export default async function EditAssignmentPage({
const recipients = await RecipientService.findAll() const recipients = await RecipientService.findAll()
const items = await ItemService.findAllWithStock() const items = await ItemService.findAllWithStock()
const assets = await AssetService.findAll() const assets = await AssetService.findAll()
const { dictionary } = await getI18n()
if (!assignment) { if (!assignment) {
return <div>Assignment not found</div> return <div>Assignment not found</div>
@@ -35,6 +37,7 @@ export default async function EditAssignmentPage({
items={items} items={items}
assets={assets} assets={assets}
initialData={assignment as UpdateAssignmentFormType} initialData={assignment as UpdateAssignmentFormType}
submitButtonCopy={dictionary.common.submitButton}
/> />
</div> </div>
) )
@@ -5,7 +5,10 @@ import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { updateAssignment } from "@/actions/assignment.actions" import { updateAssignment } from "@/actions/assignment.actions"
import { SubmitButton } from "@/components/forms/submitButton" import {
SubmitButton,
type SubmitButtonCopy,
} from "@/components/forms/submitButton"
import { import {
type UpdateAssignmentFormType, type UpdateAssignmentFormType,
updateAssignmentSchema, updateAssignmentSchema,
@@ -17,6 +20,7 @@ interface Props {
items: Item[] items: Item[]
assets: Asset[] assets: Asset[]
initialData: UpdateAssignmentFormType initialData: UpdateAssignmentFormType
submitButtonCopy: SubmitButtonCopy
} }
export default function EditAssignmentForm({ export default function EditAssignmentForm({
@@ -24,6 +28,7 @@ export default function EditAssignmentForm({
items, items,
assets, assets,
initialData, initialData,
submitButtonCopy,
}: Props) { }: Props) {
const router = useRouter() const router = useRouter()
@@ -149,6 +154,7 @@ export default function EditAssignmentForm({
)} )}
</div> </div>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
disabled={!itemId || (assets.length > 0 && !assetId)} disabled={!itemId || (assets.length > 0 && !assetId)}
@@ -6,7 +6,10 @@ import { useMemo } from "react"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { createAssignment } from "@/actions/assignment.actions" import { createAssignment } from "@/actions/assignment.actions"
import { SubmitButton } from "@/components/forms/submitButton" import {
SubmitButton,
type SubmitButtonCopy,
} from "@/components/forms/submitButton"
import { import {
type CreateAssignmentFormType, type CreateAssignmentFormType,
createAssignmentSchema, createAssignmentSchema,
@@ -17,12 +20,14 @@ interface Props {
recipients: Recipient[] recipients: Recipient[]
items: Item[] items: Item[]
assets: Asset[] assets: Asset[]
submitButtonCopy: SubmitButtonCopy
} }
export default function CreateAssignmentForm({ export default function CreateAssignmentForm({
recipients, recipients,
items, items,
assets, assets,
submitButtonCopy,
}: Props) { }: Props) {
const router = useRouter() const router = useRouter()
@@ -156,6 +161,7 @@ export default function CreateAssignmentForm({
)} )}
</div> </div>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
disabled={!itemId || (itemAssets.length > 0 && !assetId)} disabled={!itemId || (itemAssets.length > 0 && !assetId)}
+8 -1
View File
@@ -1,3 +1,4 @@
import { getI18n } from "@/i18n/server"
import { AssetService } from "@/services/asset.service" import { AssetService } from "@/services/asset.service"
import { ItemService } from "@/services/item.service" import { ItemService } from "@/services/item.service"
import { RecipientService } from "@/services/recipient.service" import { RecipientService } from "@/services/recipient.service"
@@ -8,8 +9,14 @@ export default async function NewAssignmentPage() {
const recipients = await RecipientService.findAll() const recipients = await RecipientService.findAll()
const items = await ItemService.findAllWithStock() const items = await ItemService.findAllWithStock()
const assets = await AssetService.findAllAvailable() const assets = await AssetService.findAllAvailable()
const { dictionary } = await getI18n()
return ( return (
<AssignmentForm recipients={recipients} items={items} assets={assets} /> <AssignmentForm
recipients={recipients}
items={items}
assets={assets}
submitButtonCopy={dictionary.common.submitButton}
/>
) )
} }
@@ -6,14 +6,19 @@ import type { ChangeEvent } from "react"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { importItems } from "@/actions/import.actions" 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 ImportFormType, importSchema } from "@/schemas/import.schema"
import type { CategorySummary } from "@/types" import type { CategorySummary } from "@/types"
export default function ImportForm({ export default function ImportForm({
categories, categories,
submitButtonCopy,
}: { }: {
categories: CategorySummary[] categories: CategorySummary[]
submitButtonCopy: SubmitButtonCopy
}) { }) {
const router = useRouter() const router = useRouter()
@@ -95,6 +100,7 @@ export default function ImportForm({
)} )}
</div> </div>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
disabled={!file} disabled={!file}
+6 -1
View File
@@ -2,6 +2,7 @@ import { Download } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { getI18n } from "@/i18n/server"
import { ENVIRONMENT } from "@/lib/constants" import { ENVIRONMENT } from "@/lib/constants"
import { CategoryService } from "@/services/category.service" import { CategoryService } from "@/services/category.service"
@@ -9,6 +10,7 @@ import ImportForm from "./_components/import.form"
export default async function ImportPage() { export default async function ImportPage() {
const categories = await CategoryService.findAllWithItemsCount() const categories = await CategoryService.findAllWithItemsCount()
const { dictionary } = await getI18n()
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
@@ -30,7 +32,10 @@ export default async function ImportPage() {
</Button> </Button>
</Link> </Link>
</div> </div>
<ImportForm categories={categories} /> <ImportForm
categories={categories}
submitButtonCopy={dictionary.common.submitButton}
/>
</div> </div>
) )
} }
@@ -1,5 +1,6 @@
"use server" "use server"
import { getI18n } from "@/i18n/server"
import { AssetService } from "@/services/asset.service" import { AssetService } from "@/services/asset.service"
import { ItemService } from "@/services/item.service" import { ItemService } from "@/services/item.service"
import { RecipientService } from "@/services/recipient.service" import { RecipientService } from "@/services/recipient.service"
@@ -16,6 +17,7 @@ export default async function EditAssetPage({
const items = await ItemService.findAll() const items = await ItemService.findAll()
const recipients = await RecipientService.findAll() const recipients = await RecipientService.findAll()
const asset = await AssetService.findById(assetId) const asset = await AssetService.findById(assetId)
const { dictionary } = await getI18n()
if (!asset) { if (!asset) {
return <div>Asset not found</div> return <div>Asset not found</div>
@@ -30,6 +32,7 @@ export default async function EditAssetPage({
items={items} items={items}
recipients={recipients} recipients={recipients}
asset={asset as unknown as AssetWithAssignment} asset={asset as unknown as AssetWithAssignment}
submitButtonCopy={dictionary.common.submitButton}
/> />
</div> </div>
) )
@@ -5,7 +5,10 @@ import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { updateAssetAction } from "@/actions/asset.actions" 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 { ITEM_STATUS } from "@/lib/constants"
import { import {
type UpdateAssetFormType, type UpdateAssetFormType,
@@ -22,12 +25,14 @@ interface EditAssetFormProps {
asset: AssetWithAssignment asset: AssetWithAssignment
items: Item[] items: Item[]
recipients: Recipient[] recipients: Recipient[]
submitButtonCopy: SubmitButtonCopy
} }
export default function EditAssetForm({ export default function EditAssetForm({
asset, asset,
items, items,
recipients, recipients,
submitButtonCopy,
}: EditAssetFormProps) { }: EditAssetFormProps) {
const router = useRouter() const router = useRouter()
@@ -170,6 +175,7 @@ export default function EditAssetForm({
</div> </div>
)} )}
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
@@ -5,7 +5,10 @@ import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { createAssetAction } from "@/actions/asset.actions" 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 { ITEM_STATUS } from "@/lib/constants"
import { import {
type CreateAssetFormType, type CreateAssetFormType,
@@ -16,9 +19,14 @@ import type { ItemWithoutStock, Recipient } from "@/types"
interface NewAssetFormProps { interface NewAssetFormProps {
items: ItemWithoutStock[] items: ItemWithoutStock[]
recipients: Recipient[] recipients: Recipient[]
submitButtonCopy: SubmitButtonCopy
} }
export default function NewAssetForm({ items, recipients }: NewAssetFormProps) { export default function NewAssetForm({
items,
recipients,
submitButtonCopy,
}: NewAssetFormProps) {
const router = useRouter() const router = useRouter()
const { const {
@@ -155,6 +163,7 @@ export default function NewAssetForm({ items, recipients }: NewAssetFormProps) {
</div> </div>
)} )}
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
@@ -1,5 +1,6 @@
"use server" "use server"
import { getI18n } from "@/i18n/server"
import { ItemService } from "@/services/item.service" import { ItemService } from "@/services/item.service"
import { RecipientService } from "@/services/recipient.service" import { RecipientService } from "@/services/recipient.service"
@@ -8,13 +9,18 @@ import NewAssetForm from "../_components/new.asset.form"
export default async function NewAssetPage() { export default async function NewAssetPage() {
const items = await ItemService.findAllAssignable() const items = await ItemService.findAllAssignable()
const recipients = await RecipientService.findAll() const recipients = await RecipientService.findAll()
const { dictionary } = await getI18n()
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">New Asset</h1> <h1 className="text-2xl font-bold">New Asset</h1>
</div> </div>
<NewAssetForm items={items} recipients={recipients} /> <NewAssetForm
items={items}
recipients={recipients}
submitButtonCopy={dictionary.common.submitButton}
/>
</div> </div>
) )
} }
@@ -1,5 +1,6 @@
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { getI18n } from "@/i18n/server"
import { CategoryService } from "@/services/category.service" import { CategoryService } from "@/services/category.service"
import EditCategoryForm from "../../_components/edit.category.form" import EditCategoryForm from "../../_components/edit.category.form"
@@ -11,6 +12,7 @@ export default async function EditCategoryPage({
}) { }) {
const { categoryId } = await params const { categoryId } = await params
const category = await CategoryService.findById(categoryId) const category = await CategoryService.findById(categoryId)
const { dictionary } = await getI18n()
if (!category) { if (!category) {
notFound() notFound()
@@ -21,7 +23,10 @@ export default async function EditCategoryPage({
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">Edit Category</h1> <h1 className="text-2xl font-bold">Edit Category</h1>
</div> </div>
<EditCategoryForm category={category} /> <EditCategoryForm
category={category}
submitButtonCopy={dictionary.common.submitButton}
/>
</div> </div>
) )
} }
@@ -5,7 +5,10 @@ import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { updateCategoryAction } from "@/actions/category.actions" import { updateCategoryAction } from "@/actions/category.actions"
import { SubmitButton } from "@/components/forms/submitButton" import {
SubmitButton,
type SubmitButtonCopy,
} from "@/components/forms/submitButton"
import { import {
type UpdateCategoryFormType, type UpdateCategoryFormType,
updateCategorySchema, updateCategorySchema,
@@ -14,8 +17,10 @@ import type { CategorySummary } from "@/types"
export default function EditCategoryForm({ export default function EditCategoryForm({
category, category,
submitButtonCopy,
}: { }: {
category: CategorySummary category: CategorySummary
submitButtonCopy: SubmitButtonCopy
}) { }) {
const router = useRouter() const router = useRouter()
@@ -73,6 +78,7 @@ export default function EditCategoryForm({
{errors.name && <p className="text-error">{errors.name.message}</p>} {errors.name && <p className="text-error">{errors.name.message}</p>}
</div> </div>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
@@ -5,13 +5,20 @@ import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { createCategoryAction } from "@/actions/category.actions" import { createCategoryAction } from "@/actions/category.actions"
import { SubmitButton } from "@/components/forms/submitButton" import {
SubmitButton,
type SubmitButtonCopy,
} from "@/components/forms/submitButton"
import { import {
type CreateCategoryFormType, type CreateCategoryFormType,
createCategorySchema, createCategorySchema,
} from "@/schemas/category.schema" } from "@/schemas/category.schema"
export default function NewCategoryForm() { export default function NewCategoryForm({
submitButtonCopy,
}: {
submitButtonCopy: SubmitButtonCopy
}) {
const router = useRouter() const router = useRouter()
const { const {
@@ -63,6 +70,7 @@ export default function NewCategoryForm() {
{errors.name && <p className="text-error">{errors.name.message}</p>} {errors.name && <p className="text-error">{errors.name.message}</p>}
</div> </div>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
@@ -1,12 +1,16 @@
import { getI18n } from "@/i18n/server"
import NewCategoryForm from "../_components/new.category.form" import NewCategoryForm from "../_components/new.category.form"
export default function NewCategoryPage() { export default async function NewCategoryPage() {
const { dictionary } = await getI18n()
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">New Category</h1> <h1 className="text-2xl font-bold">New Category</h1>
</div> </div>
<NewCategoryForm /> <NewCategoryForm submitButtonCopy={dictionary.common.submitButton} />
</div> </div>
) )
} }
@@ -1,3 +1,4 @@
import { getI18n } from "@/i18n/server"
import { CategoryService } from "@/services/category.service" import { CategoryService } from "@/services/category.service"
import { ItemService } from "@/services/item.service" import { ItemService } from "@/services/item.service"
@@ -11,6 +12,7 @@ export default async function AddItem({
const { itemId } = await params const { itemId } = await params
const categories = await CategoryService.findAll() const categories = await CategoryService.findAll()
const item = await ItemService.findByIdWithAssetCount(itemId) const item = await ItemService.findByIdWithAssetCount(itemId)
const { dictionary } = await getI18n()
if (!item) { if (!item) {
return <div>Item not found</div> return <div>Item not found</div>
@@ -26,7 +28,11 @@ export default async function AddItem({
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">Edit Item</h1> <h1 className="text-2xl font-bold">Edit Item</h1>
</div> </div>
<UpdateItemForm categories={categories} item={item} /> <UpdateItemForm
categories={categories}
item={item}
submitButtonCopy={dictionary.common.submitButton}
/>
</div> </div>
) )
} }
@@ -5,7 +5,10 @@ import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { createItemAction } from "@/actions/item.actions" import { createItemAction } from "@/actions/item.actions"
import { SubmitButton } from "@/components/forms/submitButton" import {
SubmitButton,
type SubmitButtonCopy,
} from "@/components/forms/submitButton"
import { import {
type CreateItemFormType, type CreateItemFormType,
createItemSchema, createItemSchema,
@@ -14,8 +17,10 @@ import type { CategorySummary } from "@/types"
export default function NewItemForm({ export default function NewItemForm({
categories, categories,
submitButtonCopy,
}: { }: {
categories: CategorySummary[] categories: CategorySummary[]
submitButtonCopy: SubmitButtonCopy
}) { }) {
const router = useRouter() const router = useRouter()
@@ -115,6 +120,7 @@ export default function NewItemForm({
{errors?.stock && <p className="text-error">{errors.stock.message}</p>} {errors?.stock && <p className="text-error">{errors.stock.message}</p>}
</div> </div>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
@@ -5,7 +5,10 @@ import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { toast } from "sonner" import { toast } from "sonner"
import { updateItemAction } from "@/actions/item.actions" import { updateItemAction } from "@/actions/item.actions"
import { SubmitButton } from "@/components/forms/submitButton" import {
SubmitButton,
type SubmitButtonCopy,
} from "@/components/forms/submitButton"
import { import {
type UpdateItemFormType, type UpdateItemFormType,
updateItemSchema, updateItemSchema,
@@ -15,9 +18,11 @@ import type { CategorySummary, ItemWithAssetCount } from "@/types"
export default function UpdateItemForm({ export default function UpdateItemForm({
categories, categories,
item, item,
submitButtonCopy,
}: { }: {
categories: CategorySummary[] categories: CategorySummary[]
item: ItemWithAssetCount item: ItemWithAssetCount
submitButtonCopy: SubmitButtonCopy
}) { }) {
const router = useRouter() const router = useRouter()
@@ -130,6 +135,7 @@ export default function UpdateItemForm({
{errors?.stock && <p className="text-error">{errors.stock.message}</p>} {errors?.stock && <p className="text-error">{errors.stock.message}</p>}
</div> </div>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
@@ -1,16 +1,21 @@
import { getI18n } from "@/i18n/server"
import { CategoryService } from "@/services/category.service" import { CategoryService } from "@/services/category.service"
import NewItemForm from "../_components/new.item.form" import NewItemForm from "../_components/new.item.form"
export default async function NewItemPage() { export default async function NewItemPage() {
const categories = await CategoryService.findAll() const categories = await CategoryService.findAll()
const { dictionary } = await getI18n()
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">New Item</h1> <h1 className="text-2xl font-bold">New Item</h1>
</div> </div>
<NewItemForm categories={categories} /> <NewItemForm
categories={categories}
submitButtonCopy={dictionary.common.submitButton}
/>
</div> </div>
) )
} }
@@ -1,3 +1,4 @@
import { getI18n } from "@/i18n/server"
import { RecipientService } from "@/services/recipient.service" import { RecipientService } from "@/services/recipient.service"
import RecipientForm from "../../_components/recipient.form" import RecipientForm from "../../_components/recipient.form"
@@ -9,6 +10,7 @@ export default async function RecipientEditPage({
}) { }) {
const { recipientId } = await params const { recipientId } = await params
const recipient = await RecipientService.findById(recipientId) const recipient = await RecipientService.findById(recipientId)
const { dictionary } = await getI18n()
if (!recipient) { if (!recipient) {
return <div>Recipient not found</div> return <div>Recipient not found</div>
@@ -19,7 +21,11 @@ export default async function RecipientEditPage({
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">Edit Recipient</h1> <h1 className="text-2xl font-bold">Edit Recipient</h1>
</div> </div>
<RecipientForm initialData={recipient} mode="edit" /> <RecipientForm
initialData={recipient}
mode="edit"
submitButtonCopy={dictionary.common.submitButton}
/>
</div> </div>
) )
} }
@@ -8,7 +8,10 @@ import {
createNewRecipient, createNewRecipient,
updateRecipient, updateRecipient,
} from "@/actions/recipient.actions" } 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 { RECIPIENT_DEPARTMENTS } from "@/lib/constants"
import { import {
type CreateRecipientFormType, type CreateRecipientFormType,
@@ -20,11 +23,13 @@ import type { Recipient } from "@/types"
interface RecipientFormProps { interface RecipientFormProps {
initialData?: Recipient initialData?: Recipient
mode?: "create" | "edit" mode?: "create" | "edit"
submitButtonCopy: SubmitButtonCopy
} }
export default function RecipientForm({ export default function RecipientForm({
initialData, initialData,
mode = "create", mode = "create",
submitButtonCopy,
}: RecipientFormProps) { }: RecipientFormProps) {
const router = useRouter() const router = useRouter()
@@ -166,6 +171,7 @@ export default function RecipientForm({
{errors?.phone && <p className="text-error">{errors.phone.message}</p>} {errors?.phone && <p className="text-error">{errors.phone.message}</p>}
</div> </div>
<SubmitButton <SubmitButton
copy={submitButtonCopy}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful} isSubmitSuccessful={isSubmitSuccessful}
> >
+9 -2
View File
@@ -1,12 +1,19 @@
import { getI18n } from "@/i18n/server"
import RecipientForm from "../_components/recipient.form" import RecipientForm from "../_components/recipient.form"
export default function NewRecipientPage() { export default async function NewRecipientPage() {
const { dictionary } = await getI18n()
return ( return (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">Add Recipient</h1> <h1 className="text-2xl font-bold">Add Recipient</h1>
</div> </div>
<RecipientForm mode="create" /> <RecipientForm
mode="create"
submitButtonCopy={dictionary.common.submitButton}
/>
</div> </div>
) )
} }
+9 -4
View File
@@ -1,8 +1,12 @@
import { Check, Loader2 } from "lucide-react" import { Check, Loader2 } from "lucide-react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import type { Dictionary } from "@/i18n/dictionaries"
export type SubmitButtonCopy = Dictionary["common"]["submitButton"]
interface SubmitButtonProps { interface SubmitButtonProps {
copy: SubmitButtonCopy
isSubmitting: boolean isSubmitting: boolean
isSubmitSuccessful: boolean isSubmitSuccessful: boolean
isSubmitError?: boolean isSubmitError?: boolean
@@ -11,10 +15,11 @@ interface SubmitButtonProps {
} }
export function SubmitButton({ export function SubmitButton({
copy,
isSubmitting, isSubmitting,
isSubmitSuccessful, isSubmitSuccessful,
disabled, disabled,
children = "Submit", children,
...props ...props
}: SubmitButtonProps) { }: SubmitButtonProps) {
return ( return (
@@ -24,16 +29,16 @@ export function SubmitButton({
disabled={disabled || isSubmitting || isSubmitSuccessful} disabled={disabled || isSubmitting || isSubmitSuccessful}
{...props} {...props}
> >
{!isSubmitting && !isSubmitSuccessful && children} {!isSubmitting && !isSubmitSuccessful && (children ?? copy.defaultLabel)}
{isSubmitting && ( {isSubmitting && (
<> <>
Processing {copy.processing}
<Loader2 className="animate-spin" /> <Loader2 className="animate-spin" />
</> </>
)} )}
{isSubmitSuccessful && ( {isSubmitSuccessful && (
<> <>
Success {copy.success}
<Check className="ml-2 animate-pulse text-green-500" /> <Check className="ml-2 animate-pulse text-green-500" />
</> </>
)} )}
+5
View File
@@ -18,6 +18,11 @@ export const en = {
previous: "Previous", previous: "Previous",
next: "Next", next: "Next",
}, },
submitButton: {
defaultLabel: "Submit",
processing: "Processing",
success: "Success",
},
forbidden: { forbidden: {
title: "Access denied", title: "Access denied",
description: "You do not have permission to access this section.", description: "You do not have permission to access this section.",
+5
View File
@@ -20,6 +20,11 @@ export const es = {
previous: "Anterior", previous: "Anterior",
next: "Siguiente", next: "Siguiente",
}, },
submitButton: {
defaultLabel: "Enviar",
processing: "Procesando",
success: "Completado",
},
forbidden: { forbidden: {
title: "Acceso denegado", title: "Acceso denegado",
description: "No tienes permisos para acceder a esta sección.", description: "No tienes permisos para acceder a esta sección.",
@@ -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<Parameters<typeof SubmitButton>[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")
})
})
+12
View File
@@ -139,11 +139,23 @@ describe("i18n dictionaries", () => {
next: "Siguiente", next: "Siguiente",
}) })
expect(getDictionary("en").common.submitButton).toEqual({
defaultLabel: "Submit",
processing: "Processing",
success: "Success",
})
expect(getDictionary("en").common.forbidden).toEqual({ expect(getDictionary("en").common.forbidden).toEqual({
title: "Access denied", title: "Access denied",
description: "You do not have permission to access this section.", description: "You do not have permission to access this section.",
homeLink: "Back to home", homeLink: "Back to home",
}) })
expect(getDictionary("es").common.submitButton).toEqual({
defaultLabel: "Enviar",
processing: "Procesando",
success: "Completado",
})
expect(getDictionary("es").common.forbidden).toEqual({ expect(getDictionary("es").common.forbidden).toEqual({
title: "Acceso denegado", title: "Acceso denegado",
description: "No tienes permisos para acceder a esta sección.", description: "No tienes permisos para acceder a esta sección.",