enabled i18n for all components

This commit is contained in:
2024-02-16 21:56:41 +01:00
parent 3746989f05
commit d30bd50e1a
11 changed files with 150 additions and 55 deletions

View File

@@ -2,20 +2,26 @@ import { PlusCircleIcon, HomeIcon } from "@heroicons/react/24/outline";
import { YearMonth } from "../lib/db-types";
import { formatYearMonth } from "../lib/format";
import Link from "next/link";
import { useTranslations } from 'next-intl';
export interface AddLocationButtonProps {
/** year and month at which the new billing location should be addes */
yearMonth: YearMonth
}
export const AddLocationButton:React.FC<AddLocationButtonProps> = ({yearMonth}) =>
export const AddLocationButton:React.FC<AddLocationButtonProps> = ({yearMonth}) => {
const t = useTranslations("home-page.add-location-button");
return(
<div className="card card-compact card-bordered bg-base-100 shadow-s my-1">
<Link href={`/location/${ formatYearMonth(yearMonth) }/add`} className="card-body tooltip self-center" data-tip="Add a new realestate">
<span className='flex self-center mr-[-3em]' data-tip="Add a new realestate">
<Link href={`/location/${ formatYearMonth(yearMonth) }/add`} className="card-body tooltip self-center" data-tip={t("tooltip")}>
<span className='flex self-center mr-[-3em]'>
<HomeIcon className="h-[1em] w-[1em] cursor-pointer text-4xl" />
<PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-xl text-green-500 ml-[-.6em] mt-[-.4em]" />
<span className="ml-1 mt-[.4em] text-xs text-left leading-[1.2em]">Add now<br/>realestate</span>
<span className="ml-1 mt-[.4em] text-xs text-left leading-[1.2em]">{t("tooltip")}</span>
</span>
</Link>
</div>;
</div>
);
}

View File

@@ -3,18 +3,24 @@ import React from "react";
import { formatYearMonth } from "../lib/format";
import { YearMonth } from "../lib/db-types";
import Link from "next/link";
import { useTranslations } from 'next-intl';
export interface AddMonthButtonProps {
yearMonth: YearMonth;
}
export const AddMonthButton:React.FC<AddMonthButtonProps> = ({ yearMonth }) =>
export const AddMonthButton:React.FC<AddMonthButtonProps> = ({ yearMonth }) => {
const t = useTranslations("home-page.add-month-button");
return(
<div className="card card-compact shadow-s mb-4">
<Link href={`/year-month/${formatYearMonth(yearMonth)}/add`} className='grid self-center tooltip' data-tip="Add next month">
<Link href={`/year-month/${formatYearMonth(yearMonth)}/add`} className='grid self-center tooltip' data-tip={t("tooltip")}>
<span className='flex self-center mr-[-3em]'>
<CalendarDaysIcon className="h-[1em] w-[1em] cursor-pointer text-4xl" />
<PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-xl text-green-500 ml-[-.4em] mt-[-.4em]" />
<span className="ml-1 mt-1 text-xs text-left leading-[1.2em]">Add next<br/>month</span>
<span className="ml-1 mt-1 text-xs text-left leading-[1.2em]">{t("tooltip")}</span>
</span>
</Link>
</div>;
</div>);
}

View File

