feat: add Revolut payment link support alongside IBAN

- Add NoteBox component for displaying warning messages with icon
- Add Revolut profile name field to user settings schema
- Update UserSettingsForm to support payment instruction selection (disabled/IBAN/Revolut)
- Add Croatian and English translations for new payment options
- Reserve fields for future per-instruction enable/disable functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-24 13:37:54 +01:00
parent 64b31a08b4
commit 686bec6c10
6 changed files with 101 additions and 57 deletions

View File

@@ -28,8 +28,18 @@ export interface UserSettings {
ownerIBAN?: string | null; ownerIBAN?: string | null;
/** currency (ISO 4217) */ /** currency (ISO 4217) */
currency?: string | null; currency?: string | null;
/** owner Revolut payment link */
ownerRevolutProfileName?: string | null;
/** whether to show 2D code in monthly statement */ /** whether to show 2D code in monthly statement */
show2dCodeInMonthlyStatement?: boolean | null; show2dCodeInMonthlyStatement?: boolean | null;
/** whether to show payment instructions in monthly statement */
showPaymentInstructionsInMonthlyStatement?: "disabled" | "iban" | "revolut" | null;
// /** whether enableshow IBAN payment instructions in monthly statement */
// enableIbanPaymentInstructionsInMonthlyStatement?: boolean | null;
// /** whether to enable Revolut payment instructions in monthly statement */
// enableRevolutPaymentInstructionsInMonthlyStatement?: boolean | null;
}; };
/** bill object in the form returned by MongoDB */ /** bill object in the form returned by MongoDB */

7
app/ui/NoteBox.tsx Normal file
View File

@@ -0,0 +1,7 @@
import { FC, ReactNode } from "react";
export const NoteBox: FC<{ children: ReactNode, className?: string }> = ({ children, className }) =>
<div className={`alert max-w-md flex flex-row items-start gap-[.8rem] ml-1 ${className}`}>
<span className="w-6 h-6 text-xl"></span>
<span className="text-left">{children}</span>
</div>

View File