@@ -1,11 +1,12 @@
"use client";
import { FC } from "react";
import { FC, ReactNode } from "react";
import { Bill, BillingLocation } from "../lib/db-types";
import { useFormState } from "react-dom";
import { Main } from "./Main";
import { deleteBillById } from "../lib/actions/billActions";
import Link from "next/link";
import { useTranslations } from "next-intl";
export interface BillDeleteFormProps {
bill: Bill,
@@ -17,18 +18,24 @@ export const BillDeleteForm:FC<BillDeleteFormProps> = ({ bill, location }) => {
const { year, month } = location.yearMonth;
const handleAction = deleteBillById.bind(null, location._id, bill._id, year, month);
const [ state, dispatch ] = useFormState(handleAction, null);
const t = useTranslations("bill-delete-form");
return(
<div className="card card-compact card-bordered min-w-[20em] max-w-[90em] bg-base-100 shadow-s my-1">
<div className="card-body">
<form action={dispatch}>
<p className="py-6 px-6">
Please confirm deletion of bill <strong>{bill.name}</strong> at <strong>{location.name}</strong>.
{
t.rich("text", {
bill_name:bill.name,
location_name:location.name,
strong: (chunks:ReactNode) => `<strong>${chunks}</strong>`,
})
}
</p>
<div className="pt-4 text-center">
<button className="btn btn-primary">Confim</button>
<Link className="btn btn-neutral ml-3" href={`/bill/${location._id}-${bill._id}/edit/`}>Cancel</Link>
<button className="btn btn-primary">{t("confirm-button")}</button>
<Link className="btn btn-neutral ml-3" href={`/bill/${location._id}-${bill._id}/edit/`}>{t("cancel-button")}</Link>
</div>
</form>
</div>

View File

@@ -8,6 +8,7 @@ import { updateOrAddBill } from "../lib/actions/billActions";
import Link from "next/link";
import { formatYearMonth } from "../lib/format";
import { findDecodePdf417 } from "../lib/pdf/barcodeDecoder";
import { useTranslations } from "next-intl";
// Next.js does not encode an utf-8 file name correctly when sending a form with a file attachment
// This is a workaround for that
@@ -25,6 +26,8 @@ export interface BillEditFormProps {
export const BillEditForm:FC<BillEditFormProps> = ({ location, bill }) => {
const t = useTranslations("bill-edit-form");
const { _id: billID, name, paid, attachment, notes, payedAmount: initialPayedAmount, barcodeImage: initialBarcodeImage } = bill ?? { _id:undefined, name:"", paid:false, notes:"" };
const { yearMonth:{year: billYear, month: billMonth}, _id: locationID } = location;
@@ -69,12 +72,12 @@ export const BillEditForm:FC<BillEditFormProps> = ({ location, bill }) => {
{
// don't show the delete button if we are adding a new bill
bill ?
<Link href={`/bill/${locationID}-${billID}/delete/`}>
<Link href={`/bill/${locationID}-${billID}/delete/`} data-tip={t("delete-tooltip")}>
<TrashIcon className="h-[1em] w-[1em] absolute cursor-pointer text-error bottom-5 right-4 text-2xl" />
</Link> : null
}
<input id="billName" name="billName" type="text" placeholder="Bill name" className="input input-bordered w-full" defaultValue={name} required />
<input id="billName" name="billName" type="text" placeholder={t("bill-name-placeholder")} className="input input-bordered w-full" defaultValue={name} required />
<div id="status-error" aria-live="polite" aria-atomic="true">
{state.errors?.billName &&
state.errors.billName.map((error: string) => (
@@ -107,13 +110,13 @@ export const BillEditForm:FC<BillEditFormProps> = ({ location, bill }) => {
<div className="flex">
<div className="form-control flex-row">
<label className="cursor-pointer label align-middle">
<span className="label-text mr-[1em]">Paid</span>
<span className="label-text mr-[1em]">{t("paid-checkbox")}</span>
<input id="billPaid" name="billPaid" type="checkbox" className="toggle toggle-success" defaultChecked={paid} onChange={billPaid_handleChange} />
</label>
</div>
<div className="form-control grow">
<label className="cursor-pointer label grow">
<span className="label-text mx-[1em]">Amount</span>
<span className="label-text mx-[1em]">{t("payed-amount")}</span>
<input type="text" id="payedAmount" name="payedAmount" className="input input-bordered text-right w-[5em] grow" placeholder="0.00" value={payedAmount} onFocus={e => e.target.select()} onChange={payedAmount_handleChange} />
</label>
</div>
@@ -134,11 +137,11 @@ export const BillEditForm:FC<BillEditFormProps> = ({ location, bill }) => {
<label className="cursor-pointer label p-2 grow bg-white">
<img src={barcodeImage} className="grow sm:max-w-[350px]" alt="2D Barcode" />
</label>
<p className="text-xs my-1">After scanning the code make sure the information is correct.<br/>We are not liable in case of an incorrect payment.</p>
<p className="text-xs my-1">{t.rich('barcode-disclaimer', { br: () => <br /> })}</p>
</div> : null
}
<textarea id="billNotes" name="billNotes" className="textarea textarea-bordered my-2 max-w-lg w-full block" placeholder="Note" defaultValue={notes ?? ''}></textarea>
<textarea id="billNotes" name="billNotes" className="textarea textarea-bordered my-2 max-w-lg w-full block" placeholder={t("notes-placeholder")} defaultValue={notes ?? ''}></textarea>
<div id="status-error" aria-live="polite" aria-atomic="true">
{state.errors?.billNotes &&
state.errors.billNotes.map((error: string) => (
@@ -149,8 +152,8 @@ export const BillEditForm:FC<BillEditFormProps> = ({ location, bill }) => {
</div>
<div className="pt-4">
<button type="submit" className="btn btn-primary">Save</button>
<Link className="btn btn-neutral ml-3" href={`/?year=${billYear}&month=${billMonth}`}>Cancel</Link>
<button type="submit" className="btn btn-primary">{t("save-button")}</button>
<Link className="btn btn-neutral ml-3" href={`/?year=${billYear}&month=${billMonth}`}>{t("cancel-button")}</Link>
</div>
<div id="status-error" aria-live="polite" aria-atomic="true">

View File

@@ -1,12 +1,13 @@
'client only';
import { Cog8ToothIcon, PlusCircleIcon } from "@heroicons/react/24/outline";
import { FC } from "react";
import { FC, ReactNode } from "react";
import { BillBadge } from "./BillBadge";
import { BillingLocation } from "../lib/db-types";
import { formatYearMonth } from "../lib/format";
import { formatCurrency } from "../lib/formatStrings";
import Link from "next/link";
import { useTranslations } from "next-intl";
export interface LocationCardProps {
location: BillingLocation
@@ -14,13 +15,15 @@ export interface LocationCardProps {
export const LocationCard:FC<LocationCardProps> = ({location: { _id, name, yearMonth, bills }}) => {
const t = useTranslations("home-page.location-card");
// sum all the billAmounts
const monthlyExpense = bills.reduce((acc, bill) => bill.paid ? acc + (bill.payedAmount ?? 0) : acc, 0);
return(
<div data-key={_id } className="card card-compact card-bordered max-w-[30em] bg-base-100 border-1 border-neutral my-1">
<div className="card-body">
<Link href={`/location/${_id}/edit`} className="card-subtitle tooltip" data-tip="Edit Location">
<Link href={`/location/${_id}/edit`} className="card-subtitle tooltip" data-tip={t("edit-card-tooltip")}>
<Cog8ToothIcon className="h-[1em] w-[1em] absolute cursor-pointer top-3 right-3 text-2xl" />
</Link>
<h2 className="card-title mr-[2em] text-[1rem]">{formatYearMonth(yearMonth)} {name}</h2>
@@ -28,14 +31,18 @@ export const LocationCard:FC<LocationCardProps> = ({location: { _id, name, yearM
{
bills.map(bill => <BillBadge key={`${_id}-${bill._id}`} locationId={_id} bill={bill} />)
}
<Link href={`/bill/${_id}/add`} className="tooltip" data-tip="Add a new bill">
<Link href={`/bill/${_id}/add`} className="tooltip" data-tip={t("add-bill-button-tooltip")}>
<PlusCircleIcon className="h-[1em] w-[1em] cursor-pointer text-2xl" />
</Link>
</div>
{
monthlyExpense > 0 ?
<p>
Payed total: <strong>{ formatCurrency(monthlyExpense) }</strong>
{
t.rich("payed-total", {
amount: formatCurrency(monthlyExpense),
strong: (chunks:ReactNode) => `<strong>${chunks}</strong>`
})}
</p>
: null
}

View File

@@ -1,11 +1,12 @@
"use client";
import { FC } from "react";
import { FC, ReactNode } from "react";
import { BillingLocation } from "../lib/db-types";
import { deleteLocationById } from "../lib/actions/locationActions";
import { useFormState } from "react-dom";
import { gotoUrl } from "../lib/actions/navigationActions";
import Link from "next/link";
import { useTranslations } from "next-intl";
export interface LocationDeleteFormProps {
/** location which should be deleted */
@@ -16,6 +17,8 @@ export const LocationDeleteForm:FC<LocationDeleteFormProps> = ({ location }) =>
{
const handleAction = deleteLocationById.bind(null, location._id, location.yearMonth);
const [ state, dispatch ] = useFormState(handleAction, null);
const t = useTranslations("location-delete-form");
const handleCancel = () => {
gotoUrl(`/location/${location._id}/edit/`);
@@ -26,11 +29,16 @@ export const LocationDeleteForm:FC<LocationDeleteFormProps> = ({ location }) =>
<div className="card-body">
<form action={dispatch}>
<p className="py-6 px-6">
Please confirm deletion of location <strong>{location.name}</strong>.
{
t.rich("text", {
name:location.name,
strong: (chunks:ReactNode) => `<strong>${chunks}</strong>`,
})
}
</p>
<div className="pt-4 text-center">
<button className="btn btn-primary w-[5.5em]">Confim</button>
<Link className="btn btn-neutral w-[5.5em] ml-3" href={`/location/${location._id}/edit/`}>Cancel</Link>
<button className="btn btn-primary w-[5.5em]">{t("confirm-button")}</button>
<Link className="btn btn-neutral w-[5.5em] ml-3" href={`/location/${location._id}/edit/`}>{t("cancel-button")}</Link>
</div>
</form>
</div>

View File

@@ -6,6 +6,7 @@ import { BillingLocation, YearMonth } from "../lib/db-types";
import { updateOrAddLocation } from "../lib/actions/locationActions";
import { useFormState } from "react-dom";
import Link from "next/link";
import { useTranslations } from "next-intl";
export type LocationEditFormProps = {
/** location which should be edited */
@@ -24,6 +25,7 @@ export const LocationEditForm:FC<LocationEditFormProps> = ({ location, yearMonth
const initialState = { message: null, errors: {} };
const handleAction = updateOrAddLocation.bind(null, location?._id, location?.yearMonth ?? yearMonth);
const [ state, dispatch ] = useFormState(handleAction, initialState);
const t = useTranslations("location-edit-form");
let { year, month } = location ? location.yearMonth : yearMonth;
@@ -33,11 +35,11 @@ export const LocationEditForm:FC<LocationEditFormProps> = ({ location, yearMonth
<form action={dispatch}>
{
location &&
<Link href={`/location/${location._id}/delete`} className="absolute bottom-5 right-4 tooltip" data-tip="Delete Location">
<Link href={`/location/${location._id}/delete`} className="absolute bottom-5 right-4 tooltip" data-tip={t("delete-tooltip")}>
<TrashIcon className="h-[1em] w-[1em] text-error text-2xl" />
</Link>
}
<input id="locationName" name="locationName" type="text" placeholder="Realestate name" className="input input-bordered w-full" defaultValue={location?.name ?? ""} />
<input id="locationName" name="locationName" type="text" placeholder={t("location-name-placeholder")} className="input input-bordered w-full" defaultValue={location?.name ?? ""} />
<div id="status-error" aria-live="polite" aria-atomic="true">
{state.errors?.locationName &&
state.errors.locationName.map((error: string) => (
@@ -47,7 +49,7 @@ export const LocationEditForm:FC<LocationEditFormProps> = ({ location, yearMonth
))}
</div>
<textarea id="locationNotes" name="locationNotes" className="textarea textarea-bordered my-1 w-full block h-[8em]" placeholder="Description" defaultValue={location?.notes ?? ""}></textarea>
<textarea id="locationNotes" name="locationNotes" className="textarea textarea-bordered my-1 w-full block h-[8em]" placeholder={t("notes-placeholder")} defaultValue={location?.notes ?? ""}></textarea>
<div id="status-error" aria-live="polite" aria-atomic="true">
{state.errors?.locationNotes &&
state.errors.locationNotes.map((error: string) => (
@@ -66,8 +68,8 @@ export const LocationEditForm:FC<LocationEditFormProps> = ({ location, yearMonth
}
</div>
<div className="pt-4">
<button className="btn btn-primary w-[5.5em]">Save</button>
<Link className="btn btn-neutral w-[5.5em] ml-3" href={`/?year=${year}&month=${month}`}>Cancel</Link>
<button className="btn btn-primary w-[5.5em]">{t("save-button")}</button>
<Link className="btn btn-neutral w-[5.5em] ml-3" href={`/?year=${year}&month=${month}`}>{t("cancel-button")}</Link>
</div>
</form>
</div>

View File

@@ -4,7 +4,7 @@ import { FC, useEffect, useRef } from "react";
import { formatYearMonth } from "../lib/format";
import { YearMonth } from "../lib/db-types";
import { formatCurrency } from "../lib/formatStrings";
import { useTranslations } from "next-intl";
export interface MonthCardProps {
yearMonth: YearMonth,
@@ -17,6 +17,7 @@ export interface MonthCardProps {
export const MonthCard:FC<MonthCardProps> = ({ yearMonth, children, monthlyExpense, expanded, onToggle }) => {
const elRef = useRef<HTMLDivElement>(null);
const t = useTranslations("home-page.month-card");
// Setting the `month` will activate the accordion belonging to that month
// If the accordion is already active, it will collapse it
@@ -37,7 +38,7 @@ export const MonthCard:FC<MonthCardProps> = ({ yearMonth, children, monthlyExpen
{
monthlyExpense>0 ?
<p className="text-xs font-medium">
Total monthly expenditure: <strong>{ formatCurrency(monthlyExpense) }</strong>
{t("payed-total-label")} <strong>{ formatCurrency(monthlyExpense) }</strong>
</p> : null
}
</div>

View File

@@ -1,6 +1,7 @@
"use client";
import { signIn } from "next-auth/react"
import { useTranslations } from "next-intl";
import Image from "next/image";
const providerLogo = (provider: {id:string, name:string}) => {
@@ -14,10 +15,15 @@ const providerLogo = (provider: {id:string, name:string}) => {
}
}
export const SignInButton:React.FC<{ provider: {id:string, name:string} }> = ({ provider }) =>
export const SignInButton:React.FC<{ provider: {id:string, name:string} }> = ({ provider }) => {
const t = useTranslations("login-page");
return(
<button className="btn btn-neutral" onClick={() => signIn(provider.id, { callbackUrl:"https://rezije.app/" }) }>
<Image alt="Provider Logo" loading="lazy" height="24" width="24" id="provider-logo-dark" src={providerLogo(provider)} />
<span>Sign in with {provider.name}</span>
<span>
{t("sign-in-button")} {provider.name}</span>
</button>
);
}

View File

@@ -40,6 +40,55 @@
"video-url": "/welcome-demo-vp9-25fps-1500bps.webm",
"image-url": "/bar-code-demo.png",
"video-title": "Demo osnovnih koraka u aplikaciji"
}
},
"sign-in-button": "Sign in with"
},
"home-page": {
"add-location-button": {
"tooltop": "Add a new realestate"
},
"add-month-button": {
"tooltop": "Add next mont"
},
"location-card": {
"edit-card-tooltip": "Edit realestate",
"add-bill-button-tooltip": "Add a new bill",
"payed-total": "Payed total: <strong>{amount}</strong>"
},
"month-card": {
"payed-total-label": "Total monthly expenditure:"
}
},
"bill-delete-form":
{
"text": "Please confirm deletion of bill “<strong>{bill_name}</strong>” at “<strong>{location_name}</strong>”.",
"cancel-button": "Cancel",
"confirm-button": "Confirm"
},
"bill-edit-form":
{
"bill-name-placeholder": "Bill name",
"paid-checkbox":"Paid",
"payed-amount": "Amount",
"barcode-disclaimer": "After scanning the code make sure the information is correct.<br/>We are not liable in case of an incorrect payment.",
"notes-placeholder": "Notes",
"save-button": "Save",
"cancel-button": "Cancel",
"delete-tooltip": "Delete bill"
},
"location-delete-form":
{
"text": "Please confirm deletion of realestate “<strong>{name}</strong>””.",
"cancel-button": "Cancel",
"confirm-button": "Confirm"
},
"location-edit-form":
{
"location-name-placeholder": "Realestate name",
"notes-placeholder": "Notes",
"save-button": "Save",
"cancel-button": "Cancel",
"delete-tooltip": "Delete realestate"
}
}

2
package-lock.json generated
View File

@@ -1,5 +1,5 @@
{
"name": "rezije",
"name": "evidencija-rezija",
"lockfileVersion": 3,
"requires": true,
"packages": {