@@ -9,6 +9,7 @@ import Link from "next/link";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
import { formatIban } from "../lib/formatStrings"; import { formatIban } from "../lib/formatStrings";
import { InfoBox } from "./InfoBox"; import { InfoBox } from "./InfoBox";
import { NoteBox } from "./NoteBox";
export type UserSettingsFormProps = { export type UserSettingsFormProps = {
userSettings: UserSettings | null; userSettings: UserSettings | null;
@@ -32,21 +33,16 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
ownerTown: userSettings?.ownerTown ?? "", ownerTown: userSettings?.ownerTown ?? "",
ownerIBAN: formatIban(userSettings?.ownerIBAN) ?? "", ownerIBAN: formatIban(userSettings?.ownerIBAN) ?? "",
currency: userSettings?.currency ?? "EUR", currency: userSettings?.currency ?? "EUR",
ownerRevolutProfileName: userSettings?.ownerRevolutProfileName ?? "",
showPaymentInstructions: userSettings?.showPaymentInstructionsInMonthlyStatement ?? "disabled",
}); });
// https://revolut.me/aderezic?currency=EUR&amount=70000
const handleInputChange = (field: keyof typeof formValues, value: string) => { const handleInputChange = (field: keyof typeof formValues, value: string) => {
setFormValues(prev => ({ ...prev, [field]: value })); setFormValues(prev => ({ ...prev, [field]: value }));
}; };
// Check if any required field is missing (clean IBAN of spaces for validation)
const cleanedOwnerIBAN = formValues.ownerIBAN.replace(/\s/g, '');
const hasMissingData = !formValues.ownerName || !formValues.ownerStreet || !formValues.ownerTown || !cleanedOwnerIBAN || !formValues.currency;
// Track whether to generate 2D code for tenant (use persisted value from database)
const [show2dCodeInMonthlyStatement, setShow2dCodeInMonthlyStatement] = useState(
userSettings?.show2dCodeInMonthlyStatement ?? false
);
return ( return (
<> <>
<fieldset className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4 pt-1 pb-2 mt-4"> <fieldset className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4 pt-1 pb-2 mt-4">
@@ -60,7 +56,7 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
id="currency" id="currency"
name="currency" name="currency"
className="select select-bordered w-full" className="select select-bordered w-full"
defaultValue={userSettings?.currency ?? "EUR"} defaultValue={formValues.currency}
onChange={(e) => handleInputChange("currency", e.target.value)} onChange={(e) => handleInputChange("currency", e.target.value)}
disabled={pending} disabled={pending}
> >
@@ -111,25 +107,28 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
</div> </div>
</fieldset> </fieldset>
<fieldset className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4 pb-2 mt-4"> <fieldset className="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4 pb-2 mt-4">
<legend className="fieldset-legend font-semibold uppercase">{t("tenant-2d-code-legend")}</legend> <legend className="fieldset-legend font-semibold uppercase">{t("tenant-payment-instructions--legend")}</legend>
<InfoBox className="p-1 mb-1">{t("info-box-message")}</InfoBox> <InfoBox className="p-1 mb-1">{t("info-box-message")}</InfoBox>
<fieldset className="fieldset"> <fieldset className="form-control w-full">
<label className="label cursor-pointer justify-start gap-3"> <select
<input id="showPaymentInstructions"
type="checkbox" name="showPaymentInstructions"
name="generateTenantCode" className="select select-bordered w-full"
className="toggle toggle-primary" defaultValue={formValues.showPaymentInstructions}
checked={show2dCodeInMonthlyStatement} onChange={(e) => handleInputChange("showPaymentInstructions", e.target.value)}
onChange={(e) => setShow2dCodeInMonthlyStatement(e.target.checked)} disabled={pending}
/> >
<legend className="fieldset-legend">{t("tenant-2d-code-toggle-label")}</legend> <option value="disabled">{t("tenant-payment-instructions--show-no-instructions")}</option>
</label> <option value="iban">{t("tenant-payment-instructions--show-iban-instructions")}</option>
</fieldset> <option value="revolut">{t("tenant-payment-instructions--show-revolut-instructions")}</option>
{show2dCodeInMonthlyStatement && ( </select>
</fieldset>
{formValues.showPaymentInstructions === "iban" && (
<> <>
<div className="divider mt-6 mb-2 font-bold uppercase">Informacije za uplatu</div>
<div className="form-control w-full"> <div className="form-control w-full">
<label className="label"> <label className="label">
<span className="label-text">{t("owner-name-label")}</span> <span className="label-text">{t("owner-name-label")}</span>
@@ -141,7 +140,7 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
maxLength={25} maxLength={25}
placeholder={t("owner-name-placeholder")} placeholder={t("owner-name-placeholder")}
className="input input-bordered w-full placeholder:text-gray-600" className="input input-bordered w-full placeholder:text-gray-600"
defaultValue={userSettings?.ownerName ?? ""} defaultValue={formValues.ownerName}
onChange={(e) => handleInputChange("ownerName", e.target.value)} onChange={(e) => handleInputChange("ownerName", e.target.value)}
disabled={pending} disabled={pending}
/> />
@@ -166,7 +165,7 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
maxLength={25} maxLength={25}
placeholder={t("owner-street-placeholder")} placeholder={t("owner-street-placeholder")}
className="input input-bordered w-full placeholder:text-gray-600" className="input input-bordered w-full placeholder:text-gray-600"
defaultValue={userSettings?.ownerStreet ?? ""} defaultValue={formValues.ownerStreet}
onChange={(e) => handleInputChange("ownerStreet", e.target.value)} onChange={(e) => handleInputChange("ownerStreet", e.target.value)}
disabled={pending} disabled={pending}
/> />
@@ -191,7 +190,7 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
maxLength={27} maxLength={27}
placeholder={t("owner-town-placeholder")} placeholder={t("owner-town-placeholder")}
className="input input-bordered w-full placeholder:text-gray-600" className="input input-bordered w-full placeholder:text-gray-600"
defaultValue={userSettings?.ownerTown ?? ""} defaultValue={formValues.ownerTown}
onChange={(e) => handleInputChange("ownerTown", e.target.value)} onChange={(e) => handleInputChange("ownerTown", e.target.value)}
disabled={pending} disabled={pending}
/> />
@@ -215,7 +214,7 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
type="text" type="text"
placeholder={t("owner-iban-placeholder")} placeholder={t("owner-iban-placeholder")}
className="input input-bordered w-full placeholder:text-gray-600" className="input input-bordered w-full placeholder:text-gray-600"
defaultValue={formatIban(userSettings?.ownerIBAN)} defaultValue={formValues.ownerIBAN}
onChange={(e) => handleInputChange("ownerIBAN", e.target.value)} onChange={(e) => handleInputChange("ownerIBAN", e.target.value)}
disabled={pending} disabled={pending}
/> />
@@ -229,7 +228,37 @@ const FormFields: FC<FormFieldsProps> = ({ userSettings, errors, message }) => {
</div> </div>
</div> </div>
<InfoBox className="p-1 mt-1">{t("additional-notes")}</InfoBox> <NoteBox className="p-1 mt-1">{t("payment-additional-notes")}</NoteBox>
</>
)}
{formValues.showPaymentInstructions === "revolut" && (
<>
<div className="divider mt-6 mb-2 font-bold uppercase">Informacije za uplatu</div>
<div className="form-control w-full">
<label className="label">
<span className="label-text">{t("owner-revolut-link-label")}</span>
</label>
<input
id="ownerRevolutProfileName"
name="ownerRevolutProfileName"
type="text"
maxLength={25}
placeholder={t("owner-revolut-link-placeholder")}
className="input input-bordered w-full placeholder:text-gray-600"
defaultValue={formValues.ownerRevolutProfileName}
onChange={(e) => handleInputChange("ownerRevolutProfileName", e.target.value)}
disabled={pending}
/>
<div id="ownerRevolutProfileName-error" aria-live="polite" aria-atomic="true">
{errors?.ownerRevolutProfileName &&
errors.ownerRevolutProfileName.map((error: string) => (
<p className="mt-2 text-sm text-red-500" key={error}>
{error}
</p>
))}
</div>
</div>
<NoteBox className="p-1 mt-1">{t("payment-additional-notes")}</NoteBox>
</> </>
)} )}
</fieldset> </fieldset>

View File

@@ -194,6 +194,13 @@
"info-box-message": "By activating this option, a 2D barcode will be included in the monthly statement sent to the tenant, allowing them to make a direct payment to your bank account.", "info-box-message": "By activating this option, a 2D barcode will be included in the monthly statement sent to the tenant, allowing them to make a direct payment to your bank account.",
"tenant-2d-code-legend": "TENANT 2D CODE", "tenant-2d-code-legend": "TENANT 2D CODE",
"tenant-2d-code-toggle-label": "include 2D code in monthly statements", "tenant-2d-code-toggle-label": "include 2D code in monthly statements",
"tenant-payment-instructions--legend": "Payment Instructions",
"tenant-payment-instructions--info": "If you enable this option, the monthly statement sent to the tenant will include payment instructions and a 2D barcode, allowing them to make a direct payment to your bank account.",
"tenant-payment-instructions--show-no-instructions": "🚫 - do not show payment instructions",
"tenant-payment-instructions--show-iban-instructions": "🏦 - show payment instructions for IBAN",
"tenant-payment-instructions--show-revolut-instructions": "🆁 - show payment instructions for Revolut",
"owner-name-label": "Your First and Last Name", "owner-name-label": "Your First and Last Name",
"owner-name-placeholder": "enter your first and last name", "owner-name-placeholder": "enter your first and last name",
"owner-street-label": "Your Street and House Number", "owner-street-label": "Your Street and House Number",
@@ -201,7 +208,12 @@
"owner-town-label": "Your Postal Code and Town", "owner-town-label": "Your Postal Code and Town",
"owner-town-placeholder": "enter your postal code and town", "owner-town-placeholder": "enter your postal code and town",
"owner-iban-label": "IBAN", "owner-iban-label": "IBAN",
"owner-iban-placeholder": "enter your IBAN", "owner-iban-placeholder": "enter your IBAN for receiving payments",
"owner-revolut-profile-label": "Revolut profile name",
"owner-revolut-profile-placeholder": "enter your Revolut profile name for receiving payments",
"owner-revolut-profile-tooltip": "You can find your Revolut profile name in the Revolut app under your user profile. It is displayed below your name and starts with the '@' symbol (e.g., '@john123').",
"general-settings-legend": "General Settings", "general-settings-legend": "General Settings",
"currency-label": "Currency", "currency-label": "Currency",
"save-button": "Save", "save-button": "Save",
@@ -215,6 +227,6 @@
"currency-required": "Currency is mandatory", "currency-required": "Currency is mandatory",
"validation-failed": "Validation failed. Please check the form and try again." "validation-failed": "Validation failed. Please check the form and try again."
}, },
"additional-notes": "Note: For the 2D code to be displayed, you must enter both the tenant's first and last names in the settings of each property for which you want to use this functionality." "payment-additional-notes": "IMPORTANT: For the payment instructions to be displayed to the tenant, you must also enable this option in the property's settings."
} }
} }

View File

@@ -190,9 +190,12 @@
}, },
"user-settings-form": { "user-settings-form": {
"title": "Korisničke postavke", "title": "Korisničke postavke",
"info-box-message": "Ako uključite ovu opciji na mjesečnom obračunu koji se šalje podstanaru biti će prikazan 2D bar kod, putem kojeg će moći izvršiti izravnu uplatu na vaš bankovni račun.", "info-box-message": "Ako uključite ovu opciju na mjesečnom obračunu koji se šalje podstanaru biti će prikazane upute za uplatu i 2D bar kod, putem kojeg će moći izvršiti izravnu uplatu na vaš bankovni račun.",
"tenant-2d-code-legend": "2D BARKOD ZA PODSTANARA", "tenant-payment-instructions--legend": "Upute za uplatu",
"tenant-2d-code-toggle-label": "prikazuj 2D barkod u mjesečnom obračunu", "tenant-payment-instructions--info": "Ako uključite ovu opciju na mjesečnom obračunu koji se šalje podstanaru biti će prikazane upute za uplatu i 2D bar kod, putem kojeg će moći izvršiti izravnu uplatu na vaš bankovni račun.",
"tenant-payment-instructions--show-no-instructions": "🚫 - Ne prikazivati upute za uplatu",
"tenant-payment-instructions--show-iban-instructions": "🏦 - Prikazuj upute za uplatu na IBAN",
"tenant-payment-instructions--show-revolut-instructions": "🆁 - Prikazuj upute za uplatu na Revolut",
"owner-name-label": "Vaše ime i prezime", "owner-name-label": "Vaše ime i prezime",
"owner-name-placeholder": "unesite svoje ime i prezime", "owner-name-placeholder": "unesite svoje ime i prezime",
"owner-street-label": "Ulica i kućni broj", "owner-street-label": "Ulica i kućni broj",
@@ -200,7 +203,11 @@
"owner-town-label": "Poštanski broj i Grad", "owner-town-label": "Poštanski broj i Grad",
"owner-town-placeholder": "unesite poštanski broj i grad", "owner-town-placeholder": "unesite poštanski broj i grad",
"owner-iban-label": "IBAN", "owner-iban-label": "IBAN",
"owner-iban-placeholder": "unesite svoj IBAN", "owner-iban-placeholder": "IBAN putem kojeg ćete primate uplate",
"owner-revolut-profile-label": "Naziv vašeg Revolut profila",
"owner-revolut-profile-placeholder": "profil putem kojeg ćete primati uplate",
"owner-revolut-profile-tooltip": "Naziv vašeg Revolute profila možete pronaći u aplikaciji Revolut u korisničkom profilu. Prikazan je ispod vašeg imena i prezimena - počinje sa znakom '@' (npr: '@ivan123').",
"general-settings-legend": "Opće postavke", "general-settings-legend": "Opće postavke",
"currency-label": "Valuta", "currency-label": "Valuta",
"save-button": "Spremi", "save-button": "Spremi",
@@ -214,6 +221,6 @@
"currency-required": "Valuta je obavezna", "currency-required": "Valuta je obavezna",
"validation-failed": "Validacija nije uspjela. Molimo provjerite formu i pokušajte ponovno." "validation-failed": "Validacija nije uspjela. Molimo provjerite formu i pokušajte ponovno."
}, },
"additional-notes": "Napomena: da bi 2D koda bio prikazan, morate unijeti i ime i prezime podstanara u postavkama svake nekretnine za koju želite koristiti ovu funkcionalnost." "payment-additional-notes": "VAŽNO: da bi upute za uplatu bile prikazane podstanaru, morate tu ovu opciju uključiti i u postavkama pripadajuće nekretnine."
} }
} }

21
package-lock.json generated
View File

@@ -147,7 +147,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz",
"integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.23.5", "@babel/code-frame": "^7.23.5",
@@ -501,7 +500,6 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
@@ -545,7 +543,6 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
@@ -1072,7 +1069,6 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz", "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
"integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==", "integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@mui/core-downloads-tracker": "^7.3.5", "@mui/core-downloads-tracker": "^7.3.5",
@@ -1473,7 +1469,6 @@
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.1.0.tgz", "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.1.0.tgz",
"integrity": "sha512-x4FavbNEeXx/baD/zC/SdrvkjSby8nBn8KcCREqk6UuwvwoAPZmaV8TFCAuo/cpovBRTIY67mHhe86MQQm/68Q==", "integrity": "sha512-x4FavbNEeXx/baD/zC/SdrvkjSby8nBn8KcCREqk6UuwvwoAPZmaV8TFCAuo/cpovBRTIY67mHhe86MQQm/68Q==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"glob": "10.3.10" "glob": "10.3.10"
} }
@@ -1804,7 +1799,6 @@
"version": "18.2.21", "version": "18.2.21",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz",
"integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
@@ -1926,7 +1920,6 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0", "@typescript-eslint/types": "6.21.0",
@@ -2226,7 +2219,6 @@
"version": "0.20.0", "version": "0.20.0",
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.20.0.tgz", "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.20.0.tgz",
"integrity": "sha512-6Ev6rcqVjMakZFIDvbUf0dtpPGeZMTfyxYg4HkVWioWeN7cRcnUWT3bU6sdohc82O1nPXcjq6WiGfXX2Pnit6A==", "integrity": "sha512-6Ev6rcqVjMakZFIDvbUf0dtpPGeZMTfyxYg4HkVWioWeN7cRcnUWT3bU6sdohc82O1nPXcjq6WiGfXX2Pnit6A==",
"peer": true,
"dependencies": { "dependencies": {
"ts-custom-error": "^3.2.1" "ts-custom-error": "^3.2.1"
}, },
@@ -2253,7 +2245,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true, "dev": true,
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -2673,7 +2664,6 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"peer": true,
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001587", "caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668", "electron-to-chromium": "^1.4.668",
@@ -3352,7 +3342,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
"integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1", "@eslint-community/regexpp": "^4.6.1",
@@ -3548,7 +3537,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
"integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"array-includes": "^3.1.7", "array-includes": "^3.1.7",
"array.prototype.findlastindex": "^1.2.3", "array.prototype.findlastindex": "^1.2.3",
@@ -5988,7 +5976,6 @@
"resolved": "https://registry.npmjs.org/next/-/next-14.2.33.tgz", "resolved": "https://registry.npmjs.org/next/-/next-14.2.33.tgz",
"integrity": "sha512-GiKHLsD00t4ACm1p00VgrI0rUFAC9cRDGReKyERlM57aeEZkOQGcZTpIbsGn0b562FTPJWmYfKwplfO9EaT6ng==", "integrity": "sha512-GiKHLsD00t4ACm1p00VgrI0rUFAC9cRDGReKyERlM57aeEZkOQGcZTpIbsGn0b562FTPJWmYfKwplfO9EaT6ng==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@next/env": "14.2.33", "@next/env": "14.2.33",
"@swc/helpers": "0.5.5", "@swc/helpers": "0.5.5",
@@ -6506,7 +6493,6 @@
"version": "8.11.3", "version": "8.11.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
"peer": true,
"dependencies": { "dependencies": {
"buffer-writer": "2.0.0", "buffer-writer": "2.0.0",
"packet-reader": "1.0.0", "packet-reader": "1.0.0",
@@ -6708,7 +6694,6 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.6", "nanoid": "^3.3.6",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
@@ -6894,7 +6879,6 @@
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/preact" "url": "https://opencollective.com/preact"
@@ -6923,7 +6907,6 @@
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true, "dev": true,
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -7065,7 +7048,6 @@
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@@ -7077,7 +7059,6 @@
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.0" "scheduler": "^0.23.0"
@@ -8002,7 +7983,6 @@
"version": "3.4.1", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
"integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==",
"peer": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@@ -8308,7 +8288,6 @@
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"