This commit is contained in:
Marco Pedone 2025-08-29 16:18:32 +02:00
parent f46702275b
commit 5755b00d7b
319 changed files with 9017 additions and 8869 deletions

View file

@ -1,16 +1,20 @@
{ {
"typescript.tsdk": "node_modules\\typescript\\lib", "editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit"
},
"editor.formatOnSave": true,
"npm.exclude": "**/.next/**",
"sqltools.connections": [ "sqltools.connections": [
{ {
"previewLimit": 50, "database": "postgres",
"server": "localhost",
"port": 5432,
"driver": "PostgreSQL", "driver": "PostgreSQL",
"name": "DB", "name": "DB",
"database": "postgres", "password": "rootpost",
"username": "postgres", "port": 5432,
"password": "rootpost" "previewLimit": 50,
"server": "localhost",
"username": "postgres"
} }
], ],
"editor.formatOnSave": true, "typescript.tsdk": "node_modules\\typescript\\lib"
} }

View file

@ -1,10 +1,14 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json",
"vcs": { "assist": {
"enabled": true, "actions": {
"root": "../../", "source": {
"clientKind": "git", "organizeImports": "on",
"useIgnoreFile": false "useSortedAttributes": "on",
"useSortedKeys": "on"
}
},
"enabled": true
}, },
"files": { "files": {
@ -21,6 +25,7 @@
"headers", "headers",
"schemas" "schemas"
], ],
"ignoreUnknown": true,
"includes": [ "includes": [
"**", "**",
"!node_modules", "!node_modules",
@ -49,60 +54,56 @@
"!src/i18n/nazioni.ts", "!src/i18n/nazioni.ts",
"!src/i18n/provincie.ts", "!src/i18n/provincie.ts",
"!src/styles/globals.css" "!src/styles/globals.css"
], ]
"ignoreUnknown": true
}, },
"formatter": { "formatter": {
"enabled": true, "enabled": true,
"indentStyle": "tab" "indentStyle": "tab"
}, },
"linter": {
"enabled": true,
"domains": {
"next": "recommended",
"react": "recommended"
},
"rules": {
"recommended": true,
"a11y": {
"noNoninteractiveElementInteractions": "error",
"useSemanticElements": "off",
"noAutofocus": "off"
},
"suspicious": {
"useIterableCallbackReturn": "off",
"noArrayIndexKey": "off",
"noUnknownAtRules": "off"
},
"complexity": {
"noUselessFragments": "off"
},
"correctness": {
"useHookAtTopLevel": "off",
"useExhaustiveDependencies": "off",
"useParseIntRadix": "off"
},
"performance": {
"noImgElement": "off"
},
"security": {
"noDangerouslySetInnerHtml": "off"
}
}
},
"javascript": { "javascript": {
"formatter": { "formatter": {
"quoteStyle": "double" "quoteStyle": "double"
} }
}, },
"assist": { "linter": {
"domains": {
"next": "recommended",
"react": "recommended"
},
"enabled": true, "enabled": true,
"actions": { "rules": {
"source": { "a11y": {
"organizeImports": "on" "noAutofocus": "off",
"noNoninteractiveElementInteractions": "error",
"useSemanticElements": "off"
},
"complexity": {
"noUselessFragments": "off"
},
"correctness": {
"useExhaustiveDependencies": "off",
"useHookAtTopLevel": "off",
"useParseIntRadix": "off"
},
"performance": {
"noImgElement": "off"
},
"recommended": true,
"security": {
"noDangerouslySetInnerHtml": "off"
},
"suspicious": {
"noArrayIndexKey": "off",
"noUnknownAtRules": "off",
"useIterableCallbackReturn": "off"
} }
} }
},
"vcs": {
"clientKind": "git",
"enabled": true,
"root": "../../",
"useIgnoreFile": false
} }
} }

View file

@ -1,21 +1,21 @@
{ {
"$schema": "https://ui.shadcn.com/schema.json", "$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york", "aliases": {
"components": "~/components",
"hooks": "~/hooks",
"lib": "~/lib",
"ui": "~/components/ui",
"utils": "~/lib/utils"
},
"iconLibrary": "lucide",
"rsc": false, "rsc": false,
"tsx": true, "style": "new-york",
"tailwind": { "tailwind": {
"baseColor": "neutral",
"config": "", "config": "",
"css": "src/styles/global.css", "css": "src/styles/global.css",
"baseColor": "neutral",
"cssVariables": true, "cssVariables": true,
"prefix": "" "prefix": ""
}, },
"aliases": { "tsx": true
"components": "~/components",
"utils": "~/lib/utils",
"ui": "~/components/ui",
"lib": "~/lib",
"hooks": "~/hooks"
},
"iconLibrary": "lucide"
} }

View file

@ -13,7 +13,7 @@ type VerificaEmailProps = {
const VerificaEmail = ({ token, userId, redirect }: VerificaEmailProps) => { const VerificaEmail = ({ token, userId, redirect }: VerificaEmailProps) => {
return ( return (
<Base preview="Verifica la tua email" noreply> <Base noreply preview="Verifica la tua email">
<> <>
<Heading className="text-center text-3xl leading-8"> <Heading className="text-center text-3xl leading-8">
Verifica la tua email Verifica la tua email

View file

@ -42,13 +42,13 @@ const Base = ({
<Body className="py-2 text-center font-sans text-base text-neutral-700"> <Body className="py-2 text-center font-sans text-base text-neutral-700">
<Container className="max-w-xl rounded-lg border border-solid border-[#eaeaea] pt-2 pb-10"> <Container className="max-w-xl rounded-lg border border-solid border-[#eaeaea] pt-2 pb-10">
<Img <Img
referrerPolicy="no-referrer"
width={230.5}
height={44.25}
// src="https://lh3.google.com/u/0/d/1nEurjVWPiz0F8QRLhKcT4ZSujeFRChBs"
src="cid:logo"
alt="Infoalloggi.it" alt="Infoalloggi.it"
className="mx-auto my-2 scale-80" className="mx-auto my-2 scale-80"
height={44.25}
// src="https://lh3.google.com/u/0/d/1nEurjVWPiz0F8QRLhKcT4ZSujeFRChBs"
referrerPolicy="no-referrer"
src="cid:logo"
width={230.5}
/> />
<Section className="max-w-lg px-8"> <Section className="max-w-lg px-8">

View file

@ -22,7 +22,7 @@ const Generic = ({
link = undefined, link = undefined,
}: GenericProps) => { }: GenericProps) => {
return ( return (
<Base preview={title} noreply={noreply}> <Base noreply={noreply} preview={title}>
<> <>
<Heading className="text-center text-3xl leading-8">{title}</Heading> <Heading className="text-center text-3xl leading-8">{title}</Heading>
<Section className="px-5 text-left text-base md:px-12"> <Section className="px-5 text-left text-base md:px-12">

View file

@ -61,11 +61,11 @@ const OnboardingServizio = ({
<Section className="mb-[30px]" key={index}> <Section className="mb-[30px]" key={index}>
<Row className="mb-[12px]"> <Row className="mb-[12px]">
<Img <Img
src={`${process.env.NEXT_PUBLIC_BASE_URL}/go-api/images/get/${annuncio.immagine}`}
width="100%"
height="140px"
alt={`Annuncio image - ${annuncio.codice}`} alt={`Annuncio image - ${annuncio.codice}`}
className="block w-[240px] rounded-[4px] object-cover object-center" className="block w-[240px] rounded-[4px] object-cover object-center"
height="140px"
src={`${process.env.NEXT_PUBLIC_BASE_URL}/go-api/images/get/${annuncio.immagine}`}
width="100%"
/> />
</Row> </Row>
<Row className="block w-full sm:hidden"> <Row className="block w-full sm:hidden">
@ -83,8 +83,8 @@ const OnboardingServizio = ({
{(annuncio.prezzo / 1e2).toFixed(2)} {(annuncio.prezzo / 1e2).toFixed(2)}
</Text> </Text>
<Link <Link
href={`${process.env.NEXT_PUBLIC_BASE_URL}/annuncio/${annuncio.codice}`}
className="block text-[14px] font-semibold text-indigo-600 no-underline" className="block text-[14px] font-semibold text-indigo-600 no-underline"
href={`${process.env.NEXT_PUBLIC_BASE_URL}/annuncio/${annuncio.codice}`}
> >
Scheda dell&apos;annuncio Scheda dell&apos;annuncio
</Link> </Link>

View file

@ -1,6 +1,5 @@
{ {
"$schema": "https://unpkg.com/knip@5/schema.json", "$schema": "https://unpkg.com/knip@5/schema.json",
"project": ["src/**", "**", "!src/schemas/public/*.ts", "!emails/**"],
"ignore": [ "ignore": [
".kanelrc.js", ".kanelrc.js",
"TypeHelpers.type.ts", "TypeHelpers.type.ts",
@ -36,5 +35,6 @@
"@radix-ui/react-slot", "@radix-ui/react-slot",
"@radix-ui/react-tooltip", "@radix-ui/react-tooltip",
"@radix-ui/react-popover" "@radix-ui/react-popover"
] ],
"project": ["src/**", "**", "!src/schemas/public/*.ts", "!emails/**"]
} }

View file

@ -7,86 +7,86 @@ import { env } from "node:process";
/** @type {import("next").NextConfig} */ /** @type {import("next").NextConfig} */
const nextConfig = { const nextConfig = {
output: env.NODE_ENV === "production" ? "standalone" : undefined, // This is for docker builds
reactStrictMode: true,
compiler: { compiler: {
removeConsole: false, removeConsole: false,
}, },
i18n: {
locales: ["it", "en"],
defaultLocale: "it",
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "*.googleusercontent.com",
port: "",
pathname: "**",
},
{
hostname: env.BACKENDSERVER_URL || "localhost:1323",
},
],
minimumCacheTTL: 86400, // 1 day in seconds
},
typescript: {
ignoreBuildErrors: true,
},
devIndicators: false, devIndicators: false,
rewrites: async () => {
return [
{
source: "/api/panel",
destination: env.NODE_ENV === "production" ? "/404" : "/api/panel",
},
{
source: "/go-api/images/get/:slug*",
destination: `${env.BACKENDSERVER_URL}/images/get/:slug*`,
},
{
source: "/go-api/storage/get/:slug*",
destination: `${env.BACKENDSERVER_URL}/storage/get/:slug*`,
has: [
{ type: "cookie", key: "access_token" },
{
type: "query",
key: "token",
value:
"(?<token>^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$)",
},
],
},
{
source: "/go-api/storage/upload:slug*",
destination: `${env.BACKENDSERVER_URL}/storage/upload:slug*`,
has: [
{ type: "cookie", key: "access_token" },
{
type: "query",
key: "token",
value:
"(?<token>^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$)",
},
],
},
];
},
headers: async () => { headers: async () => {
return [ return [
{ {
source: "/:path*",
headers: [ headers: [
{ {
key: "X-Frame-Options", key: "X-Frame-Options",
value: "", value: "",
}, },
], ],
source: "/:path*",
}, },
]; ];
}, },
i18n: {
defaultLocale: "it",
locales: ["it", "en"],
},
images: {
minimumCacheTTL: 86400, // 1 day in seconds
remotePatterns: [
{
hostname: "*.googleusercontent.com",
pathname: "**",
port: "",
protocol: "https",
},
{
hostname: env.BACKENDSERVER_URL || "localhost:1323",
},
],
},
output: env.NODE_ENV === "production" ? "standalone" : undefined, // This is for docker builds
reactStrictMode: true,
rewrites: async () => {
return [
{
destination: env.NODE_ENV === "production" ? "/404" : "/api/panel",
source: "/api/panel",
},
{
destination: `${env.BACKENDSERVER_URL}/images/get/:slug*`,
source: "/go-api/images/get/:slug*",
},
{
destination: `${env.BACKENDSERVER_URL}/storage/get/:slug*`,
has: [
{ key: "access_token", type: "cookie" },
{
key: "token",
type: "query",
value:
"(?<token>^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$)",
},
],
source: "/go-api/storage/get/:slug*",
},
{
destination: `${env.BACKENDSERVER_URL}/storage/upload:slug*`,
has: [
{ key: "access_token", type: "cookie" },
{
key: "token",
type: "query",
value:
"(?<token>^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$)",
},
],
source: "/go-api/storage/upload:slug*",
},
];
},
typescript: {
ignoreBuildErrors: true,
},
}; };
export default nextConfig; export default nextConfig;

View file

@ -1,22 +1,6 @@
{ {
"name": "infoalloggi", "ct3aMetadata": {
"version": "0.1.0", "initVersion": "7.9.0"
"private": true,
"scripts": {
"build": "next build",
"dev": "node ./headers/dev.js && next dev --turbopack",
"dev-https": "node ./headers/dev-https.js && cross-env NEXT_PUBLIC_BASE_URL='https://localhost:3000' INTERNAL_BASE_URL='https://localhost:3000' next dev --experimental-https --turbopack",
"idev": "cross-env NODE_OPTIONS='--inspect' next dev --turbopack",
"lint": "node ./headers/lint.js && biome lint && node check-env-vars.js",
"start": "next start",
"types": "node ./headers/type.js && tsc --noEmit",
"pg-typegen": "node ./headers/pg-typegen.js && npx kanel",
"email": "cross-env NEXT_PUBLIC_BASE_URL='https://localhost:3000' email dev --port 3500",
"test": "npm run lint && npm run types",
"knip": "knip --no-exit-code",
"stripe-dev": "stripe listen --forward-to https://localhost:3000/api/stripe-webhook",
"d": "npm run-p dev stripe-dev -cl",
"test-kr": "node ./kyselyRules/rules/select.test.js && node ./kyselyRules/rules/where.test.js&& node ./kyselyRules/rules/null.test.js"
}, },
"dependencies": { "dependencies": {
"@fattureincloud/fattureincloud-ts-sdk": "^2.1.1", "@fattureincloud/fattureincloud-ts-sdk": "^2.1.1",
@ -122,7 +106,23 @@
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"typescript": "^5.9.2" "typescript": "^5.9.2"
}, },
"ct3aMetadata": { "name": "infoalloggi",
"initVersion": "7.9.0" "private": true,
} "scripts": {
"build": "next build",
"d": "npm run-p dev stripe-dev -cl",
"dev": "node ./headers/dev.js && next dev --turbopack",
"dev-https": "node ./headers/dev-https.js && cross-env NEXT_PUBLIC_BASE_URL='https://localhost:3000' INTERNAL_BASE_URL='https://localhost:3000' next dev --experimental-https --turbopack",
"email": "cross-env NEXT_PUBLIC_BASE_URL='https://localhost:3000' email dev --port 3500",
"idev": "cross-env NODE_OPTIONS='--inspect' next dev --turbopack",
"knip": "knip --no-exit-code",
"lint": "node ./headers/lint.js && biome lint && node check-env-vars.js",
"pg-typegen": "node ./headers/pg-typegen.js && npx kanel",
"start": "next start",
"stripe-dev": "stripe listen --forward-to https://localhost:3000/api/stripe-webhook",
"test": "npm run lint && npm run types",
"test-kr": "node ./kyselyRules/rules/select.test.js && node ./kyselyRules/rules/where.test.js&& node ./kyselyRules/rules/null.test.js",
"types": "node ./headers/type.js && tsc --noEmit"
},
"version": "0.1.0"
} }

View file

@ -1,27 +1,27 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import "~/styles/globals.css"; import "~/styles/globals.css";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Home",
description: "Welcome to Next.js", description: "Welcome to Next.js",
icons: { icons: {
apple: { apple: {
url: "/favicon/apple-touch-icon.png",
sizes: "180x180", sizes: "180x180",
type: "image/png", type: "image/png",
url: "/favicon/apple-touch-icon.png",
}, },
icon: [ icon: [
{ url: "/favicon/favicon-32x32.png", sizes: "32x32", type: "image/png" }, { sizes: "32x32", type: "image/png", url: "/favicon/favicon-32x32.png" },
{ url: "/favicon/favicon-16x16.png", sizes: "16x16", type: "image/png" }, { sizes: "16x16", type: "image/png", url: "/favicon/favicon-16x16.png" },
], ],
other: [ other: [
{ {
color: "#5bbad5",
rel: "mask-icon", rel: "mask-icon",
url: "/favicon/safari-pinned-tab.svg", url: "/favicon/safari-pinned-tab.svg",
color: "#5bbad5",
}, },
], ],
}, },
manifest: "/site.webmanifest", manifest: "/site.webmanifest",
title: "Home",
}; };
export default function RootLayout({ export default function RootLayout({

View file

@ -216,15 +216,15 @@ export const IconMatrixTooltip = () => {
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
{iconOptions.map((icon) => ( {iconOptions.map((icon) => (
<button <button
type="button" className="bg-muted flex flex-col items-center rounded-xl p-2 hover:opacity-75"
key={icon}
onClick={async () => { onClick={async () => {
await navigator.clipboard.writeText(icon); await navigator.clipboard.writeText(icon);
toast("Copiato", { icon: "📋" }); toast("Copiato", { icon: "📋" });
}} }}
key={icon} type="button"
className="bg-muted flex flex-col items-center rounded-xl p-2 hover:opacity-75"
> >
<IconMatrix type={icon} className="size-6" /> <IconMatrix className="size-6" type={icon} />
<span className="text-sm">{icon}</span> <span className="text-sm">{icon}</span>
</button> </button>
))} ))}
@ -310,16 +310,16 @@ export const ShieldExclamationIcon = ({
className?: string; className?: string;
}) => ( }) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={className} className={className}
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
> >
<title>Shield</title> <title>Shield</title>
<path <path
fillRule="evenodd"
d="M11.484 2.17a.75.75 0 0 1 1.032 0 11.209 11.209 0 0 0 7.877 3.08.75.75 0 0 1 .722.515 12.74 12.74 0 0 1 .635 3.985c0 5.942-4.064 10.933-9.563 12.348a.749.749 0 0 1-.374 0C6.314 20.683 2.25 15.692 2.25 9.75c0-1.39.223-2.73.635-3.985a.75.75 0 0 1 .722-.516l.143.001c2.996 0 5.718-1.17 7.734-3.08ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75ZM12 15a.75.75 0 0 0-.75.75v.008c0 .414.336.75.75.75h.008a.75.75 0 0 0 .75-.75v-.008a.75.75 0 0 0-.75-.75H12Z"
clipRule="evenodd" clipRule="evenodd"
d="M11.484 2.17a.75.75 0 0 1 1.032 0 11.209 11.209 0 0 0 7.877 3.08.75.75 0 0 1 .722.515 12.74 12.74 0 0 1 .635 3.985c0 5.942-4.064 10.933-9.563 12.348a.749.749 0 0 1-.374 0C6.314 20.683 2.25 15.692 2.25 9.75c0-1.39.223-2.73.635-3.985a.75.75 0 0 1 .722-.516l.143.001c2.996 0 5.718-1.17 7.734-3.08ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75ZM12 15a.75.75 0 0 0-.75.75v.008c0 .414.336.75.75.75h.008a.75.75 0 0 0 .75-.75v-.008a.75.75 0 0 0-.75-.75H12Z"
fillRule="evenodd"
/> />
</svg> </svg>
); );
@ -330,49 +330,49 @@ export const QuestionMarkCircleIcon = ({
className?: string; className?: string;
}) => ( }) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={className} className={className}
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
> >
<title>QuestionMark</title> <title>QuestionMark</title>
<path <path
fillRule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm11.378-3.917c-.89-.777-2.366-.777-3.255 0a.75.75 0 0 1-.988-1.129c1.454-1.272 3.776-1.272 5.23 0 1.513 1.324 1.513 3.518 0 4.842a3.75 3.75 0 0 1-.837.552c-.676.328-1.028.774-1.028 1.152v.75a.75.75 0 0 1-1.5 0v-.75c0-1.279 1.06-2.107 1.875-2.502.182-.088.351-.199.503-.331.83-.727.83-1.857 0-2.584ZM12 18a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
clipRule="evenodd" clipRule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm11.378-3.917c-.89-.777-2.366-.777-3.255 0a.75.75 0 0 1-.988-1.129c1.454-1.272 3.776-1.272 5.23 0 1.513 1.324 1.513 3.518 0 4.842a3.75 3.75 0 0 1-.837.552c-.676.328-1.028.774-1.028 1.152v.75a.75.75 0 0 1-1.5 0v-.75c0-1.279 1.06-2.107 1.875-2.502.182-.088.351-.199.503-.331.83-.727.83-1.857 0-2.584ZM12 18a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
fillRule="evenodd"
/> />
</svg> </svg>
); );
export const HomeIcon = ({ className }: { className?: string }) => ( export const HomeIcon = ({ className }: { className?: string }) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className={className} className={className}
fill="none"
stroke="currentColor"
strokeWidth={1.5}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
> >
<title>Home</title> <title>Home</title>
<path <path
d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
/> />
</svg> </svg>
); );
const CheckCircleIcon = ({ className }: { className?: string }) => ( const CheckCircleIcon = ({ className }: { className?: string }) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={className} className={className}
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
> >
<title>CheckCircle</title> <title>CheckCircle</title>
<path <path
fillRule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
clipRule="evenodd" clipRule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"
fillRule="evenodd"
/> />
</svg> </svg>
); );

View file

@ -6,14 +6,14 @@ export const ImageFlbk = (props: ComponentProps<typeof Image>) => {
return ( return (
<Image <Image
{...props} {...props}
onError={(e) => {
e.currentTarget.src = fallbackImage;
}}
onLoad={(e) => { onLoad={(e) => {
if (e.currentTarget.naturalWidth === 0) { if (e.currentTarget.naturalWidth === 0) {
e.currentTarget.src = fallbackImage; e.currentTarget.src = fallbackImage;
} }
}} }}
onError={(e) => {
e.currentTarget.src = fallbackImage;
}}
/> />
); );
}; };

View file

@ -59,20 +59,20 @@ const InformationBubble = ({
)} )}
> >
<IconMatrix <IconMatrix
type={icon}
className={cn("size-6 text-indigo-500", iconClassName)} className={cn("size-6 text-indigo-500", iconClassName)}
type={icon}
/> />
</HybridTooltipTrigger> </HybridTooltipTrigger>
<HybridTooltipContent <HybridTooltipContent
side={side}
className="border-border -mb-1.5 bg-white shadow-sm [&>span>svg]:invisible" className="border-border -mb-1.5 bg-white shadow-sm [&>span>svg]:invisible"
side={side}
> >
{href ? ( {href ? (
<Link <Link
className="flex items-center justify-center text-sm text-blue-500 underline-offset-2 hover:underline"
href={href} href={href}
target={target} target={target}
className="flex items-center justify-center text-sm text-blue-500 underline-offset-2 hover:underline"
> >
<ExternalLink className="mr-1 size-4" /> <ExternalLink className="mr-1 size-4" />
<p>{txt}</p> <p>{txt}</p>

View file

@ -145,8 +145,8 @@ export const AreaRiservataLayoutUserView = ({
<Head> <Head>
<title>{t.heads.area_riservata_titolo}</title> <title>{t.heads.area_riservata_titolo}</title>
<meta <meta
name="description"
content={t.heads.area_riservata_description} content={t.heads.area_riservata_description}
name="description"
/> />
</Head> </Head>
<div className="flex h-auto w-full flex-col md:flex-row"> <div className="flex h-auto w-full flex-col md:flex-row">
@ -191,8 +191,8 @@ const UserViewHeader = () => {
<Link href={`https://wa.me/${data.telefono}`} target="_blank"> <Link href={`https://wa.me/${data.telefono}`} target="_blank">
<Button <Button
className="flex items-center gap-2" className="flex items-center gap-2"
variant="success"
size="sm" size="sm"
variant="success"
> >
<WhatsAppIcon className="size-4 fill-white" /> <WhatsAppIcon className="size-4 fill-white" />
WhatsApp WhatsApp
@ -203,8 +203,8 @@ const UserViewHeader = () => {
<Link href={`/area-riservata/admin/user-view/edit-user/${data.id}`}> <Link href={`/area-riservata/admin/user-view/edit-user/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="flex items-center gap-2 border"
variant={pathname.includes("edit-user") ? "secondary" : "outline"}
size="sm" size="sm"
variant={pathname.includes("edit-user") ? "secondary" : "outline"}
> >
<UserCog /> Profilo <UserCog /> Profilo
</Button> </Button>
@ -212,8 +212,8 @@ const UserViewHeader = () => {
<Link href={`/area-riservata/admin/user-view/servizio/${data.id}`}> <Link href={`/area-riservata/admin/user-view/servizio/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="flex items-center gap-2 border"
variant={pathname.includes("servizio") ? "secondary" : "outline"}
size="sm" size="sm"
variant={pathname.includes("servizio") ? "secondary" : "outline"}
> >
<Tickets /> Servizi <Tickets /> Servizi
</Button> </Button>
@ -222,8 +222,8 @@ const UserViewHeader = () => {
<Link href={`/area-riservata/admin/user-view/ricerca/${data.id}`}> <Link href={`/area-riservata/admin/user-view/ricerca/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="flex items-center gap-2 border"
variant={pathname.includes("ricerca") ? "secondary" : "outline"}
size="sm" size="sm"
variant={pathname.includes("ricerca") ? "secondary" : "outline"}
> >
<Search /> Ricerca <Search /> Ricerca
</Button> </Button>
@ -231,10 +231,10 @@ const UserViewHeader = () => {
<Link href={`/area-riservata/admin/user-view/comunicazioni/${data.id}`}> <Link href={`/area-riservata/admin/user-view/comunicazioni/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="flex items-center gap-2 border"
size="sm"
variant={ variant={
pathname.includes("comunicazioni") ? "secondary" : "outline" pathname.includes("comunicazioni") ? "secondary" : "outline"
} }
size="sm"
> >
<Mail /> Comunicazioni <Mail /> Comunicazioni
</Button> </Button>
@ -243,8 +243,8 @@ const UserViewHeader = () => {
<Link href={`/area-riservata/admin/user-view/ordini/${data.id}`}> <Link href={`/area-riservata/admin/user-view/ordini/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="flex items-center gap-2 border"
variant={pathname.includes("ordini") ? "secondary" : "outline"}
size="sm" size="sm"
variant={pathname.includes("ordini") ? "secondary" : "outline"}
> >
<ShoppingBag /> Ordini <ShoppingBag /> Ordini
</Button> </Button>
@ -252,8 +252,8 @@ const UserViewHeader = () => {
<Link href={`/area-riservata/admin/user-view/allegati/${data.id}`}> <Link href={`/area-riservata/admin/user-view/allegati/${data.id}`}>
<Button <Button
className="flex items-center gap-2 border" className="flex items-center gap-2 border"
variant={pathname.includes("allegati") ? "secondary" : "outline"}
size="sm" size="sm"
variant={pathname.includes("allegati") ? "secondary" : "outline"}
> >
<Paperclip /> Allegati <Paperclip /> Allegati
</Button> </Button>
@ -272,21 +272,21 @@ const UserViewHeader = () => {
> >
<Button <Button
className="flex items-center gap-2 border" className="flex items-center gap-2 border"
variant={!data.chatid ? "ghost" : "outline"}
size="sm" size="sm"
variant={!data.chatid ? "ghost" : "outline"}
> >
<MessagesSquare /> Chat <MessagesSquare /> Chat
</Button> </Button>
</Link> </Link>
<Button <Button
size="sm"
className="flex gap-2" className="flex gap-2"
onClick={() => onClick={() =>
swap({ swap({
id: data.id, id: data.id,
}) })
} }
size="sm"
> >
<ExternalLink /> Login <ExternalLink /> Login
</Button> </Button>

View file

@ -27,7 +27,7 @@ export const MappaDialogFullscreen = ({
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="w-auto grow" disabled={!lat || !long}> <Button className="w-auto grow" disabled={!lat || !long}>
<MapIcon className="size-5" /> {t.mappa} <MapIcon className="size-5" /> {t.mappa}
@ -42,18 +42,18 @@ export const MappaDialogFullscreen = ({
{lat && long && ( {lat && long && (
<> <>
<MapComp <MapComp
height="100%"
markerType="home"
posix={{ posix={{
popupTxt:
"Posizione esatta fornita dopo la richiesta di contatto",
pos: { pos: {
lat: Number.parseFloat(lat), lat: Number.parseFloat(lat),
lng: Number.parseFloat(long), lng: Number.parseFloat(long),
}, },
popupTxt:
"Posizione esatta fornita dopo la richiesta di contatto",
}} }}
markerType="home"
scrollWheelZoom scrollWheelZoom
width="100%" width="100%"
height="100%"
zoom={14} zoom={14}
/> />
</> </>

View file

@ -38,7 +38,7 @@ export const AccordionComp = forwardRef<HTMLDivElement, AccordionCompProps>(
className={cn("mx-auto flex max-w-xl justify-center", className)} className={cn("mx-auto flex max-w-xl justify-center", className)}
ref={ref} ref={ref}
> >
<Accordion type="single" collapsible className="w-full"> <Accordion className="w-full" collapsible type="single">
{texts.map((text, index) => ( {texts.map((text, index) => (
<AccordionItem key={`ac-item-${index}`} value={`item-${index}`}> <AccordionItem key={`ac-item-${index}`} value={`item-${index}`}>
<AccordionTrigger className="cursor-pointer gap-4 text-left text-lg"> <AccordionTrigger className="cursor-pointer gap-4 text-left text-lg">

View file

@ -58,13 +58,13 @@ export const AcquistoProcessing = ({
</h3> </h3>
{!generatedIntent ? ( {!generatedIntent ? (
<PricePreparation <PricePreparation
packId={packId}
onProcedi={() => { onProcedi={() => {
setGeneratedIntent(true); setGeneratedIntent(true);
mutate({ mutate({
paymentId, paymentId,
}); });
}} }}
packId={packId}
/> />
) : isPending ? ( ) : isPending ? (
<LoadingPage /> <LoadingPage />
@ -85,9 +85,9 @@ export const AcquistoProcessing = ({
stripe={stripePromise} stripe={stripePromise}
> >
<CheckoutForm <CheckoutForm
titolo={stripeIntent.titolo}
prezzo={stripeIntent.prezzo}
descrizione={stripeIntent.descrizione} descrizione={stripeIntent.descrizione}
prezzo={stripeIntent.prezzo}
titolo={stripeIntent.titolo}
/> />
</Elements> </Elements>
</> </>
@ -136,9 +136,9 @@ export const PricePreparation = ({
{prezziario.testo_condizioni && ( {prezziario.testo_condizioni && (
<Link <Link
aria-label="Leggi le condizioni" aria-label="Leggi le condizioni"
className="block w-fit"
href={`/servizio/condizioni/${prezziario.testo_condizioni}`} href={`/servizio/condizioni/${prezziario.testo_condizioni}`}
target="_blank" target="_blank"
className="block w-fit"
> >
<Button className="flex w-full items-center gap-2 text-sm"> <Button className="flex w-full items-center gap-2 text-sm">
<span>{t.acquisto.leggi_condizioni}</span> <span>{t.acquisto.leggi_condizioni}</span>
@ -148,11 +148,11 @@ export const PricePreparation = ({
)} )}
<Button <Button
onClick={onProcedi}
className="flex w-full items-center gap-2 text-lg" className="flex w-full items-center gap-2 text-lg"
variant="info"
size="xl"
disabled={isLoading || !prezziario} disabled={isLoading || !prezziario}
onClick={onProcedi}
size="xl"
variant="info"
> >
<span>{t.acquisto.procedi_pagamento}</span> <ArrowRight /> <span>{t.acquisto.procedi_pagamento}</span> <ArrowRight />
</Button> </Button>
@ -188,10 +188,10 @@ const CheckoutForm = ({
setIsLoading(true); setIsLoading(true);
const { error } = await stripe.confirmPayment({ const { error } = await stripe.confirmPayment({
elements,
confirmParams: { confirmParams: {
return_url: `${env.NEXT_PUBLIC_BASE_URL}/servizio/acquisto-processing`, return_url: `${env.NEXT_PUBLIC_BASE_URL}/servizio/acquisto-processing`,
}, },
elements,
}); });
if (error.type === "card_error" || error.type === "validation_error") { if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message || "An unexpected error occurred."); setMessage(error.message || "An unexpected error occurred.");
@ -218,7 +218,7 @@ const CheckoutForm = ({
</span> </span>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form id="payment-form" onSubmit={handleSubmit} className="space-y-5"> <form className="space-y-5" id="payment-form" onSubmit={handleSubmit}>
<PaymentElement <PaymentElement
id="payment-element" id="payment-element"
options={{ layout: "accordion" }} options={{ layout: "accordion" }}
@ -226,10 +226,10 @@ const CheckoutForm = ({
<LoadingButton <LoadingButton
aria-label="Procedi al pagamento" aria-label="Procedi al pagamento"
type="submit"
className="w-full" className="w-full"
disabled={isLoading || !stripe || !elements} disabled={isLoading || !stripe || !elements}
loading={isLoading || !stripe || !elements} loading={isLoading || !stripe || !elements}
type="submit"
> >
{t.procedi} {t.procedi}
</LoadingButton> </LoadingButton>

View file

@ -22,7 +22,7 @@ export function ReceiptGenerator({ data }: { data: PurchaseData }) {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Ricevuta di cortesia</h1> <h1 className="text-2xl font-bold">Ricevuta di cortesia</h1>
<Button onClick={() => reactToPrintFn()} className="print:hidden"> <Button className="print:hidden" onClick={() => reactToPrintFn()}>
Stampa Stampa
</Button> </Button>
</div> </div>
@ -79,10 +79,10 @@ function Receipt({ data }: ReceiptProps) {
<div className="relative size-24"> <div className="relative size-24">
<Image <Image
src={"/favicon/favicon.png"}
alt={`${companyName} logo`} alt={`${companyName} logo`}
fill
className="object-contain" className="object-contain"
fill
src={"/favicon/favicon.png"}
/> />
</div> </div>
</div> </div>
@ -131,7 +131,7 @@ function Receipt({ data }: ReceiptProps) {
</thead> </thead>
<tbody> <tbody>
{data.items.map((item, idx) => ( {data.items.map((item, idx) => (
<tr key={idx} className="border-b"> <tr className="border-b" key={idx}>
<td className="py-2">{item.description}</td> <td className="py-2">{item.description}</td>
<td className="py-2 text-right"> <td className="py-2 text-right">
{formatCurrency(item.price)} {formatCurrency(item.price)}

View file

@ -45,12 +45,12 @@ export const AllegatoIframe = ({
> >
{/** biome-ignore lint/a11y/noNoninteractiveElementInteractions: <need interaction> */} {/** biome-ignore lint/a11y/noNoninteractiveElementInteractions: <need interaction> */}
<iframe <iframe
title="Allegato" allowFullScreen
ref={if_ref}
src={requestUrl}
className="h-full w-full" className="h-full w-full"
onLoad={handleIframeLoad} onLoad={handleIframeLoad}
allowFullScreen ref={if_ref}
src={requestUrl}
title="Allegato"
/> />
</div> </div>
); );

View file

@ -10,23 +10,23 @@ export const AnnunciGrid = ({ pagedata }: { pagedata: AnnuncioRicerca[] }) => {
<div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"> <div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{pagedata.map((annuncio) => ( {pagedata.map((annuncio) => (
<CardAnnuncio <CardAnnuncio
id={annuncio.id} camere={annuncio.numero_camere}
key={annuncio.codice}
codice={annuncio.codice} codice={annuncio.codice}
comune={annuncio.comune}
consegna={annuncio.consegna}
id={annuncio.id}
immagini={annuncio.url_immagini || undefined}
key={annuncio.codice}
mq={annuncio.mq}
prezzo={annuncio.prezzo} prezzo={annuncio.prezzo}
provincia={annuncio.provincia}
stato={annuncio.stato}
tipo={annuncio.tipo}
titolo={getTitoloTranslation({ titolo={getTitoloTranslation({
locale: locale, locale: locale,
titolo_it: annuncio.titolo_it,
titolo_en: annuncio.titolo_en, titolo_en: annuncio.titolo_en,
titolo_it: annuncio.titolo_it,
})} })}
mq={annuncio.mq}
comune={annuncio.comune}
provincia={annuncio.provincia}
consegna={annuncio.consegna}
camere={annuncio.numero_camere}
immagini={annuncio.url_immagini || undefined}
tipo={annuncio.tipo}
stato={annuncio.stato}
/> />
))} ))}
</div> </div>

View file

@ -30,9 +30,9 @@ export const MapSection = () => {
return ( return (
<div className="relative z-50 flex h-[60vh] w-full items-center justify-center sm:h-[65vh]"> <div className="relative z-50 flex h-[60vh] w-full items-center justify-center sm:h-[65vh]">
<MapDisplay <MapDisplay
selectTipo={tipo}
selectComune={comune} selectComune={comune}
selectConsegna={consegna} selectConsegna={consegna}
selectTipo={tipo}
setSelected={setSelected} setSelected={setSelected}
/> />
<SelectedComp selected={selected} /> <SelectedComp selected={selected} />
@ -53,9 +53,9 @@ const MapDisplay = memo(
setSelected: Dispatch<SetStateAction<AnnuncioRicercaWPosition | null>>; setSelected: Dispatch<SetStateAction<AnnuncioRicercaWPosition | null>>;
}) => { }) => {
const { data, isLoading } = api.annunci.getMapping.useQuery({ const { data, isLoading } = api.annunci.getMapping.useQuery({
tipologia: selectTipo,
comune: selectComune, comune: selectComune,
consegna: selectConsegna || undefined, consegna: selectConsegna || undefined,
tipologia: selectTipo,
}); });
if (isLoading) return <LoadingPage />; if (isLoading) return <LoadingPage />;
@ -65,12 +65,12 @@ const MapDisplay = memo(
.map((a) => { .map((a) => {
if (a.lat_secondario && a.lon_secondario) { if (a.lat_secondario && a.lon_secondario) {
return { return {
markerTxt: `${a.prezzo / 1e2}`,
onClick: () => setSelected(a),
pos: { pos: {
lat: parseFloat(a.lat_secondario), lat: parseFloat(a.lat_secondario),
lng: parseFloat(a.lon_secondario), lng: parseFloat(a.lon_secondario),
}, },
onClick: () => setSelected(a),
markerTxt: `${a.prezzo / 1e2}`,
}; };
} }
return undefined; return undefined;
@ -79,15 +79,15 @@ const MapDisplay = memo(
return ( return (
<MapComp <MapComp
isGroup
groupData={posizioni} groupData={posizioni}
posix={{ pos: { lat: 45.764718, lng: 11.73067 } }}
width="100%"
height="100%" height="100%"
zoom={13} isGroup
scrollWheelZoom
markerType="price" markerType="price"
onMapClick={() => setSelected(null)} onMapClick={() => setSelected(null)}
posix={{ pos: { lat: 45.764718, lng: 11.73067 } }}
scrollWheelZoom
width="100%"
zoom={13}
/> />
); );
}, },
@ -104,27 +104,27 @@ const SelectedComp = memo(
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{selected && ( {selected && (
<motion.a <motion.a
aria-label="Visualizza Dettagli Annuncio"
href={`/annuncio/${selected.codice}`}
target="_blank"
className="group absolute right-0 bottom-0 left-0 z-20 mx-2 mb-2 flex flex-col gap-2 rounded-md bg-white p-4 ring-neutral-500 hover:ring-1"
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
aria-label="Visualizza Dettagli Annuncio"
className="group absolute right-0 bottom-0 left-0 z-20 mx-2 mb-2 flex flex-col gap-2 rounded-md bg-white p-4 ring-neutral-500 hover:ring-1"
exit={{ opacity: 0, scale: 0 }} exit={{ opacity: 0, scale: 0 }}
href={`/annuncio/${selected.codice}`}
initial={{ opacity: 0, scale: 0 }}
key={selected.id}
target="_blank"
transition={{ transition={{
duration: 0.1, duration: 0.1,
scale: { type: "spring", visualDuration: 0.1, bounce: 0.2 }, scale: { bounce: 0.2, type: "spring", visualDuration: 0.1 },
}} }}
key={selected.id}
> >
<div className="flex gap-2"> <div className="flex gap-2">
{selected.url_immagini?.[0] ? ( {selected.url_immagini?.[0] ? (
<Image <Image
alt="a"
className="size-24 rounded-md object-cover sm:size-40"
height={500}
src={selected.url_immagini[0]} src={selected.url_immagini[0]}
width={500} width={500}
height={500}
className="size-24 rounded-md object-cover sm:size-40"
alt="a"
/> />
) : ( ) : (
<div className="size-20 bg-gray-200 sm:size-40" /> <div className="size-20 bg-gray-200 sm:size-40" />
@ -142,11 +142,11 @@ const SelectedComp = memo(
</div> </div>
<p> <p>
{handleConsegna({ {handleConsegna({
aggiornamento: t.card.in_aggiornamento,
consegna: selected.consegna, consegna: selected.consegna,
consegna_da: t.card.consegna_da, consegna_da: t.card.consegna_da,
mesi: t.preferenze.mesi, mesi: t.preferenze.mesi,
subito: t.card.consegna_subito, subito: t.card.consegna_subito,
aggiornamento: t.card.in_aggiornamento,
})} })}
</p> </p>
</div> </div>

View file

@ -45,16 +45,16 @@ export const CTA_TipologiaModal = () => {
return ( return (
<Dialog <Dialog
open={open}
onOpenChange={async (v) => { onOpenChange={async (v) => {
setOpen(v); setOpen(v);
if (!v) { if (!v) {
await update(null); await update(null);
} }
}} }}
open={open}
> >
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="info" className="flex items-center gap-2"> <Button className="flex items-center gap-2" variant="info">
<CircleHelp />{" "} <CircleHelp />{" "}
<span className="hidden sm:block"> <span className="hidden sm:block">
{t.tipologia_tutorial.btn_title} {t.tipologia_tutorial.btn_title}
@ -78,8 +78,8 @@ export const CTA_TipologiaModal = () => {
<p>{t.tipologia_tutorial.transitorio_desc}</p> <p>{t.tipologia_tutorial.transitorio_desc}</p>
<Button <Button
aria-label={t.tipologia_tutorial.transitorio_cta} aria-label={t.tipologia_tutorial.transitorio_cta}
onClick={async () => await update("Transitorio")}
className="bg-transitorio flex w-full items-center gap-2" className="bg-transitorio flex w-full items-center gap-2"
onClick={async () => await update("Transitorio")}
> >
{t.tipologia_tutorial.transitorio_cta} {t.tipologia_tutorial.transitorio_cta}
@ -93,8 +93,8 @@ export const CTA_TipologiaModal = () => {
<p>{t.tipologia_tutorial.stabile_desc}</p> <p>{t.tipologia_tutorial.stabile_desc}</p>
<Button <Button
aria-label={t.tipologia_tutorial.stabile_cta} aria-label={t.tipologia_tutorial.stabile_cta}
onClick={async () => await update("Stabile")}
className="bg-stabile flex w-full items-center gap-2" className="bg-stabile flex w-full items-center gap-2"
onClick={async () => await update("Stabile")}
> >
{t.tipologia_tutorial.stabile_cta} {t.tipologia_tutorial.stabile_cta}
@ -104,9 +104,9 @@ export const CTA_TipologiaModal = () => {
<Button <Button
aria-label={t.tipologia_tutorial.alone} aria-label={t.tipologia_tutorial.alone}
variant="outline"
onClick={async () => await update(null)}
className="flex w-full items-center gap-2" className="flex w-full items-center gap-2"
onClick={async () => await update(null)}
variant="outline"
> >
{t.tipologia_tutorial.alone} {t.tipologia_tutorial.alone}
</Button> </Button>

View file

@ -34,9 +34,9 @@ export const AnnuncioInteractions = ({
{session.user ? ( {session.user ? (
tipo ? ( tipo ? (
<ServizioInteraction <ServizioInteraction
userId={session.user.id}
tipologia={tipo}
annuncioId={id} annuncioId={id}
tipologia={tipo}
userId={session.user.id}
/> />
) : ( ) : (
<span>Errore</span> <span>Errore</span>
@ -72,12 +72,12 @@ const ServizioInteraction = ({
}) => { }) => {
const { data: servizio, isLoading } = const { data: servizio, isLoading } =
api.servizio.AnnuncioServizioTipologiaMatch.useQuery({ api.servizio.AnnuncioServizioTipologiaMatch.useQuery({
userId,
tipologia,
annuncioId, annuncioId,
tipologia,
userId,
}); });
if (isLoading) return <LoadingButton loading className="w-full" />; if (isLoading) return <LoadingButton className="w-full" loading />;
if ( if (
servizio === undefined || servizio === undefined ||
servizio.status === "invalid" || servizio.status === "invalid" ||
@ -97,10 +97,10 @@ const ServizioInteraction = ({
Annuncio già salvato! Annuncio già salvato!
</div> </div>
<Link <Link
href={`/area-riservata/dashboard`}
aria-label="Vai alla tua area riservata" aria-label="Vai alla tua area riservata"
href={`/area-riservata/dashboard`}
> >
<Button variant="success" className="flex w-full items-center gap-2"> <Button className="flex w-full items-center gap-2" variant="success">
Vai alla tua area riservata <ArrowRight /> Vai alla tua area riservata <ArrowRight />
</Button> </Button>
</Link> </Link>
@ -149,15 +149,15 @@ export const InteressatoButton = ({
return ( return (
<Button <Button
className="flex w-full items-center gap-2"
variant="info"
//size="xl"
aria-label="Sono interessato" aria-label="Sono interessato"
className="flex w-full items-center gap-2"
//size="xl"
onClick={() => { onClick={() => {
add({ add({
annuncioId, annuncioId,
}); });
}} }}
variant="info"
> >
<BadgePlus className="size-6" /> <BadgePlus className="size-6" />
Sono interessato Sono interessato
@ -191,15 +191,15 @@ export const InteressatoButtonBatchV = ({
return ( return (
<Button <Button
className="flex w-full items-center gap-2"
variant="info"
//size="xl"
aria-label="Sono interessato" aria-label="Sono interessato"
className="flex w-full items-center gap-2"
//size="xl"
onClick={() => { onClick={() => {
add({ add({
annuncioId, annuncioId,
}); });
}} }}
variant="info"
> >
<BadgePlus className="size-6" /> <BadgePlus className="size-6" />
Sono interessato Sono interessato

View file

@ -19,8 +19,8 @@ export const ContattoAnnuncio = () => {
<Credenza> <Credenza>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
variant="destructive"
className="flex w-full items-center gap-2" className="flex w-full items-center gap-2"
variant="destructive"
> >
<MessageCircleQuestion className="size-6" /> Contattaci <MessageCircleQuestion className="size-6" /> Contattaci
</Button> </Button>

View file

@ -63,9 +63,9 @@ export const CardAnnuncio = ({
id={`card-annuncio-${id}`} id={`card-annuncio-${id}`}
> >
<Link <Link
href={`/annuncio/${codice}`} className="block p-2"
//target="_blank" //target="_blank"
className="block p-2" //duration-700 ease-in-out animate-in fade-in href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
> >
<div className="group relative rounded-xl text-clip"> <div className="group relative rounded-xl text-clip">
{stato === "Trattativa" && ( {stato === "Trattativa" && (
@ -84,24 +84,24 @@ export const CardAnnuncio = ({
<CarouselItem key={`${img}-${idx}`}> <CarouselItem key={`${img}-${idx}`}>
<ImageFlbk <ImageFlbk
alt={t.card.alt_immagine} alt={t.card.alt_immagine}
width={800}
height={400}
src={img}
priority={idx === 0}
className={ className={
"h-56 w-full rounded-xl object-cover outline outline-neutral-200" "h-56 w-full rounded-xl object-cover outline outline-neutral-200"
} }
height={400}
priority={idx === 0}
src={img}
width={800}
/> />
</CarouselItem> </CarouselItem>
))} ))}
</CarouselContent> </CarouselContent>
<div <div
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
onClick={(e) => e.preventDefault()}
onKeyDown={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}
role="button" role="button"
tabIndex={0} tabIndex={0}
onClick={(e) => e.preventDefault()}
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
> >
<CarouselPrevious <CarouselPrevious
className="left-2 cursor-pointer" className="left-2 cursor-pointer"
@ -187,11 +187,11 @@ export const CardAnnuncio = ({
<div> <div>
<p className="truncate font-medium"> <p className="truncate font-medium">
{handleConsegna({ {handleConsegna({
aggiornamento: t.card.in_aggiornamento,
consegna: consegna, consegna: consegna,
consegna_da: t.card.consegna_da, consegna_da: t.card.consegna_da,
mesi: t.preferenze.mesi, mesi: t.preferenze.mesi,
subito: t.card.consegna_subito, subito: t.card.consegna_subito,
aggiornamento: t.card.in_aggiornamento,
})} })}
</p> </p>
</div> </div>
@ -220,52 +220,52 @@ export const CarouselAnnuncio = ({
<> <>
<div className="mx-auto my-4 w-full rounded-md text-clip"> <div className="mx-auto my-4 w-full rounded-md text-clip">
<div className="relative"> <div className="relative">
<Carousel opts={{ loop: true, align: "start" }}> <Carousel opts={{ align: "start", loop: true }}>
<CarouselContent> <CarouselContent>
{immagini?.map((img, idx) => ( {immagini?.map((img, idx) => (
<CarouselItem <CarouselItem
key={`${img}-${idx}-cF`}
className={cn( className={cn(
"group relative", "group relative",
immagini && immagini.length === 1 immagini && immagini.length === 1
? "" ? ""
: !single && "md:basis-1/2 lg:basis-1/3", : !single && "md:basis-1/2 lg:basis-1/3",
)} )}
key={`${img}-${idx}-cF`}
> >
<ImageFlbk <ImageFlbk
priority={idx === 0}
alt={img} alt={img}
width={800}
height={400}
onClick={() => handleOpenModal(idx)}
src={img}
className={cn( className={cn(
"h-72 w-full rounded-md object-cover", "h-72 w-full rounded-md object-cover",
immagini.length === 1 && "object-contain", immagini.length === 1 && "object-contain",
)} )}
height={400}
onClick={() => handleOpenModal(idx)}
priority={idx === 0}
src={img}
width={800}
/> />
<Maximize2 <Maximize2
onClick={() => handleOpenModal(idx)}
className="absolute right-2 bottom-2 size-6 rounded-md bg-white/70 stroke-2 p-1 transition-opacity duration-150 group-hover:opacity-100 sm:opacity-0" className="absolute right-2 bottom-2 size-6 rounded-md bg-white/70 stroke-2 p-1 transition-opacity duration-150 group-hover:opacity-100 sm:opacity-0"
onClick={() => handleOpenModal(idx)}
/> />
</CarouselItem> </CarouselItem>
))} ))}
</CarouselContent> </CarouselContent>
<CarouselPrevious <CarouselPrevious
type="button"
className="left-2 cursor-pointer opacity-60 disabled:opacity-0" className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1} disabled={!immagini || immagini.length === 1}
type="button"
/> />
<CarouselNext <CarouselNext
type="button"
className="right-2 cursor-pointer opacity-60 disabled:opacity-0" className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1} disabled={!immagini || immagini.length === 1}
type="button"
/> />
</Carousel> </Carousel>
</div> </div>
</div> </div>
<Dialog open={openModal} onOpenChange={setOpenModal}> <Dialog onOpenChange={setOpenModal} open={openModal}>
<DialogContent className="flex h-full w-full max-w-full items-center justify-center border-none border-transparent bg-white p-0 md:max-w-[80vw] md:p-6 [&_#dialog-close>svg]:size-8"> <DialogContent className="flex h-full w-full max-w-full items-center justify-center border-none border-transparent bg-white p-0 md:max-w-[80vw] md:p-6 [&_#dialog-close>svg]:size-8">
<DialogHeader className="absolute top-0 right-0 left-0 z-10"> <DialogHeader className="absolute top-0 right-0 left-0 z-10">
<DialogTitle className="sr-only">Image Dialog</DialogTitle> <DialogTitle className="sr-only">Image Dialog</DialogTitle>
@ -277,24 +277,24 @@ export const CarouselAnnuncio = ({
<CarouselItem key={`${img}-${idx}-cD`}> <CarouselItem key={`${img}-${idx}-cD`}>
<Image <Image
alt={`carousel-img-${idx}`} alt={`carousel-img-${idx}`}
width={1920} className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw]"
height={1080} height={1080}
src={img} src={img}
className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw]" width={1920}
/> />
</CarouselItem> </CarouselItem>
))} ))}
</CarouselContent> </CarouselContent>
<CarouselPrevious <CarouselPrevious
type="button"
className="left-2 cursor-pointer opacity-60 disabled:opacity-0" className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1} disabled={!immagini || immagini.length === 1}
type="button"
/> />
<CarouselNext <CarouselNext
type="button"
className="right-2 cursor-pointer opacity-60 disabled:opacity-0" className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1} disabled={!immagini || immagini.length === 1}
type="button"
/> />
</Carousel> </Carousel>
</DialogContent> </DialogContent>

View file

@ -128,24 +128,24 @@ export const AllegatiComp = ({
<h1 className="text-xl font-semibold">{t.allegati.da_info}</h1> <h1 className="text-xl font-semibold">{t.allegati.da_info}</h1>
{from_admin && ( {from_admin && (
<FileList <FileList
files={from_admin} delete_onClick={(id) => deleteFile({ fileId: id, userId })}
isAdmin={isAdmin}
selectHandler={(id, filename) => {
setEditData({ id, filename });
setEditOpen(true);
}}
delete_onClick={(id) => deleteFile({ userId, fileId: id })}
download_onClick={(file) => download_onClick={(file) =>
handleDownload({ handleDownload({
file, file,
getToken: async () => await getToken(), getToken: async () => await getToken(),
}) })
} }
files={from_admin}
isAdmin={isAdmin}
selectHandler={(id, filename) => {
setEditData({ filename, id });
setEditOpen(true);
}}
/> />
)} )}
<Separator /> <Separator />
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<UploadModal userId={userId} isAdmin={isAdmin} />{" "} <UploadModal isAdmin={isAdmin} userId={userId} />{" "}
<span className="text-sm">Max 5 MB per file</span> <span className="text-sm">Max 5 MB per file</span>
</div> </div>
<div className="space-y-2 rounded-md border border-orange-500 p-2"> <div className="space-y-2 rounded-md border border-orange-500 p-2">
@ -158,26 +158,26 @@ export const AllegatiComp = ({
{not_from_admin && ( {not_from_admin && (
<FileList <FileList
files={not_from_admin} delete_onClick={(id) => deleteFile({ fileId: id, userId })}
isAdmin={isAdmin}
selectHandler={(id, filename) => {
setEditData({ id, filename });
setEditOpen(true);
}}
delete_onClick={(id) => deleteFile({ userId, fileId: id })}
download_onClick={(file) => download_onClick={(file) =>
handleDownload({ handleDownload({
file, file,
getToken: async () => await getToken(), getToken: async () => await getToken(),
}) })
} }
files={not_from_admin}
isAdmin={isAdmin}
selectHandler={(id, filename) => {
setEditData({ filename, id });
setEditOpen(true);
}}
/> />
)} )}
<EditFileDialog <EditFileDialog
open={editOpen}
onOpenChange={setEditOpen}
data={editData} data={editData}
handleEditSubmit={handleEditSubmit} handleEditSubmit={handleEditSubmit}
onOpenChange={setEditOpen}
open={editOpen}
/> />
</div> </div>
); );
@ -241,35 +241,35 @@ const EditFileDialog = ({
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog onOpenChange={onOpenChange} open={open}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>{t.modifica}</DialogTitle> <DialogTitle>{t.modifica}</DialogTitle>
<DialogDescription className="sr-only">Edit</DialogDescription> <DialogDescription className="sr-only">Edit</DialogDescription>
</DialogHeader> </DialogHeader>
{data && ( {data && (
<form onSubmit={handleEditSubmit} method="post"> <form method="post" onSubmit={handleEditSubmit}>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<label htmlFor="id" className="sr-only"> <label className="sr-only" htmlFor="id">
ID ID
</label> </label>
<Input <Input
type="text"
className="hidden" className="hidden"
name="id" name="id"
value={data.id}
readOnly readOnly
type="text"
value={data.id}
/> />
<label htmlFor="filename" className="sr-only"> <label className="sr-only" htmlFor="filename">
filename filename
</label> </label>
<Input <Input
type="text"
placeholder="Modifica messaggio"
className="mt-0" className="mt-0"
defaultValue={data.filename || undefined}
id="filename" id="filename"
name="filename" name="filename"
defaultValue={data.filename || undefined} placeholder="Modifica messaggio"
type="text"
/> />
<Button type="submit">{t.salva}</Button> <Button type="submit">{t.salva}</Button>
</div> </div>
@ -306,8 +306,8 @@ function FileList({
<div className="divide-y"> <div className="divide-y">
{files.map((file) => ( {files.map((file) => (
<div <div
key={file.id}
className="hover:bg-muted/50 grid grid-cols-12 items-center gap-2 p-4 text-sm transition-colors" className="hover:bg-muted/50 grid grid-cols-12 items-center gap-2 p-4 text-sm transition-colors"
key={file.id}
> >
<div className="col-span-6 flex items-center gap-2 md:col-span-8"> <div className="col-span-6 flex items-center gap-2 md:col-span-8">
<div className="shrink-0"> <div className="shrink-0">
@ -315,9 +315,9 @@ function FileList({
</div> </div>
<Link <Link
aria-label="Allegato" aria-label="Allegato"
className="text-foreground truncate font-medium hover:underline"
href={`/area-riservata/allegato-view/${file.id}`} href={`/area-riservata/allegato-view/${file.id}`}
target="_blank" target="_blank"
className="text-foreground truncate font-medium hover:underline"
> >
{file.filename} {file.filename}
</Link> </Link>
@ -329,16 +329,16 @@ function FileList({
<div className="col-span-2 flex justify-end gap-1 md:col-span-1"> <div className="col-span-2 flex justify-end gap-1 md:col-span-1">
<Button <Button
aria-label="Download" aria-label="Download"
variant="ghost"
size="icon"
asChild asChild
className="size-8" className="size-8"
size="icon"
title="Download" title="Download"
variant="ghost"
> >
<Button <Button
variant="outline"
className="flex gap-1 text-sm" className="flex gap-1 text-sm"
onClick={() => download_onClick(file)} onClick={() => download_onClick(file)}
variant="outline"
> >
<Download className="size-4" /> <Download className="size-4" />
</Button> </Button>
@ -347,7 +347,7 @@ function FileList({
{isAdmin && ( {isAdmin && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="size-8"> <Button className="size-8" size="icon" variant="ghost">
<MoreVertical className="size-4" /> <MoreVertical className="size-4" />
<span className="sr-only">More options</span> <span className="sr-only">More options</span>
</Button> </Button>
@ -364,8 +364,8 @@ function FileList({
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
aria-label="Elimina" aria-label="Elimina"
onClick={() => delete_onClick(file.id)}
className="text-destructive focus:text-destructive" className="text-destructive focus:text-destructive"
onClick={() => delete_onClick(file.id)}
> >
<Trash2 className="mr-2 size-4" /> <Trash2 className="mr-2 size-4" />
Elimina Elimina

View file

@ -25,32 +25,32 @@ export const EmailAccordion = ({ userId }: { userId: UsersId }) => {
return ( return (
<div className="mx-auto py-4"> <div className="mx-auto py-4">
<Accordion <Accordion
type="single"
collapsible
className="space-y-4" className="space-y-4"
value={openIndex} collapsible
onValueChange={setOpenIndex} onValueChange={setOpenIndex}
type="single"
value={openIndex}
> >
{data.map((email, index) => ( {data.map((email, index) => (
<AccordionItem <AccordionItem
value={String(index)}
key={index}
className="space-y-2 border-none" className="space-y-2 border-none"
key={index}
value={String(index)}
> >
<AccordionTrigger className="rounded-md bg-muted px-4 py-2"> <AccordionTrigger className="rounded-md bg-muted px-4 py-2">
Email &ldquo;{email.title}&rdquo; del{" "} Email &ldquo;{email.title}&rdquo; del{" "}
{new Date(email.created_at).toLocaleDateString("it-IT", { {new Date(email.created_at).toLocaleDateString("it-IT", {
year: "numeric",
month: "2-digit",
day: "2-digit", day: "2-digit",
month: "2-digit",
year: "numeric",
})} })}
</AccordionTrigger> </AccordionTrigger>
<AccordionContent> <AccordionContent>
{openIndex === String(index) && ( {openIndex === String(index) && (
<iframe <iframe
title="Email Content"
srcDoc={email.html}
height={mobile ? "600px" : "500px"} height={mobile ? "600px" : "500px"}
srcDoc={email.html}
title="Email Content"
width="100%" width="100%"
/> />
)} )}

View file

@ -43,20 +43,20 @@ export const AdminDashboard = () => {
</CardContent> </CardContent>
</Card> </Card>
<TimeserieBarChart <TimeserieBarChart
title="Utenti registrati"
timeserie={stats.userTimeserie} timeserie={stats.userTimeserie}
title="Utenti registrati"
/> />
<TimeserieBarChartStacked <TimeserieBarChartStacked
timeserie={stats.serviziTimeserie.data}
title="Servizi creati" title="Servizi creati"
valueALabel={stats.serviziTimeserie.valueALabel} valueALabel={stats.serviziTimeserie.valueALabel}
valueBLabel={stats.serviziTimeserie.valueBLabel} valueBLabel={stats.serviziTimeserie.valueBLabel}
timeserie={stats.serviziTimeserie.data}
/> />
<TimeserieBarChart <TimeserieBarChart
title="Contatti generati"
timeserie={stats.contattiTimeserie} timeserie={stats.contattiTimeserie}
title="Contatti generati"
/> />
</div> </div>
)} )}
@ -79,7 +79,7 @@ export const UserDashboard = ({ userId }: { userId: UsersId }) => {
</div> </div>
<div className="flex flex-col space-y-5 px-2"> <div className="flex flex-col space-y-5 px-2">
<TabRicerca userId={userId} isAdmin={false} /> <TabRicerca isAdmin={false} userId={userId} />
{/* {loadingServizi ? ( {/* {loadingServizi ? (
<LoadingPage /> <LoadingPage />
) : ( ) : (
@ -94,8 +94,8 @@ export const UserDashboard = ({ userId }: { userId: UsersId }) => {
)} */} )} */}
</div> </div>
<AccordionComp <AccordionComp
texts={t.area_riservata.dash_accordion_minifaq}
className="w-full max-w-full px-2 pt-4" className="w-full max-w-full px-2 pt-4"
texts={t.area_riservata.dash_accordion_minifaq}
/> />
</div> </div>
</div> </div>

View file

@ -45,6 +45,9 @@ export const OrdiniModal = ({
}) => { }) => {
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate: remove } = api.servizio.removeOrder.useMutation({ const { mutate: remove } = api.servizio.removeOrder.useMutation({
onError: (error) => {
toast.error(`Errore durante la rimozione ordine: ${error.message}`);
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Ordine rimosso con successo!"); toast.success("Ordine rimosso con successo!");
await utils.servizio.getOrdini.invalidate({ await utils.servizio.getOrdini.invalidate({
@ -52,19 +55,19 @@ export const OrdiniModal = ({
}); });
setModalOpen(false); setModalOpen(false);
}, },
onError: (error) => {
toast.error(`Errore durante la rimozione ordine: ${error.message}`);
},
}); });
const { mutate: update } = api.servizio.updateOrder.useMutation({ const { mutate: update } = api.servizio.updateOrder.useMutation({
onError: (error) => {
toast.error(`Errore durante la modifica: ${error.message}`);
},
onSuccess: async (data) => { onSuccess: async (data) => {
if (data.type === OrderTypeEnum.Acconto && data.isActive) { if (data.type === OrderTypeEnum.Acconto && data.isActive) {
updateServizio({ updateServizio({
servizioId: data.servizio_id,
data: { data: {
isOkAcconto: true,
decorrenza: new Date(), decorrenza: new Date(),
isOkAcconto: true,
}, },
servizioId: data.servizio_id,
}); });
} }
toast.success("Ordine aggiornato con successo!"); toast.success("Ordine aggiornato con successo!");
@ -75,19 +78,16 @@ export const OrdiniModal = ({
ordineId, ordineId,
}); });
}, },
onError: (error) => {
toast.error(`Errore durante la modifica: ${error.message}`);
},
}); });
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({ const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio modificato con successo"); toast.success("Servizio modificato con successo");
await utils.servizio.getUserServizi.invalidate(); await utils.servizio.getUserServizi.invalidate();
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
const { data } = api.servizio.getOrdineById.useQuery({ const { data } = api.servizio.getOrdineById.useQuery({
@ -97,10 +97,10 @@ export const OrdiniModal = ({
return ( return (
<Credenza <Credenza
open={open}
onOpenChange={(b) => { onOpenChange={(b) => {
setModalOpen(b); setModalOpen(b);
}} }}
open={open}
> >
<CredenzaContent className="max-h-[90vh] max-w-2xl"> <CredenzaContent className="max-h-[90vh] max-w-2xl">
<CredenzaHeader> <CredenzaHeader>
@ -114,9 +114,9 @@ export const OrdiniModal = ({
<p> <p>
Data Ordine: Data Ordine:
{new Date(data.created_at).toLocaleDateString("it-IT", { {new Date(data.created_at).toLocaleDateString("it-IT", {
year: "numeric",
month: "2-digit",
day: "2-digit", day: "2-digit",
month: "2-digit",
year: "numeric",
})} })}
</p> </p>
<p>Tipologia: {data.type.toString()}</p> <p>Tipologia: {data.type.toString()}</p>
@ -145,7 +145,7 @@ export const OrdiniModal = ({
href={`/area-riservata/payment-recap/${data.ordine_id}`} href={`/area-riservata/payment-recap/${data.ordine_id}`}
target="_blank" target="_blank"
> >
<Button variant="outline" className="flex items-center gap-2"> <Button className="flex items-center gap-2" variant="outline">
Ricevuta di cortesia <ReceiptText /> Ricevuta di cortesia <ReceiptText />
</Button> </Button>
</Link> </Link>
@ -164,7 +164,7 @@ export const OrdiniModal = ({
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" aria-label="Modifica Status"> <Button aria-label="Modifica Status" variant="outline">
Modifica Status Modifica Status
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
@ -172,13 +172,13 @@ export const OrdiniModal = ({
<DropdownMenuLabel>Status</DropdownMenuLabel> <DropdownMenuLabel>Status</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
value={data.isActive ? "true" : "false"}
onValueChange={(v) => { onValueChange={(v) => {
update({ update({
ordineId,
data: { isActive: v === "true" }, data: { isActive: v === "true" },
ordineId,
}); });
}} }}
value={data.isActive ? "true" : "false"}
> >
<DropdownMenuRadioItem value="true"> <DropdownMenuRadioItem value="true">
Attivo Attivo
@ -191,13 +191,13 @@ export const OrdiniModal = ({
</DropdownMenu> </DropdownMenu>
<Confirm <Confirm
title="Elimina ordine"
description="Sei sicuro di voler eliminare l'ordine?" description="Sei sicuro di voler eliminare l'ordine?"
onConfirm={() => { onConfirm={() => {
remove({ ordineId }); remove({ ordineId });
}} }}
title="Elimina ordine"
> >
<Button variant="destructive" aria-label="Elimina Ordine"> <Button aria-label="Elimina Ordine" variant="destructive">
<Trash2 className="size-6" /> <Trash2 className="size-6" />
Elimina Ordine Elimina Ordine
</Button> </Button>

View file

@ -32,13 +32,14 @@ export const Sidebar = ({
return ( return (
<motion.nav <motion.nav
animate={{ width: minimized ? 65 : 192 }}
aria-label="Sidebar Menu"
className={cn( className={cn(
"text-accent-foreground z-30 hidden h-auto w-48 shrink-0 space-y-4 border-r bg-white pt-3 md:block", "text-accent-foreground z-30 hidden h-auto w-48 shrink-0 space-y-4 border-r bg-white pt-3 md:block",
className, className,
minimized && "cursor-pointer", minimized && "cursor-pointer",
//defaultOpen && "w-[65px]", //defaultOpen && "w-[65px]",
)} )}
animate={{ width: minimized ? 65 : 192 }}
onClick={ onClick={
minimized minimized
? (e) => { ? (e) => {
@ -48,7 +49,6 @@ export const Sidebar = ({
} }
: undefined : undefined
} }
aria-label="Sidebar Menu"
> >
<div <div
className={cn( className={cn(
@ -61,9 +61,9 @@ export const Sidebar = ({
)} )}
<Button <Button
aria-label="Toggle Sidebar" aria-label="Toggle Sidebar"
variant="ghost"
onClick={() => toggleSidebar(!minimized)}
className="px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0" className="px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0"
onClick={() => toggleSidebar(!minimized)}
variant="ghost"
> >
{minimized ? ( {minimized ? (
<Expand className="size-6" /> <Expand className="size-6" />
@ -76,9 +76,9 @@ export const Sidebar = ({
<div className={cn("px-4 text-lg", minimized && "px-0")}> <div className={cn("px-4 text-lg", minimized && "px-0")}>
<ARMinimizableLinks <ARMinimizableLinks
isAdmin={isAdmin} isAdmin={isAdmin}
setOpen={toggleSidebar}
pathname={pathname}
minimized={minimized} minimized={minimized}
pathname={pathname}
setOpen={toggleSidebar}
/> />
</div> </div>
</motion.nav> </motion.nav>

View file

@ -82,13 +82,13 @@ export const BannerFactory = (bannerData: Banners) => {
bannerData.cta_href && bannerData.cta_href &&
bannerData.cta_icon && ( bannerData.cta_icon && (
<Link <Link
href={bannerData.cta_href}
className="inline-flex items-center gap-x-1 font-semibold underline duration-150 hover:text-indigo-100" className="inline-flex items-center gap-x-1 font-semibold underline duration-150 hover:text-indigo-100"
href={bannerData.cta_href}
> >
{bannerData.cta_text} {bannerData.cta_text}
<IconMatrix <IconMatrix
type={bannerData.cta_icon as IconType}
className="size-4" className="size-4"
type={bannerData.cta_icon as IconType}
/> />
</Link> </Link>
)} )}
@ -96,12 +96,12 @@ export const BannerFactory = (bannerData: Banners) => {
</div> </div>
{bannerData.is_unskippable === false && ( {bannerData.is_unskippable === false && (
<button <button
type="button"
className={cn( className={cn(
"rounded-lg p-2 ring-offset-2 duration-150 hover:bg-indigo-500 focus:ring", "rounded-lg p-2 ring-offset-2 duration-150 hover:bg-indigo-500 focus:ring",
closeHoverColor, closeHoverColor,
)} )}
onClick={onClick} onClick={onClick}
type="button"
> >
<X className="size-5" /> <X className="size-5" />
</button> </button>

View file

@ -45,17 +45,17 @@ export const ChatAttachments = ({
<Credenza onOpenChange={(o) => o && refetch()}> <Credenza onOpenChange={(o) => o && refetch()}>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
variant="ghost"
size="icon"
className="relative size-9"
aria-label="Allegati Chat" aria-label="Allegati Chat"
className="relative size-9"
size="icon"
variant="ghost"
> >
{attachments && attachments.length > 0 && ( {attachments && attachments.length > 0 && (
<span className="absolute -top-1 right-0 rounded-full bg-blue-500 px-1 text-xs font-bold text-white"> <span className="absolute -top-1 right-0 rounded-full bg-blue-500 px-1 text-xs font-bold text-white">
{attachments.length} {attachments.length}
</span> </span>
)} )}
<Paperclip size={20} className="text-muted-foreground" /> <Paperclip className="text-muted-foreground" size={20} />
</Button> </Button>
</CredenzaTrigger> </CredenzaTrigger>
<CredenzaContent className="max-h-[90vh]"> <CredenzaContent className="max-h-[90vh]">
@ -92,19 +92,19 @@ export const ChatAttachments = ({
return ( return (
<li <li
key={`${file.filename}-${idx}`}
className="flex items-center justify-between border-b px-3 py-1" className="flex items-center justify-between border-b px-3 py-1"
key={`${file.filename}-${idx}`}
> >
<Link <Link
aria-label="Visualizza Allegato" aria-label="Visualizza Allegato"
href={`/area-riservata/allegato-view/${file.id}`}
target="_blank"
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: "link", variant: "link",
}), }),
"flex h-8 items-center gap-2 truncate p-0 text-base", "flex h-8 items-center gap-2 truncate p-0 text-base",
)} )}
href={`/area-riservata/allegato-view/${file.id}`}
target="_blank"
> >
{file.ext && <ExtIcon ext={file.ext} />} {file.ext && <ExtIcon ext={file.ext} />}
<span className="truncate">{file.filename}</span> <span className="truncate">{file.filename}</span>
@ -127,13 +127,13 @@ export const ChatAttachments = ({
<CredenzaFooter className="flex flex-row items-center justify-between gap-2"> <CredenzaFooter className="flex flex-row items-center justify-between gap-2">
<Link <Link
aria-label="Visualizza Allegati" aria-label="Visualizza Allegati"
href="/area-riservata/allegati"
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: "outline", variant: "outline",
}), }),
"relative w-full", "relative w-full",
)} )}
href="/area-riservata/allegati"
> >
<Paperclip /> {t.allegati.i_tuoi_allegati} <Paperclip /> {t.allegati.i_tuoi_allegati}
{attachments && attachments.length > 0 && ( {attachments && attachments.length > 0 && (

View file

@ -96,14 +96,19 @@ export default function ChatBottombar({
return ( return (
<div className="flex min-h-[74px] w-full items-center justify-between gap-2 p-2"> <div className="flex min-h-[74px] w-full items-center justify-between gap-2 p-2">
<div className="flex"> <div className="flex">
<ChatAttachments userData={userData} chatUserData={chatUserData} /> <ChatAttachments chatUserData={chatUserData} userData={userData} />
</div> </div>
<div className="relative w-full"> <div className="relative w-full">
<AutosizeTextarea <AutosizeTextarea
autoComplete="off" autoComplete="off"
value={message} className="bg-background flex h-14 min-h-14 w-full resize-none items-center overflow-hidden rounded-xl border pr-8 ring-0 outline-hidden hover:border-neutral-400 focus:border-neutral-600 focus:ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
minHeight={56} minHeight={56}
ref={inputRef} name="message"
onBlur={() => setIsFocused(false)}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => {
setMessage(e.target.value);
}}
onFocus={() => setIsFocused(true)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") { if (e.key === "Enter") {
e.preventDefault(); e.preventDefault();
@ -114,18 +119,13 @@ export default function ChatBottombar({
} }
} }
}} }}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => {
setMessage(e.target.value);
}}
name="message"
placeholder="Aa" placeholder="Aa"
className="bg-background flex h-14 min-h-14 w-full resize-none items-center overflow-hidden rounded-xl border pr-8 ring-0 outline-hidden hover:border-neutral-400 focus:border-neutral-600 focus:ring-0 focus-visible:ring-0 focus-visible:ring-offset-0" ref={inputRef}
value={message}
/> />
{isMobile && ( {isMobile && (
<div className="absolute top-0 right-2 flex h-full items-center"> <div className="absolute top-0 right-2 flex h-full items-center">
<Popover open={openEmoji} onOpenChange={setOpenEmoji}> <Popover onOpenChange={setOpenEmoji} open={openEmoji}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<SmilePlusIcon className="stroke-muted-foreground" /> <SmilePlusIcon className="stroke-muted-foreground" />
@ -133,8 +133,8 @@ export default function ChatBottombar({
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="end" className="w-fit p-0"> <PopoverContent align="end" className="w-fit p-0">
<EmojiPicker <EmojiPicker
locale={locale as "it" | "en"}
className="h-84" className="h-84"
locale={locale as "it" | "en"}
onEmojiSelect={({ emoji }) => { onEmojiSelect={({ emoji }) => {
setMessage(message + emoji); setMessage(message + emoji);
// if (inputRef.current) { // if (inputRef.current) {
@ -154,16 +154,16 @@ export default function ChatBottombar({
</div> </div>
<Button <Button
aria-label="Send Message" aria-label="Send Message"
variant="ghost"
className="shrink-0" className="shrink-0"
onClick={ onClick={
message.length !== 0 ? () => handleSend() : () => handleThumbsUp() message.length !== 0 ? () => handleSend() : () => handleThumbsUp()
} }
variant="ghost"
> >
{message.length !== 0 ? ( {message.length !== 0 ? (
<SendHorizontal size={20} className="text-muted-foreground" /> <SendHorizontal className="text-muted-foreground" size={20} />
) : ( ) : (
<ThumbsUp size={20} className="text-muted-foreground" /> <ThumbsUp className="text-muted-foreground" size={20} />
)} )}
</Button> </Button>
</div> </div>

View file

@ -9,19 +9,19 @@ import { cn } from "~/lib/utils";
const chatBubbleVariant = cva( const chatBubbleVariant = cva(
"flex gap-2 max-w-[60%] items-end relative group", "flex gap-2 max-w-[60%] items-end relative group",
{ {
defaultVariants: {
layout: "default",
variant: "received",
},
variants: { variants: {
layout: {
ai: "max-w-full w-full items-center",
default: "",
},
variant: { variant: {
received: "self-start", received: "self-start",
sent: "self-end flex-row-reverse", sent: "self-end flex-row-reverse",
}, },
layout: {
default: "",
ai: "max-w-full w-full items-center",
},
},
defaultVariants: {
variant: "received",
layout: "default",
}, },
}, },
); );
@ -34,7 +34,7 @@ const ChatBubble = React.forwardRef<HTMLDivElement, ChatBubbleProps>(
({ className, variant, layout, children, ...props }, ref) => ( ({ className, variant, layout, children, ...props }, ref) => (
<div <div
className={cn( className={cn(
chatBubbleVariant({ variant, layout, className }), chatBubbleVariant({ className, layout, variant }),
"group relative", "group relative",
)} )}
ref={ref} ref={ref}
@ -43,8 +43,8 @@ const ChatBubble = React.forwardRef<HTMLDivElement, ChatBubbleProps>(
{React.Children.map(children, (child) => {React.Children.map(children, (child) =>
React.isValidElement(child) && typeof child.type !== "string" React.isValidElement(child) && typeof child.type !== "string"
? React.cloneElement(child, { ? React.cloneElement(child, {
variant,
layout, layout,
variant,
} as Partial<React.ComponentProps<typeof child.type>>) } as Partial<React.ComponentProps<typeof child.type>>)
: child, : child,
)} )}
@ -67,20 +67,20 @@ export const ChatBubbleReadStatus = ({ isread }: ChatBubbleReadStatusProps) => {
// ChatBubbleMessage // ChatBubbleMessage
const chatBubbleMessageVariants = cva("p-2", { const chatBubbleMessageVariants = cva("p-2", {
defaultVariants: {
layout: "default",
variant: "received",
},
variants: { variants: {
layout: {
ai: "border-t w-full rounded-none bg-transparent",
default: "",
},
variant: { variant: {
received: received:
"bg-secondary text-secondary-foreground rounded-r-lg rounded-tl-lg", "bg-secondary text-secondary-foreground rounded-r-lg rounded-tl-lg",
sent: "bg-primary text-primary-foreground rounded-l-lg rounded-tr-lg", sent: "bg-primary text-primary-foreground rounded-l-lg rounded-tr-lg",
}, },
layout: {
default: "",
ai: "border-t w-full rounded-none bg-transparent",
},
},
defaultVariants: {
variant: "received",
layout: "default",
}, },
}); });
@ -100,7 +100,7 @@ const ChatBubbleMessage = React.forwardRef<
) => ( ) => (
<div <div
className={cn( className={cn(
chatBubbleMessageVariants({ variant, layout, className }), chatBubbleMessageVariants({ className, layout, variant }),
"max-w-full break-words whitespace-pre-wrap", "max-w-full break-words whitespace-pre-wrap",
)} )}
ref={ref} ref={ref}
@ -149,10 +149,10 @@ const ChatBubbleAction: React.FC<ChatBubbleActionProps> = ({
}) => ( }) => (
<Button <Button
aria-label="Chat Bubble Action" aria-label="Chat Bubble Action"
variant={variant}
size={size}
className={className} className={className}
onClick={onClick} onClick={onClick}
size={size}
variant={variant}
{...props} {...props}
> >
{icon} {icon}
@ -173,7 +173,6 @@ const ChatBubbleActionWrapper = React.forwardRef<
<> <>
{show && ( {show && (
<div <div
ref={ref}
className={cn( className={cn(
"absolute top-1/2 flex -translate-y-1/2 opacity-0 transition-opacity duration-200 group-hover:opacity-100", "absolute top-1/2 flex -translate-y-1/2 opacity-0 transition-opacity duration-200 group-hover:opacity-100",
variant === "sent" variant === "sent"
@ -181,6 +180,7 @@ const ChatBubbleActionWrapper = React.forwardRef<
: "-right-1 translate-x-full", : "-right-1 translate-x-full",
className, className,
)} )}
ref={ref}
{...props} {...props}
> >
{children} {children}

View file

@ -79,27 +79,26 @@ export const ChatList = ({
}; };
const { mutate: deleteMutation } = api.messagesSSE.delete.useMutation({ const { mutate: deleteMutation } = api.messagesSSE.delete.useMutation({
onError: () => {
toast.error("Error deleting message", { id: "deleteMessage" });
},
onMutate: () => { onMutate: () => {
toast("Deleting message...", { icon: "🗑️", id: "deleteMessage" }); toast("Deleting message...", { icon: "🗑️", id: "deleteMessage" });
}, },
onSuccess: () => { onSuccess: () => {
toast("Message deleted", { icon: "🗑️", id: "deleteMessage" }); toast("Message deleted", { icon: "🗑️", id: "deleteMessage" });
}, },
onError: () => {
toast.error("Error deleting message", { id: "deleteMessage" });
},
}); });
const { mutate: editMutation } = api.messagesSSE.edit.useMutation({ const { mutate: editMutation } = api.messagesSSE.edit.useMutation({
onError: () => {
toast.error("Error editing message", { id: "editMessage" });
},
onMutate: () => { onMutate: () => {
toast("Editing message...", { icon: "📝", id: "editMessage" }); toast("Editing message...", { icon: "📝", id: "editMessage" });
}, },
onSuccess: () => { onSuccess: () => {
toast("Message edited", { icon: "📝", id: "editMessage" }); toast("Message edited", { icon: "📝", id: "editMessage" });
}, },
onError: () => {
toast.error("Error editing message", { id: "editMessage" });
},
}); });
const handleEditSubmit = useCallback( const handleEditSubmit = useCallback(
@ -112,8 +111,8 @@ export const ChatList = ({
} }
editMutation({ editMutation({
chatId, chatId,
messageId: formData.get("message-id") as MessagesMessageid,
message: formData.get("message-edit") as string, message: formData.get("message-edit") as string,
messageId: formData.get("message-id") as MessagesMessageid,
}); });
setEditModalOpen(false); setEditModalOpen(false);
}, },
@ -141,15 +140,15 @@ export const ChatList = ({
{query.hasNextPage && ( {query.hasNextPage && (
<LoadingButton <LoadingButton
aria-label="Load More Messages" aria-label="Load More Messages"
loading={query.isFetchingNextPage} className="mb-3"
variant="secondary"
key="load-more"
disabled={!query.hasNextPage || query.isFetchingNextPage} disabled={!query.hasNextPage || query.isFetchingNextPage}
key="load-more"
loading={query.isFetchingNextPage}
onClick={async () => { onClick={async () => {
setLocked(true); setLocked(true);
await query.fetchNextPage(); await query.fetchNextPage();
}} }}
className="mb-3" variant="secondary"
> >
{query.isFetchingNextPage {query.isFetchingNextPage
? "Caricamento..." ? "Caricamento..."
@ -175,24 +174,24 @@ export const ChatList = ({
return ( return (
<motion.div <motion.div
key={message.messageid} animate={{ opacity: 1, scale: 1, x: 0, y: 0 }}
layout
initial={{ opacity: 0, scale: 1, y: 50, x: 0 }}
animate={{ opacity: 1, scale: 1, y: 0, x: 0 }}
exit={{ opacity: 0, scale: 1, y: 1, x: 0 }}
transition={{
opacity: { duration: 0.1 },
layout: {
type: "spring",
bounce: 0.3,
duration: 0.3,
},
}}
style={{ originX: 0.5, originY: 0.5 }}
className={cn( className={cn(
"flex flex-col gap-2 pb-4", "flex flex-col gap-2 pb-4",
isNextSameSender && "pb-0.5", isNextSameSender && "pb-0.5",
)} )}
exit={{ opacity: 0, scale: 1, x: 0, y: 1 }}
initial={{ opacity: 0, scale: 1, x: 0, y: 50 }}
key={message.messageid}
layout
style={{ originX: 0.5, originY: 0.5 }}
transition={{
layout: {
bounce: 0.3,
duration: 0.3,
type: "spring",
},
opacity: { duration: 0.1 },
}}
> >
{!isPrevSameDay && ( {!isPrevSameDay && (
<span className="mx-auto rounded-md bg-muted px-2 py-1 text-center text-xs text-muted-foreground"> <span className="mx-auto rounded-md bg-muted px-2 py-1 text-center text-xs text-muted-foreground">
@ -207,7 +206,7 @@ export const ChatList = ({
{isNextSameSender ? ( {isNextSameSender ? (
<div className="size-6"></div> <div className="size-6"></div>
) : ( ) : (
<UserAvatar userId={message.sender} className="size-6" /> <UserAvatar className="size-6" userId={message.sender} />
)} )}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
@ -225,8 +224,8 @@ export const ChatList = ({
</p> </p>
)} )}
<ChatBubbleMessage <ChatBubbleMessage
isLoading={false}
className={cn(isNextSameSender && "rounded-lg")} className={cn(isNextSameSender && "rounded-lg")}
isLoading={false}
> >
{message.message} {message.message}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -239,9 +238,9 @@ export const ChatList = ({
"it", "it",
{ {
day: "2-digit", day: "2-digit",
month: "short",
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
month: "short",
}, },
)} )}
/> />
@ -252,8 +251,8 @@ export const ChatList = ({
<ChatBubbleActionWrapper show={userData.isAdmin}> <ChatBubbleActionWrapper show={userData.isAdmin}>
<ChatBubbleAction <ChatBubbleAction
className="size-7 hover:bg-red-50 hover:text-red-500" className="size-7 hover:bg-red-50 hover:text-red-500"
key={"delete"}
icon={<Trash2 className="size-4" />} icon={<Trash2 className="size-4" />}
key={"delete"}
onClick={() => { onClick={() => {
setMsgSelected(message); setMsgSelected(message);
handleDeleteOpen(true); handleDeleteOpen(true);
@ -261,8 +260,8 @@ export const ChatList = ({
/> />
<ChatBubbleAction <ChatBubbleAction
className="size-7 hover:bg-blue-50 hover:text-blue-500" className="size-7 hover:bg-blue-50 hover:text-blue-500"
key={"edit"}
icon={<Pencil className="size-4" />} icon={<Pencil className="size-4" />}
key={"edit"}
onClick={() => { onClick={() => {
setMsgSelected(message); setMsgSelected(message);
handleEditOpen(true); handleEditOpen(true);
@ -282,18 +281,18 @@ export const ChatList = ({
</AnimatePresence> </AnimatePresence>
</ChatMessageList> </ChatMessageList>
<EditMessageDialog <EditMessageDialog
open={editModalOpen}
onOpenChange={handleEditOpen}
msgEdit={msgSelected}
handleEditSubmit={handleEditSubmit} handleEditSubmit={handleEditSubmit}
msgEdit={msgSelected}
onOpenChange={handleEditOpen}
open={editModalOpen}
/> />
<DeleteMessageDialog <DeleteMessageDialog
open={deleteModalOpen}
onOpenChange={handleDeleteOpen}
msgDelete={msgSelected}
handleDeleteSubmit={(messageId) => { handleDeleteSubmit={(messageId) => {
deleteMutation({ chatId, messageId }); deleteMutation({ chatId, messageId });
}} }}
msgDelete={msgSelected}
onOpenChange={handleDeleteOpen}
open={deleteModalOpen}
/> />
</div> </div>
); );
@ -316,24 +315,24 @@ const Typers = ({
return ( return (
<motion.div <motion.div
animate={{ opacity: 1, scale: 1, x: 0, y: 0 }}
className="flex flex-col gap-2 py-2"
exit={{ opacity: 0, scale: 1, x: 0, y: 1 }}
initial={{ opacity: 0, scale: 1, x: 0, y: 50 }}
key={`typers${messageLenght}${index}`} key={`typers${messageLenght}${index}`}
layout layout
initial={{ opacity: 0, scale: 1, y: 50, x: 0 }} style={{ originX: 0.5, originY: 0.5 }}
animate={{ opacity: 1, scale: 1, y: 0, x: 0 }}
exit={{ opacity: 0, scale: 1, y: 1, x: 0 }}
transition={{ transition={{
opacity: { duration: 0.1 },
layout: { layout: {
type: "spring",
bounce: 0.3, bounce: 0.3,
duration: 0.3, duration: 0.3,
type: "spring",
}, },
opacity: { duration: 0.1 },
}} }}
style={{ originX: 0.5, originY: 0.5 }}
className="flex flex-col gap-2 py-2"
> >
<ChatBubble variant={variant}> <ChatBubble variant={variant}>
<UserAvatar userId={typer.userId} className="size-6" /> <UserAvatar className="size-6" userId={typer.userId} />
<ChatBubbleMessage isLoading={true}></ChatBubbleMessage> <ChatBubbleMessage isLoading={true}></ChatBubbleMessage>
</ChatBubble> </ChatBubble>
@ -357,36 +356,36 @@ const EditMessageDialog = ({
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog onOpenChange={onOpenChange} open={open}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>{t.modifica}</DialogTitle> <DialogTitle>{t.modifica}</DialogTitle>
<DialogDescription className="sr-only">Edit</DialogDescription> <DialogDescription className="sr-only">Edit</DialogDescription>
</DialogHeader> </DialogHeader>
{msgEdit && ( {msgEdit && (
<form onSubmit={handleEditSubmit} method="post"> <form method="post" onSubmit={handleEditSubmit}>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<label htmlFor="id" className="sr-only"> <label className="sr-only" htmlFor="id">
ID ID
</label> </label>
<Input <Input
id="id"
type="text"
className="hidden" className="hidden"
id="id"
name="message-id" name="message-id"
value={msgEdit.messageid}
readOnly readOnly
type="text"
value={msgEdit.messageid}
/> />
<label htmlFor="message-edit" className="sr-only"> <label className="sr-only" htmlFor="message-edit">
Modifica messaggio Modifica messaggio
</label> </label>
<Input <Input
id="message-edit"
type="text"
placeholder="Modifica messaggio"
className="mt-0" className="mt-0"
name="message-edit"
defaultValue={msgEdit.message || undefined} defaultValue={msgEdit.message || undefined}
id="message-edit"
name="message-edit"
placeholder="Modifica messaggio"
type="text"
/> />
<Button type="submit">{t.salva}</Button> <Button type="submit">{t.salva}</Button>
</div> </div>
@ -412,7 +411,7 @@ const DeleteMessageDialog = ({
return ( return (
<> <>
{msgDelete && ( {msgDelete && (
<AlertDialog open={open} onOpenChange={onOpenChange}> <AlertDialog onOpenChange={onOpenChange} open={open}>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Vuoi eliminare il messaggio?</AlertDialogTitle> <AlertDialogTitle>Vuoi eliminare il messaggio?</AlertDialogTitle>

View file

@ -104,18 +104,18 @@ export const ChatSidebar = ({ chatId }: { chatId: ChatsChatid | null }) => {
{isDesktop ? ( {isDesktop ? (
<div className="relative"> <div className="relative">
<Search className="text-muted-foreground absolute top-[0.8rem] left-2 size-4" /> <Search className="text-muted-foreground absolute top-[0.8rem] left-2 size-4" />
<label htmlFor="search" className="sr-only"> <label className="sr-only" htmlFor="search">
Search Chats Search Chats
</label> </label>
<Input placeholder="Search" className="m-0 pl-8" id="search" /> <Input className="m-0 pl-8" id="search" placeholder="Search" />
</div> </div>
) : ( ) : (
<Button <Button
aria-label="Open Search Dialog" aria-label="Open Search Dialog"
onClick={() => setSearchDialogOpen(true)}
variant="ghost"
size="icon"
className="size-8" className="size-8"
onClick={() => setSearchDialogOpen(true)}
size="icon"
variant="ghost"
> >
<Search size={20} /> <Search size={20} />
</Button> </Button>
@ -123,16 +123,16 @@ export const ChatSidebar = ({ chatId }: { chatId: ChatsChatid | null }) => {
<SearchUserComponent <SearchUserComponent
activeChats={activeChats} activeChats={activeChats}
handleChatSelection={handleChatSelection}
isOpen={searchDialogOpen} isOpen={searchDialogOpen}
OpenChange={setSearchDialogOpen} OpenChange={setSearchDialogOpen}
handleChatSelection={handleChatSelection}
/> />
<Button <Button
aria-label="Open Add User Dialog" aria-label="Open Add User Dialog"
onClick={() => setAddDialogOpen(true)}
variant="ghost"
size="icon"
className="size-8" className="size-8"
onClick={() => setAddDialogOpen(true)}
size="icon"
variant="ghost"
> >
<SquarePen size={20} /> <SquarePen size={20} />
</Button> </Button>
@ -150,21 +150,21 @@ export const ChatSidebar = ({ chatId }: { chatId: ChatsChatid | null }) => {
<ContextMenuTrigger asChild> <ContextMenuTrigger asChild>
<Button <Button
aria-label="Select Chat" aria-label="Select Chat"
onClick={() => handleChatSelection(chat.id)}
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: getVariant(chat.lastmessage),
size: !isDesktop ? "icon" : "xl", size: !isDesktop ? "icon" : "xl",
variant: getVariant(chat.lastmessage),
}), }),
!isDesktop ? "p-1" : "justify-start gap-4", !isDesktop ? "p-1" : "justify-start gap-4",
"h-fit w-full", "h-fit w-full",
)} )}
onClick={() => handleChatSelection(chat.id)}
> >
<UserAvatar <UserAvatar
username={chat.userinfo.username}
userId={chat.userinfo.id}
className="size-10" className="size-10"
userId={chat.userinfo.id}
username={chat.userinfo.username}
/> />
{isDesktop && ( {isDesktop && (
@ -173,10 +173,10 @@ export const ChatSidebar = ({ chatId }: { chatId: ChatsChatid | null }) => {
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{chat.etichette.map((etichetta) => ( {chat.etichette.map((etichetta) => (
<Etichetta <Etichetta
className="text-xs"
color_hex={etichetta.color_hex}
key={etichetta.id_etichetta} key={etichetta.id_etichetta}
title={etichetta.title} title={etichetta.title}
color_hex={etichetta.color_hex}
className="text-xs"
/> />
))} ))}
</div> </div>
@ -188,10 +188,10 @@ export const ChatSidebar = ({ chatId }: { chatId: ChatsChatid | null }) => {
{chat.lastmessage && {chat.lastmessage &&
new Date(chat.lastmessage.time).toLocaleString("it", { new Date(chat.lastmessage.time).toLocaleString("it", {
day: "2-digit", day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
month: "2-digit",
year: "numeric",
})} })}
</span> </span>
</div> </div>
@ -200,17 +200,17 @@ export const ChatSidebar = ({ chatId }: { chatId: ChatsChatid | null }) => {
</ContextMenuTrigger> </ContextMenuTrigger>
<ChatSidebarContextMenu <ChatSidebarContextMenu
chatid={chat.id} chatid={chat.id}
setSelectedChat={setSelectedChat}
setDeleteModalOpen={setDeleteModalOpen} setDeleteModalOpen={setDeleteModalOpen}
setEditEtichettaModalOpen={setEditEtichettaModalOpen} setEditEtichettaModalOpen={setEditEtichettaModalOpen}
setSelectedChat={setSelectedChat}
/> />
</ContextMenu> </ContextMenu>
))} ))}
{selectedChat && ( {selectedChat && (
<> <>
<AlertDialog <AlertDialog
open={deleteModalOpen}
onOpenChange={setDeleteModalOpen} onOpenChange={setDeleteModalOpen}
open={deleteModalOpen}
> >
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
@ -240,9 +240,9 @@ export const ChatSidebar = ({ chatId }: { chatId: ChatsChatid | null }) => {
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
<EtichetteModal <EtichetteModal
open={editEtichettaModalOpen}
onOpenChange={setEditEtichettaModalOpen}
chatId={selectedChat} chatId={selectedChat}
onOpenChange={setEditEtichettaModalOpen}
open={editEtichettaModalOpen}
/> />
</> </>
)} )}
@ -263,10 +263,10 @@ const SearchUserComponent = ({
handleChatSelection: (chatId: ChatsChatid) => void; handleChatSelection: (chatId: ChatsChatid) => void;
}) => { }) => {
return ( return (
<CommandDialog open={isOpen} onOpenChange={OpenChange}> <CommandDialog onOpenChange={OpenChange} open={isOpen}>
<CommandInput <CommandInput
placeholder="Digita il nome utente..."
className="border-none ring-0 focus:ring-0 focus-visible:ring-0" className="border-none ring-0 focus:ring-0 focus-visible:ring-0"
placeholder="Digita il nome utente..."
/> />
<CommandList> <CommandList>
<CommandEmpty>Nessun risultato</CommandEmpty> <CommandEmpty>Nessun risultato</CommandEmpty>
@ -284,9 +284,9 @@ const SearchUserComponent = ({
> >
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<UserAvatar <UserAvatar
username={chat.userinfo.username}
userId={chat.userinfo.id}
className="size-8" className="size-8"
userId={chat.userinfo.id}
username={chat.userinfo.username}
/> />
<span> <span>
@ -320,10 +320,10 @@ const NewUserComponent = ({
}, },
}); });
return ( return (
<CommandDialog open={isOpen} onOpenChange={OpenChange}> <CommandDialog onOpenChange={OpenChange} open={isOpen}>
<CommandInput <CommandInput
placeholder="Digita il nome utente..."
className="border-none ring-0 focus:ring-0 focus-visible:ring-0" className="border-none ring-0 focus:ring-0 focus-visible:ring-0"
placeholder="Digita il nome utente..."
/> />
<CommandList> <CommandList>
<CommandEmpty>Nessun risultato</CommandEmpty> <CommandEmpty>Nessun risultato</CommandEmpty>
@ -343,9 +343,9 @@ const NewUserComponent = ({
> >
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<UserAvatar <UserAvatar
username={user.username}
userId={user.id}
className="size-8" className="size-8"
userId={user.id}
username={user.username}
/> />
<span> <span>
@ -384,13 +384,13 @@ const ChatSidebarContextMenu = ({
<ContextMenuSubContent className="w-48"> <ContextMenuSubContent className="w-48">
<ContextMenuItem asChild> <ContextMenuItem asChild>
<button <button
type="button"
aria-label="Edit Chat Labels" aria-label="Edit Chat Labels"
className="w-full cursor-pointer"
onClick={() => { onClick={() => {
setSelectedChat(chatid); setSelectedChat(chatid);
setEditEtichettaModalOpen(true); setEditEtichettaModalOpen(true);
}} }}
className="w-full cursor-pointer" type="button"
> >
Modifica Modifica
</button> </button>
@ -401,8 +401,8 @@ const ChatSidebarContextMenu = ({
<ContextMenuItem> <ContextMenuItem>
<Link <Link
aria-label="View User Profile" aria-label="View User Profile"
target="_blank"
href={`/area-riservata/admin/user-view/edit-user/${chatData.user_ref}`} href={`/area-riservata/admin/user-view/edit-user/${chatData.user_ref}`}
target="_blank"
> >
Profilo Profilo
</Link> </Link>
@ -411,8 +411,8 @@ const ChatSidebarContextMenu = ({
<ContextMenuItem> <ContextMenuItem>
<Link <Link
aria-label="View User Orders" aria-label="View User Orders"
target="_blank"
href={`/area-riservata/admin/user-view/ordini/${chatData.user_ref}`} href={`/area-riservata/admin/user-view/ordini/${chatData.user_ref}`}
target="_blank"
> >
Ordini Ordini
</Link> </Link>
@ -420,8 +420,8 @@ const ChatSidebarContextMenu = ({
<ContextMenuItem> <ContextMenuItem>
<Link <Link
aria-label="View User Attachments" aria-label="View User Attachments"
target="_blank"
href={`/area-riservata/admin/user-view/allegati/${chatData.user_ref}`} href={`/area-riservata/admin/user-view/allegati/${chatData.user_ref}`}
target="_blank"
> >
Allegati Allegati
</Link> </Link>
@ -431,11 +431,11 @@ const ChatSidebarContextMenu = ({
<ContextMenuSubContent className="w-48"> <ContextMenuSubContent className="w-48">
<ContextMenuItem <ContextMenuItem
aria-label="Edit Chat" aria-label="Edit Chat"
className="cursor-pointer rounded-md bg-red-500 text-white hover:bg-red-500/90 focus:bg-red-500/90 focus:text-white"
onClick={() => { onClick={() => {
setSelectedChat(chatid); setSelectedChat(chatid);
setDeleteModalOpen(true); setDeleteModalOpen(true);
}} }}
className="cursor-pointer rounded-md bg-red-500 text-white hover:bg-red-500/90 focus:bg-red-500/90 focus:text-white"
> >
Elimina Chat Elimina Chat
</ContextMenuItem> </ContextMenuItem>

View file

@ -88,17 +88,17 @@ export default function ChatTopbar({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{!userData.isAdmin ? ( {!userData.isAdmin ? (
<Image <Image
src={"/favicon/favicon.png"}
alt="Infoalloggi logo" alt="Infoalloggi logo"
width={40}
height={40}
className="size-10 rounded-full" className="size-10 rounded-full"
height={40}
src={"/favicon/favicon.png"}
width={40}
/> />
) : ( ) : (
<UserAvatar <UserAvatar
className="size-10" className="size-10"
username={chatUserData.username}
userId={chatUserData.id} userId={chatUserData.id}
username={chatUserData.username}
/> />
)} )}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
@ -118,8 +118,8 @@ export default function ChatTopbar({
{userData.isAdmin && ( {userData.isAdmin && (
<AdminPopover <AdminPopover
chatUserData={chatUserData} chatUserData={chatUserData}
handleSwapUser={handleSwapUser}
handleDeleteChat={handleDeleteChat} handleDeleteChat={handleDeleteChat}
handleSwapUser={handleSwapUser}
/> />
)} )}
</div> </div>
@ -137,7 +137,7 @@ const UserStatus = ({
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
{onlineStatus.map((user, idx) => {onlineStatus.map((user, idx) =>
user.isAdmin ? ( user.isAdmin ? (
<TooltipProvider key={idx} delayDuration={0}> <TooltipProvider delayDuration={0} key={idx}>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<span <span
@ -145,7 +145,7 @@ const UserStatus = ({
"flex flex-row items-center text-xs font-bold text-green-500", "flex flex-row items-center text-xs font-bold text-green-500",
)} )}
> >
<ShieldUser size={15} className="mr-[0.2rem]" /> <ShieldUser className="mr-[0.2rem]" size={15} />
<span> <span>
{user.nome} {user.nome}
{idx < onlineStatus.length - 1 && ","} {idx < onlineStatus.length - 1 && ","}
@ -159,8 +159,8 @@ const UserStatus = ({
</TooltipProvider> </TooltipProvider>
) : ( ) : (
<span <span
key={idx}
className={cn("flex flex-row text-xs font-bold text-green-500")} className={cn("flex flex-row text-xs font-bold text-green-500")}
key={idx}
> >
<span> <span>
{user.nome} {user.nome}
@ -185,12 +185,12 @@ const AdminPopover = ({
<Popover> <Popover>
<PopoverTrigger <PopoverTrigger
className={cn( className={cn(
buttonVariants({ variant: "outline", size: "icon" }), buttonVariants({ size: "icon", variant: "outline" }),
"size-10", "size-10",
"dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white", "dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white",
)} )}
> >
<Info size={24} className="text-muted-foreground" /> <Info className="text-muted-foreground" size={24} />
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="mr-4 w-fit px-4"> <PopoverContent className="mr-4 w-fit px-4">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
@ -231,8 +231,8 @@ const AdminPopover = ({
buttonVariants({ variant: "outline" }), buttonVariants({ variant: "outline" }),
"justify-start gap-2", "justify-start gap-2",
)} )}
type="button"
onClick={handleSwapUser} onClick={handleSwapUser}
type="button"
> >
<User /> Login <User /> Login
</button> </button>

View file

@ -21,24 +21,24 @@ const Chat = memo(
<div className="shrink-0"> <div className="shrink-0">
<ChatTopbar <ChatTopbar
chatId={chatId} chatId={chatId}
userData={userData}
chatUserData={userinfo} chatUserData={userinfo}
onlineStatus={liveChat.onlineUsers} onlineStatus={liveChat.onlineUsers}
userData={userData}
/> />
</div> </div>
<ChatList <ChatList
chatId={chatId} chatId={chatId}
userData={userData}
messages={liveChat.messages} messages={liveChat.messages}
query={liveChat.query} query={liveChat.query}
typingStatus={liveChat.typingStatus} typingStatus={liveChat.typingStatus}
userData={userData}
/> />
<div className="shrink-0 border-t"> <div className="shrink-0 border-t">
<ChatBottombar <ChatBottombar
chatId={chatId} chatId={chatId}
userData={userData}
chatUserData={userinfo} chatUserData={userinfo}
userData={userData}
/> />
</div> </div>
</div> </div>

View file

@ -2,43 +2,43 @@
export default function MessageLoading() { export default function MessageLoading() {
return ( return (
<svg <svg
width="24" className="text-foreground"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="text-foreground"
> >
<title>Loading</title> <title>Loading</title>
<circle cx="4" cy="12" r="2" fill="currentColor"> <circle cx="4" cy="12" fill="currentColor" r="2">
<animate <animate
id="spinner_qFRN" attributeName="cy"
begin="0;spinner_OcgL.end+0.25s" begin="0;spinner_OcgL.end+0.25s"
attributeName="cy"
calcMode="spline" calcMode="spline"
dur="0.6s" dur="0.6s"
values="12;6;12" id="spinner_qFRN"
keySplines=".33,.66,.66,1;.33,0,.66,.33" keySplines=".33,.66,.66,1;.33,0,.66,.33"
values="12;6;12"
/> />
</circle> </circle>
<circle cx="12" cy="12" r="2" fill="currentColor"> <circle cx="12" cy="12" fill="currentColor" r="2">
<animate <animate
attributeName="cy"
begin="spinner_qFRN.begin+0.1s" begin="spinner_qFRN.begin+0.1s"
attributeName="cy"
calcMode="spline" calcMode="spline"
dur="0.6s" dur="0.6s"
values="12;6;12"
keySplines=".33,.66,.66,1;.33,0,.66,.33" keySplines=".33,.66,.66,1;.33,0,.66,.33"
values="12;6;12"
/> />
</circle> </circle>
<circle cx="20" cy="12" r="2" fill="currentColor"> <circle cx="20" cy="12" fill="currentColor" r="2">
<animate <animate
id="spinner_OcgL"
begin="spinner_qFRN.begin+0.2s"
attributeName="cy" attributeName="cy"
begin="spinner_qFRN.begin+0.2s"
calcMode="spline" calcMode="spline"
dur="0.6s" dur="0.6s"
values="12;6;12" id="spinner_OcgL"
keySplines=".33,.66,.66,1;.33,0,.66,.33" keySplines=".33,.66,.66,1;.33,0,.66,.33"
values="12;6;12"
/> />
</circle> </circle>
</svg> </svg>

View file

@ -26,13 +26,13 @@ export const CodiceBox = ({
return ( return (
<AutocompleteSearchBox <AutocompleteSearchBox
key={locale}
options={codici || []}
loading={loading}
className={className} className={className}
optionBoxClassName={optionBoxClassName}
optionsClassName={optionsClassName}
containerClassName={containerClassName} containerClassName={containerClassName}
key={locale}
loading={loading}
optionBoxClassName={optionBoxClassName}
options={codici || []}
optionsClassName={optionsClassName}
/> />
); );
}; };
@ -77,7 +77,7 @@ const AutocompleteSearchBox = ({
}, [searchValue, options]); }, [searchValue, options]);
return ( return (
<div ref={wrapperRef} className="z-[5000] w-full"> <div className="z-[5000] w-full" ref={wrapperRef}>
<div <div
className={cn( className={cn(
"relative flex w-full items-center justify-center rounded-md bg-white", "relative flex w-full items-center justify-center rounded-md bg-white",
@ -85,42 +85,42 @@ const AutocompleteSearchBox = ({
)} )}
id="searchContainer" id="searchContainer"
> >
<label htmlFor="search" className="sr-only"> <label className="sr-only" htmlFor="search">
Search Box Search Box
</label> </label>
<Input <Input
className={cn(
"placeholder:text-primary search-cancel:brightness-75 search-cancel:grayscale z-10 cursor-pointer rounded-md bg-transparent text-xl placeholder:font-bold",
className,
)}
id="search" id="search"
type="search" onBlur={() => {
value={searchValue} setIsFocused(false);
}}
onChange={(e) => setSearchValue(e.target.value)} onChange={(e) => setSearchValue(e.target.value)}
onFocus={() => { onFocus={() => {
setOpen(true); setOpen(true);
setIsFocused(true); setIsFocused(true);
}} }}
onBlur={() => {
setIsFocused(false);
}}
onKeyDown={async (e) => { onKeyDown={async (e) => {
if (e.key === "Enter" && results[0] && results.length > 0) { if (e.key === "Enter" && results[0] && results.length > 0) {
setNavigationEnter(true); setNavigationEnter(true);
await router.push(`/annuncio/${results[0].trim()}`); await router.push(`/annuncio/${results[0].trim()}`);
} }
}} }}
className={cn( type="search"
"placeholder:text-primary search-cancel:brightness-75 search-cancel:grayscale z-10 cursor-pointer rounded-md bg-transparent text-xl placeholder:font-bold", value={searchValue}
className,
)}
/> />
<MovingText hidden={isFocused || searchValue !== ""} /> <MovingText hidden={isFocused || searchValue !== ""} />
</div> </div>
{open && ( {open && (
<div <div
ref={listRef}
className={cn( className={cn(
"border-foreground ring-foreground absolute z-50 mt-1 max-h-64 w-full overflow-hidden rounded-md border bg-white shadow-xs ring-1", "border-foreground ring-foreground absolute z-50 mt-1 max-h-64 w-full overflow-hidden rounded-md border bg-white shadow-xs ring-1",
optionBoxClassName, optionBoxClassName,
)} )}
id="searchOptionsBox" id="searchOptionsBox"
ref={listRef}
> >
<ul <ul
className={cn( className={cn(
@ -150,11 +150,11 @@ const AutocompleteSearchBox = ({
{results.map((item) => ( {results.map((item) => (
<li key={item}> <li key={item}>
<Link <Link
href={`/annuncio/${item}`}
className={cn( className={cn(
"flex w-full cursor-pointer items-center justify-between border-b border-neutral-200 bg-white px-4 py-2 text-xl text-neutral-500 duration-150 hover:bg-neutral-100 hover:text-neutral-600 hover:underline hover:underline-offset-2", "flex w-full cursor-pointer items-center justify-between border-b border-neutral-200 bg-white px-4 py-2 text-xl text-neutral-500 duration-150 hover:bg-neutral-100 hover:text-neutral-600 hover:underline hover:underline-offset-2",
optionsClassName, optionsClassName,
)} )}
href={`/annuncio/${item}`}
> >
{item} {item}
</Link> </Link>
@ -189,10 +189,10 @@ const MovingText = ({ hidden }: { hidden: boolean }) => {
return ( return (
<StaggeredFade <StaggeredFade
key={randomFrase}
text={randomFrase}
className="absolute text-2xl font-bold dark:text-neutral-800" className="absolute text-2xl font-bold dark:text-neutral-800"
hide={hidden} hide={hidden}
key={randomFrase}
text={randomFrase}
/> />
); );
}; };

View file

@ -7,34 +7,34 @@ export const ComeFunziona = () => {
const features = [ const features = [
{ {
image: "/come_funziona_ricerca.jpg",
icon: <Search className="size-8" />,
title: t.come_funziona.features.ricerca.title,
desc: t.come_funziona.features.ricerca.desc, desc: t.come_funziona.features.ricerca.desc,
icon: <Search className="size-8" />,
image: "/come_funziona_ricerca.jpg",
title: t.come_funziona.features.ricerca.title,
}, },
{ {
image: "/come_funziona_aggiungi.jpg",
icon: <HeartPlus className="size-8" />,
title: t.come_funziona.features.aggiungi.title,
desc: t.come_funziona.features.aggiungi.desc, desc: t.come_funziona.features.aggiungi.desc,
icon: <HeartPlus className="size-8" />,
image: "/come_funziona_aggiungi.jpg",
title: t.come_funziona.features.aggiungi.title,
}, },
{ {
image: "/come_funziona_meet.jpg",
icon: <Handshake className="size-8" />,
title: t.come_funziona.features.contatta.title,
desc: t.come_funziona.features.contatta.desc, desc: t.come_funziona.features.contatta.desc,
icon: <Handshake className="size-8" />,
image: "/come_funziona_meet.jpg",
title: t.come_funziona.features.contatta.title,
}, },
{ {
image: "/come_funziona_confirm.jpg",
icon: <Home className="size-8" />,
title: t.come_funziona.features.conferma.title,
desc: t.come_funziona.features.conferma.desc, desc: t.come_funziona.features.conferma.desc,
icon: <Home className="size-8" />,
image: "/come_funziona_confirm.jpg",
title: t.come_funziona.features.conferma.title,
}, },
{ {
image: "/come_funziona_contratto.jpg",
icon: <ScrollText className="size-8" />,
title: t.come_funziona.features.firma.title,
desc: t.come_funziona.features.firma.desc, desc: t.come_funziona.features.firma.desc,
icon: <ScrollText className="size-8" />,
image: "/come_funziona_contratto.jpg",
title: t.come_funziona.features.firma.title,
}, },
]; ];
return ( return (
@ -45,16 +45,16 @@ export const ComeFunziona = () => {
<div className="mx-auto mt-8 w-full space-y-12 md:mt-16"> <div className="mx-auto mt-8 w-full space-y-12 md:mt-16">
{features.map((feature, idx) => ( {features.map((feature, idx) => (
<div <div
key={idx}
className="flex flex-col items-center gap-x-8 gap-y-6 lg:flex-row lg:odd:flex-row-reverse" className="flex flex-col items-center gap-x-8 gap-y-6 lg:flex-row lg:odd:flex-row-reverse"
key={idx}
> >
<div className="flex shrink-0 basis-1/2 justify-center"> <div className="flex shrink-0 basis-1/2 justify-center">
<Image <Image
alt={`img${idx}`}
className="border-border/50 aspect-[6/4] rounded-xl border" className="border-border/50 aspect-[6/4] rounded-xl border"
height={200}
src={feature.image} src={feature.image}
width={400} width={400}
height={200}
alt={`img${idx}`}
/> />
</div> </div>
<div className="flex basis-1/2 flex-col"> <div className="flex basis-1/2 flex-col">

View file

@ -37,7 +37,7 @@ export const Confirm = ({
confirmText?: string; confirmText?: string;
}) => { }) => {
return ( return (
<AlertDialog open={open} onOpenChange={setOpen}> <AlertDialog onOpenChange={setOpen} open={open}>
{onlyHedless ? null : ( {onlyHedless ? null : (
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger> <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
)} )}

View file

@ -20,24 +20,24 @@ export const Counter = ({
}; };
return ( return (
<div className="w-[9.5rem]"> <div className="w-[9.5rem]">
<label htmlFor={name} className="sr-only"> <label className="sr-only" htmlFor={name}>
{name} {name}
</label> </label>
<div className="flex items-center rounded-md border px-1"> <div className="flex items-center rounded-md border px-1">
<button <button
type="button"
onClick={() => handleChange(-1)}
className="flex size-10 items-center justify-center leading-10 transition hover:opacity-75 disabled:opacity-50" className="flex size-10 items-center justify-center leading-10 transition hover:opacity-75 disabled:opacity-50"
disabled={disabled} disabled={disabled}
onClick={() => handleChange(-1)}
type="button"
> >
<Minus /> <Minus />
</button> </button>
<Input <Input
type="number" className="no-spinner h-10 w-16 rounded-md border-none text-center shadow-none disabled:cursor-default sm:text-sm"
disabled={disabled}
id={name} id={name}
value={value}
onChange={(e) => { onChange={(e) => {
if (!Number.isNaN(Number.parseInt(e.target.value))) { if (!Number.isNaN(Number.parseInt(e.target.value))) {
setValue(Number.parseInt(e.target.value)); setValue(Number.parseInt(e.target.value));
@ -45,15 +45,15 @@ export const Counter = ({
e.target.value = String(value); e.target.value = String(value);
} }
}} }}
disabled={disabled} type="number"
className="no-spinner h-10 w-16 rounded-md border-none text-center shadow-none disabled:cursor-default sm:text-sm" value={value}
/> />
<button <button
type="button"
onClick={() => handleChange(1)}
className="flex size-10 items-center justify-center leading-10 transition hover:opacity-75 disabled:opacity-50" className="flex size-10 items-center justify-center leading-10 transition hover:opacity-75 disabled:opacity-50"
disabled={disabled} disabled={disabled}
onClick={() => handleChange(1)}
type="button"
> >
<Plus /> <Plus />
</button> </button>

View file

@ -46,15 +46,15 @@ export const AnimatedButton = forwardRef<
const sparklesAnimation: AnimationSequence = sparkles.map((_, index) => [ const sparklesAnimation: AnimationSequence = sparkles.map((_, index) => [
`.sparkle-${index}`, `.sparkle-${index}`,
{ {
opacity: 1,
scale: randomNumberBetween(sparklesScale[0], sparklesScale[1]),
x: randomNumberBetween(-sparklesRadius, sparklesRadius), x: randomNumberBetween(-sparklesRadius, sparklesRadius),
y: randomNumberBetween(-sparklesRadius, sparklesRadius), y: randomNumberBetween(-sparklesRadius, sparklesRadius),
scale: randomNumberBetween(sparklesScale[0], sparklesScale[1]),
opacity: 1,
}, },
{ {
duration: 0.4,
at: "<", at: "<",
delay: index * 0.01, delay: index * 0.01,
duration: 0.4,
}, },
]); ]);
@ -65,8 +65,8 @@ export const AnimatedButton = forwardRef<
scale: 0, scale: 0,
}, },
{ {
duration: 0.3,
at: "<", at: "<",
duration: 0.3,
}, },
]); ]);
@ -83,7 +83,7 @@ export const AnimatedButton = forwardRef<
animate([ animate([
...sparklesReset, ...sparklesReset,
["button", { scale: 0.8 }, { duration: 0.1, at: "<" }], ["button", { scale: 0.8 }, { at: "<", duration: 0.1 }],
["button", { scale: 1 }, { duration: 0.1 }], ["button", { scale: 1 }, { duration: 0.1 }],
...sparklesAnimation, ...sparklesAnimation,
["button", { duration: 0.000001 }], ["button", { duration: 0.000001 }],
@ -96,11 +96,11 @@ export const AnimatedButton = forwardRef<
})); }));
return ( return (
<div ref={scope} className="w-full"> <div className="w-full" ref={scope}>
<Button <Button
{...props} {...props}
className={cn("relative", props.className)}
aria-label="Animated Button" aria-label="Animated Button"
className={cn("relative", props.className)}
> >
{props.children} {props.children}
<span <span
@ -109,12 +109,12 @@ export const AnimatedButton = forwardRef<
> >
{Array.from({ length: sparklesNumber }).map((_, index) => ( {Array.from({ length: sparklesNumber }).map((_, index) => (
<IconMatrix <IconMatrix
type={sparklesIcon}
className={cn( className={cn(
`absolute top-1/2 left-1/2 opacity-0 sparkle-${index}`, `absolute top-1/2 left-1/2 opacity-0 sparkle-${index}`,
sparklesClassName, sparklesClassName,
)} )}
key={index} key={index}
type={sparklesIcon}
/> />
))} ))}
</span> </span>

View file

@ -32,9 +32,9 @@ export function AnimatedNumber({
onAnimationComplete, onAnimationComplete,
}: AnimatedNumberProps) { }: AnimatedNumberProps) {
const spring = useSpring(value, { const spring = useSpring(value, {
damping,
mass, mass,
stiffness, stiffness,
damping,
}); });
const display: MotionValue<string> = useTransform(spring, (current) => const display: MotionValue<string> = useTransform(spring, (current) =>

View file

@ -77,18 +77,18 @@ export const AutosizeTextarea = forwardRef<
const [triggerAutoSize, setTriggerAutoSize] = useState(""); const [triggerAutoSize, setTriggerAutoSize] = useState("");
useAutosizeTextArea({ useAutosizeTextArea({
textAreaRef: textAreaRef.current,
triggerAutoSize: triggerAutoSize,
maxHeight, maxHeight,
minHeight, minHeight,
textAreaRef: textAreaRef.current,
triggerAutoSize: triggerAutoSize,
}); });
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
// biome-ignore lint/style/noNonNullAssertion: <it exists>
textArea: textAreaRef.current!,
focus: () => textAreaRef.current?.focus(), focus: () => textAreaRef.current?.focus(),
maxHeight, maxHeight,
minHeight, minHeight,
// biome-ignore lint/style/noNonNullAssertion: <it exists>
textArea: textAreaRef.current!,
})); }));
useEffect(() => { useEffect(() => {
@ -100,8 +100,6 @@ export const AutosizeTextarea = forwardRef<
return ( return (
<textarea <textarea
{...props} {...props}
value={value}
ref={textAreaRef}
className={cn( className={cn(
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50", "border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className, className,
@ -110,6 +108,8 @@ export const AutosizeTextarea = forwardRef<
setTriggerAutoSize(e.target.value); setTriggerAutoSize(e.target.value);
onChange?.(e); onChange?.(e);
}} }}
ref={textAreaRef}
value={value}
/> />
); );
}, },

View file

@ -83,23 +83,23 @@ export function DataTable<TData, TValue>({
}, [rowSelection, columnFilters, columnVisibility, sorting, onStateChange]); }, [rowSelection, columnFilters, columnVisibility, sorting, onStateChange]);
const table = useReactTable({ const table = useReactTable({
data,
columns, columns,
onRowSelectionChange: setRowSelection, data,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(), getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters, onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility, onColumnVisibilityChange: setColumnVisibility,
onGlobalFilterChange: setGlobalFilter, onGlobalFilterChange: setGlobalFilter,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
state: { state: {
globalFilter,
sorting,
columnFilters, columnFilters,
columnVisibility, columnVisibility,
globalFilter,
rowSelection, rowSelection,
sorting,
}, },
}); });
@ -107,8 +107,8 @@ export function DataTable<TData, TValue>({
const { rows } = table.getRowModel(); const { rows } = table.getRowModel();
const virtualizer = useVirtualizer({ const virtualizer = useVirtualizer({
count: rows.length, count: rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 34, estimateSize: () => 34,
getScrollElement: () => parentRef.current,
overscan: 20, overscan: 20,
}); });
@ -121,11 +121,11 @@ export function DataTable<TData, TValue>({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<DataTableToolbar <DataTableToolbar
table={table}
pinnedColumns={pinnedFiltri}
searchColumn={searchColumn}
columns_titles={columns_titles} columns_titles={columns_titles}
globalFilter={globalFilter} globalFilter={globalFilter}
pinnedColumns={pinnedFiltri}
searchColumn={searchColumn}
table={table}
/> />
<div className="rounded-md border" ref={parentRef}> <div className="rounded-md border" ref={parentRef}>
<Table> <Table>
@ -154,25 +154,25 @@ export function DataTable<TData, TValue>({
const row = rows[virtualRow.index]!; const row = rows[virtualRow.index]!;
return ( return (
<TableRow <TableRow
className={cn(
rowOnClick && "cursor-pointer",
highlightedRow === row.id && "bg-neutral-100/50",
"group/row",
)}
data-row-id={row.id}
data-state={row.getIsSelected() && "selected"}
key={row.id}
onClick={ onClick={
rowOnClick rowOnClick
? () => rowOnClick(row.original, row.id) ? () => rowOnClick(row.original, row.id)
: undefined : undefined
} }
key={row.id}
data-state={row.getIsSelected() && "selected"}
data-row-id={row.id}
style={{ style={{
height: `${virtualRow.size}px`, height: `${virtualRow.size}px`,
transform: `translateY(${ transform: `translateY(${
virtualRow.start - index * virtualRow.size virtualRow.start - index * virtualRow.size
}px)`, }px)`,
}} }}
className={cn(
rowOnClick && "cursor-pointer",
highlightedRow === row.id && "bg-neutral-100/50",
"group/row",
)}
> >
{row.getVisibleCells().map((cell) => { {row.getVisibleCells().map((cell) => {
return ( return (
@ -190,8 +190,8 @@ export function DataTable<TData, TValue>({
) : ( ) : (
<TableRow> <TableRow>
<TableCell <TableCell
colSpan={columns.length}
className="h-24 text-center" className="h-24 text-center"
colSpan={columns.length}
> >
No results. No results.
</TableCell> </TableCell>
@ -201,7 +201,7 @@ export function DataTable<TData, TValue>({
</Table> </Table>
</div> </div>
{pagination ? ( {pagination ? (
<DataTablePagination table={table} hasSelectRow={hasSelectRow} /> <DataTablePagination hasSelectRow={hasSelectRow} table={table} />
) : null} ) : null}
</div> </div>
); );

View file

@ -41,23 +41,23 @@ export function DataTableFacetedFilter<TData, TValue>({
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="outline" size="sm" className="h-8 border-dashed"> <Button className="h-8 border-dashed" size="sm" variant="outline">
<CirclePlus className="mr-2 size-4" /> <CirclePlus className="mr-2 size-4" />
{title} {title}
{selectedValues?.size > 0 && ( {selectedValues?.size > 0 && (
<> <>
<Separator orientation="vertical" className="mx-2 h-4" /> <Separator className="mx-2 h-4" orientation="vertical" />
<Badge <Badge
variant="secondary"
className="rounded-xs px-1 font-normal lg:hidden" className="rounded-xs px-1 font-normal lg:hidden"
variant="secondary"
> >
{selectedValues.size} {selectedValues.size}
</Badge> </Badge>
<div className="hidden space-x-1 lg:flex"> <div className="hidden space-x-1 lg:flex">
{selectedValues.size > 2 ? ( {selectedValues.size > 2 ? (
<Badge <Badge
variant="secondary"
className="rounded-xs px-1 font-normal" className="rounded-xs px-1 font-normal"
variant="secondary"
> >
{selectedValues.size} selected {selectedValues.size} selected
</Badge> </Badge>
@ -66,9 +66,9 @@ export function DataTableFacetedFilter<TData, TValue>({
.filter((option) => selectedValues.has(option.value)) .filter((option) => selectedValues.has(option.value))
.map((option) => ( .map((option) => (
<Badge <Badge
variant="secondary"
key={option.value}
className="rounded-xs px-1 font-normal" className="rounded-xs px-1 font-normal"
key={option.value}
variant="secondary"
> >
{option.label} {option.label}
</Badge> </Badge>
@ -79,7 +79,7 @@ export function DataTableFacetedFilter<TData, TValue>({
)} )}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-[200px] p-0" align="start"> <PopoverContent align="start" className="w-[200px] p-0">
<Command> <Command>
<CommandInput placeholder={title} /> <CommandInput placeholder={title} />
<CommandList> <CommandList>
@ -130,8 +130,8 @@ export function DataTableFacetedFilter<TData, TValue>({
<CommandSeparator /> <CommandSeparator />
<CommandGroup> <CommandGroup>
<CommandItem <CommandItem
onSelect={() => column?.setFilterValue(undefined)}
className="justify-center text-center" className="justify-center text-center"
onSelect={() => column?.setFilterValue(undefined)}
> >
Reset filtri Reset filtri
</CommandItem> </CommandItem>

View file

@ -30,9 +30,9 @@ export function DataTableViewOptions<TData>({
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
variant="outline"
size="sm"
className="ml-auto hidden h-8 lg:flex" className="ml-auto hidden h-8 lg:flex"
size="sm"
variant="outline"
> >
<Filter className="mr-2 size-4" /> <Filter className="mr-2 size-4" />
Filtro Filtro
@ -44,9 +44,9 @@ export function DataTableViewOptions<TData>({
{hidableColumns.map((column) => { {hidableColumns.map((column) => {
return ( return (
<DropdownMenuCheckboxItem <DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()} checked={column.getIsVisible()}
className="capitalize"
key={column.id}
onCheckedChange={(value) => column.toggleVisibility(!!value)} onCheckedChange={(value) => column.toggleVisibility(!!value)}
> >
{columns_titles[column.id]} {columns_titles[column.id]}

View file

@ -36,9 +36,9 @@ export function DataTableColumnHeader<TData, TValue>({
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
variant="ghost"
size="sm"
className="data-[state=open]:bg-accent -ml-3 h-8" className="data-[state=open]:bg-accent -ml-3 h-8"
size="sm"
variant="ghost"
> >
<span>{title}</span> <span>{title}</span>
{column.getIsSorted() === "desc" ? ( {column.getIsSorted() === "desc" ? (

View file

@ -37,10 +37,10 @@ export function DataTablePagination<TData>({
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<p className="text-sm font-medium">Righe per pagina</p> <p className="text-sm font-medium">Righe per pagina</p>
<Select <Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => { onValueChange={(value) => {
table.setPageSize(Number(value)); table.setPageSize(Number(value));
}} }}
value={`${table.getState().pagination.pageSize}`}
> >
<SelectTrigger className="h-8 w-[70px]"> <SelectTrigger className="h-8 w-[70px]">
<SelectValue placeholder={table.getState().pagination.pageSize} /> <SelectValue placeholder={table.getState().pagination.pageSize} />
@ -61,37 +61,37 @@ export function DataTablePagination<TData>({
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Button <Button
variant="outline"
className="hidden size-8 p-0 lg:flex" className="hidden size-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
onClick={() => table.setPageIndex(0)}
variant="outline"
> >
<span className="sr-only">Vai alla prima pagina</span> <span className="sr-only">Vai alla prima pagina</span>
<ChevronFirst className="size-4" /> <ChevronFirst className="size-4" />
</Button> </Button>
<Button <Button
variant="outline"
className="size-8 p-0" className="size-8 p-0"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}
variant="outline"
> >
<span className="sr-only">Vai alla pagina precedente</span> <span className="sr-only">Vai alla pagina precedente</span>
<ChevronLeftIcon className="size-4" /> <ChevronLeftIcon className="size-4" />
</Button> </Button>
<Button <Button
variant="outline"
className="size-8 p-0" className="size-8 p-0"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
onClick={() => table.nextPage()}
variant="outline"
> >
<span className="sr-only">Vai alla pagina successiva</span> <span className="sr-only">Vai alla pagina successiva</span>
<ChevronRightIcon className="size-4" /> <ChevronRightIcon className="size-4" />
</Button> </Button>
<Button <Button
variant="outline"
className="hidden size-8 p-0 lg:flex" className="hidden size-8 p-0 lg:flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
variant="outline"
> >
<span className="sr-only">Vai alla pagina finale</span> <span className="sr-only">Vai alla pagina finale</span>
<ChevronLast className="size-4" /> <ChevronLast className="size-4" />

View file

@ -36,53 +36,53 @@ export function DataTableToolbar<TData>(props: {
{searchColumn && {searchColumn &&
(searchColumn.columnName ? ( (searchColumn.columnName ? (
<Input <Input
className="mt-0 h-8 w-[150px]"
onChange={(event) =>
table
.getColumn(searchColumn.columnName)
?.setFilterValue(event.target.value)
}
placeholder={searchColumn.placeholder} placeholder={searchColumn.placeholder}
value={ value={
(table (table
.getColumn(searchColumn.columnName) .getColumn(searchColumn.columnName)
?.getFilterValue() as string) ?? "" ?.getFilterValue() as string) ?? ""
} }
onChange={(event) =>
table
.getColumn(searchColumn.columnName)
?.setFilterValue(event.target.value)
}
className="mt-0 h-8 w-[150px]"
/> />
) : ( ) : (
<Input <Input
placeholder={searchColumn.placeholder} className="mt-0 h-8 w-[150px]"
value={globalFilter ?? ""}
onChange={(event) => onChange={(event) =>
table.setGlobalFilter(String(event.target.value)) table.setGlobalFilter(String(event.target.value))
} }
className="mt-0 h-8 w-[150px]" placeholder={searchColumn.placeholder}
value={globalFilter ?? ""}
/> />
))} ))}
{pinnedColumns?.map( {pinnedColumns?.map(
(column) => (column) =>
table.getColumn(column.columnName) && ( table.getColumn(column.columnName) && (
<DataTableFacetedFilter <DataTableFacetedFilter
key={column.columnName}
column={table.getColumn(column.columnName)} column={table.getColumn(column.columnName)}
title={column.title} key={column.columnName}
options={column.options} options={column.options}
title={column.title}
/> />
), ),
)} )}
{isFiltered && ( {isFiltered && (
<Button <Button
variant="ghost"
onClick={() => table.resetColumnFilters()}
className="h-8 px-2 lg:px-3" className="h-8 px-2 lg:px-3"
onClick={() => table.resetColumnFilters()}
variant="ghost"
> >
Reset Reset
<RotateCcw className="ml-2 size-4" /> <RotateCcw className="ml-2 size-4" />
</Button> </Button>
)} )}
</div> </div>
<DataTableViewOptions table={table} columns_titles={columns_titles} /> <DataTableViewOptions columns_titles={columns_titles} table={table} />
</div> </div>
); );
} }

View file

@ -29,11 +29,11 @@ function EmojiPicker({
}: React.ComponentProps<typeof EmojiPickerPrimitive.Root>) { }: React.ComponentProps<typeof EmojiPickerPrimitive.Root>) {
return ( return (
<EmojiPickerPrimitive.Root <EmojiPickerPrimitive.Root
data-slot="emoji-picker"
className={cn( className={cn(
"bg-popover text-popover-foreground isolate flex h-full w-fit flex-col overflow-hidden rounded-md", "bg-popover text-popover-foreground isolate flex h-full w-fit flex-col overflow-hidden rounded-md",
className, className,
)} )}
data-slot="emoji-picker"
{...props} {...props}
/> />
); );
@ -45,13 +45,13 @@ function EmojiPickerSearch({
}: React.ComponentProps<typeof EmojiPickerPrimitive.Search>) { }: React.ComponentProps<typeof EmojiPickerPrimitive.Search>) {
return ( return (
<div <div
data-slot="emoji-picker-search-wrapper"
className={cn("flex h-10 items-center gap-2 border-b px-3", className)} className={cn("flex h-10 items-center gap-2 border-b px-3", className)}
data-slot="emoji-picker-search-wrapper"
> >
<SearchIcon className="size-4 shrink-0 opacity-50" /> <SearchIcon className="size-4 shrink-0 opacity-50" />
<EmojiPickerPrimitive.Search <EmojiPickerPrimitive.Search
data-slot="emoji-picker-search"
className="placeholder:text-muted-foreground focus:border-accent-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50" className="placeholder:text-muted-foreground focus:border-accent-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50"
data-slot="emoji-picker-search"
{...props} {...props}
/> />
</div> </div>
@ -60,7 +60,7 @@ function EmojiPickerSearch({
function EmojiPickerRow({ children, ...props }: EmojiPickerListRowProps) { function EmojiPickerRow({ children, ...props }: EmojiPickerListRowProps) {
return ( return (
<div data-slot="emoji-picker-row" className="scroll-my-1 px-1" {...props}> <div className="scroll-my-1 px-1" data-slot="emoji-picker-row" {...props}>
{children} {children}
</div> </div>
); );
@ -73,11 +73,11 @@ function EmojiPickerEmoji({
}: EmojiPickerListEmojiProps) { }: EmojiPickerListEmojiProps) {
return ( return (
<button <button
data-slot="emoji-picker-emoji"
className={cn( className={cn(
"data-[active]:bg-accent flex size-7 items-center justify-center rounded-xs text-base", "data-[active]:bg-accent flex size-7 items-center justify-center rounded-xs text-base",
className, className,
)} )}
data-slot="emoji-picker-emoji"
{...props} {...props}
> >
{emoji.emoji} {emoji.emoji}
@ -91,8 +91,8 @@ function EmojiPickerCategoryHeader({
}: EmojiPickerListCategoryHeaderProps) { }: EmojiPickerListCategoryHeaderProps) {
return ( return (
<div <div
data-slot="emoji-picker-category-header"
className="bg-popover text-muted-foreground px-3 pt-3.5 pb-2 text-xs leading-none" className="bg-popover text-muted-foreground px-3 pt-3.5 pb-2 text-xs leading-none"
data-slot="emoji-picker-category-header"
{...props} {...props}
> >
{category.label} {category.label}
@ -106,30 +106,30 @@ function EmojiPickerContent({
}: React.ComponentProps<typeof EmojiPickerPrimitive.Viewport>) { }: React.ComponentProps<typeof EmojiPickerPrimitive.Viewport>) {
return ( return (
<EmojiPickerPrimitive.Viewport <EmojiPickerPrimitive.Viewport
data-slot="emoji-picker-viewport"
className={cn("relative flex-1 outline-hidden", className)} className={cn("relative flex-1 outline-hidden", className)}
data-slot="emoji-picker-viewport"
{...props} {...props}
> >
<EmojiPickerPrimitive.Loading <EmojiPickerPrimitive.Loading
data-slot="emoji-picker-loading"
className="text-muted-foreground absolute inset-0 flex items-center justify-center" className="text-muted-foreground absolute inset-0 flex items-center justify-center"
data-slot="emoji-picker-loading"
> >
<LoaderIcon className="size-4 animate-spin" /> <LoaderIcon className="size-4 animate-spin" />
</EmojiPickerPrimitive.Loading> </EmojiPickerPrimitive.Loading>
<EmojiPickerPrimitive.Empty <EmojiPickerPrimitive.Empty
data-slot="emoji-picker-empty"
className="text-muted-foreground absolute inset-0 flex items-center justify-center text-sm" className="text-muted-foreground absolute inset-0 flex items-center justify-center text-sm"
data-slot="emoji-picker-empty"
> >
No emoji found. No emoji found.
</EmojiPickerPrimitive.Empty> </EmojiPickerPrimitive.Empty>
<EmojiPickerPrimitive.List <EmojiPickerPrimitive.List
data-slot="emoji-picker-list"
className="pb-1 select-none" className="pb-1 select-none"
components={{ components={{
Row: EmojiPickerRow,
Emoji: EmojiPickerEmoji,
CategoryHeader: EmojiPickerCategoryHeader, CategoryHeader: EmojiPickerCategoryHeader,
Emoji: EmojiPickerEmoji,
Row: EmojiPickerRow,
}} }}
data-slot="emoji-picker-list"
/> />
</EmojiPickerPrimitive.Viewport> </EmojiPickerPrimitive.Viewport>
); );

View file

@ -114,8 +114,8 @@ function createStore(
invalid?: boolean, invalid?: boolean,
) { ) {
const initialState: StoreState = { const initialState: StoreState = {
files,
dragOver: false, dragOver: false,
files,
invalid: invalid ?? false, invalid: invalid ?? false,
}; };
@ -247,7 +247,7 @@ function createStore(
return () => listeners.delete(listener); return () => listeners.delete(listener);
} }
return { getState, dispatch, subscribe }; return { dispatch, getState, subscribe };
} }
const StoreContext = createContext<ReturnType<typeof createStore> | null>(null); const StoreContext = createContext<ReturnType<typeof createStore> | null>(null);
@ -277,7 +277,7 @@ function useStore<T>(selector: (state: StoreState) => T): T {
} }
const nextValue = selector(state); const nextValue = selector(state);
lastValueRef.current = { value: nextValue, state }; lastValueRef.current = { state, value: nextValue };
return nextValue; return nextValue;
}, [store, selector, lastValueRef]); }, [store, selector, lastValueRef]);
@ -380,26 +380,26 @@ const FileUploadRoot = forwardRef<HTMLDivElement, FileUploadRootProps>(
const contextValue = useMemo<FileUploadContextValue>( const contextValue = useMemo<FileUploadContextValue>(
() => ({ () => ({
dropzoneId,
inputId,
listId,
labelId,
dir, dir,
disabled, disabled,
dropzoneId,
inputId,
inputRef, inputRef,
labelId,
listId,
}), }),
[dropzoneId, inputId, listId, labelId, dir, disabled], [dropzoneId, inputId, listId, labelId, dir, disabled],
); );
useEffect(() => { useEffect(() => {
if (isControlled) { if (isControlled) {
store.dispatch({ variant: "SET_FILES", files: value }); store.dispatch({ files: value, variant: "SET_FILES" });
} else if ( } else if (
defaultValue && defaultValue &&
defaultValue.length > 0 && defaultValue.length > 0 &&
!store.getState().files.size !store.getState().files.size
) { ) {
store.dispatch({ variant: "SET_FILES", files: defaultValue }); store.dispatch({ files: defaultValue, variant: "SET_FILES" });
} }
}, [value, defaultValue, isControlled, store]); }, [value, defaultValue, isControlled, store]);
@ -497,14 +497,14 @@ const FileUploadRoot = forwardRef<HTMLDivElement, FileUploadRootProps>(
} }
if (invalid) { if (invalid) {
store.dispatch({ variant: "SET_INVALID", invalid }); store.dispatch({ invalid, variant: "SET_INVALID" });
setTimeout(() => { setTimeout(() => {
store.dispatch({ variant: "SET_INVALID", invalid: false }); store.dispatch({ invalid: false, variant: "SET_INVALID" });
}, 2000); }, 2000);
} }
if (acceptedFiles.length > 0) { if (acceptedFiles.length > 0) {
store.dispatch({ variant: "ADD_FILES", files: acceptedFiles }); store.dispatch({ files: acceptedFiles, variant: "ADD_FILES" });
if (isControlled && propsRef.current.onValueChange) { if (isControlled && propsRef.current.onValueChange) {
const currentFiles = Array.from( const currentFiles = Array.from(
@ -537,32 +537,32 @@ const FileUploadRoot = forwardRef<HTMLDivElement, FileUploadRootProps>(
async (files: File[]) => { async (files: File[]) => {
try { try {
for (const file of files) { for (const file of files) {
store.dispatch({ variant: "SET_PROGRESS", file, progress: 0 }); store.dispatch({ file, progress: 0, variant: "SET_PROGRESS" });
} }
if (propsRef.current.onUpload) { if (propsRef.current.onUpload) {
await propsRef.current.onUpload(files, { await propsRef.current.onUpload(files, {
onError: (file, error) => {
store.dispatch({
error: error.message ?? "Upload failed",
file,
variant: "SET_ERROR",
});
},
onProgress: (file, progress) => { onProgress: (file, progress) => {
store.dispatch({ store.dispatch({
variant: "SET_PROGRESS",
file, file,
progress: Math.min(Math.max(0, progress), 100), progress: Math.min(Math.max(0, progress), 100),
variant: "SET_PROGRESS",
}); });
}, },
onSuccess: (file) => { onSuccess: (file) => {
store.dispatch({ variant: "SET_SUCCESS", file }); store.dispatch({ file, variant: "SET_SUCCESS" });
},
onError: (file, error) => {
store.dispatch({
variant: "SET_ERROR",
file,
error: error.message ?? "Upload failed",
});
}, },
}); });
} else { } else {
for (const file of files) { for (const file of files) {
store.dispatch({ variant: "SET_SUCCESS", file }); store.dispatch({ file, variant: "SET_SUCCESS" });
} }
} }
} catch (error) { } catch (error) {
@ -570,9 +570,9 @@ const FileUploadRoot = forwardRef<HTMLDivElement, FileUploadRootProps>(
error instanceof Error ? error.message : "Upload failed"; error instanceof Error ? error.message : "Upload failed";
for (const file of files) { for (const file of files) {
store.dispatch({ store.dispatch({
variant: "SET_ERROR",
file,
error: errorMessage, error: errorMessage,
file,
variant: "SET_ERROR",
}); });
} }
} }
@ -600,26 +600,26 @@ const FileUploadRoot = forwardRef<HTMLDivElement, FileUploadRootProps>(
data-slot="file-upload" data-slot="file-upload"
dir={dir} dir={dir}
{...rootProps} {...rootProps}
ref={forwardedRef}
className={cn("relative flex flex-col gap-2", className)} className={cn("relative flex flex-col gap-2", className)}
ref={forwardedRef}
> >
{children} {children}
<input <input
type="file"
id={inputId}
aria-labelledby={labelId}
aria-describedby={dropzoneId}
ref={inputRef}
tabIndex={-1}
accept={accept} accept={accept}
name={name} aria-describedby={dropzoneId}
disabled={disabled} aria-labelledby={labelId}
multiple={multiple}
required={required}
className="sr-only" className="sr-only"
disabled={disabled}
id={inputId}
multiple={multiple}
name={name}
onChange={onInputChange} onChange={onInputChange}
ref={inputRef}
required={required}
tabIndex={-1}
type="file"
/> />
<span id={labelId} className="sr-only"> <span className="sr-only" id={labelId}>
{label ?? "File upload"} {label ?? "File upload"}
</span> </span>
</RootPrimitive> </RootPrimitive>
@ -671,7 +671,7 @@ const FileUploadDropzone = forwardRef<HTMLDivElement, FileUploadDropzoneProps>(
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
store.dispatch({ variant: "SET_DRAG_OVER", dragOver: true }); store.dispatch({ dragOver: true, variant: "SET_DRAG_OVER" });
}, },
[store, propsRef.current.onDragOver], [store, propsRef.current.onDragOver],
); );
@ -683,7 +683,7 @@ const FileUploadDropzone = forwardRef<HTMLDivElement, FileUploadDropzoneProps>(
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
store.dispatch({ variant: "SET_DRAG_OVER", dragOver: true }); store.dispatch({ dragOver: true, variant: "SET_DRAG_OVER" });
}, },
[store, propsRef.current.onDragEnter], [store, propsRef.current.onDragEnter],
); );
@ -695,7 +695,7 @@ const FileUploadDropzone = forwardRef<HTMLDivElement, FileUploadDropzoneProps>(
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
store.dispatch({ variant: "SET_DRAG_OVER", dragOver: false }); store.dispatch({ dragOver: false, variant: "SET_DRAG_OVER" });
}, },
[store, propsRef.current.onDragLeave], [store, propsRef.current.onDragLeave],
); );
@ -707,7 +707,7 @@ const FileUploadDropzone = forwardRef<HTMLDivElement, FileUploadDropzoneProps>(
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
store.dispatch({ variant: "SET_DRAG_OVER", dragOver: false }); store.dispatch({ dragOver: false, variant: "SET_DRAG_OVER" });
const files = Array.from(event.dataTransfer.files); const files = Array.from(event.dataTransfer.files);
const inputElement = context.inputRef.current; const inputElement = context.inputRef.current;
@ -743,19 +743,17 @@ const FileUploadDropzone = forwardRef<HTMLDivElement, FileUploadDropzoneProps>(
return ( return (
<DropzonePrimitive <DropzonePrimitive
role="region"
id={context.dropzoneId}
aria-controls={`${context.inputId} ${context.listId}`} aria-controls={`${context.inputId} ${context.listId}`}
//aria-disabled={context.disabled}
//aria-invalid={invalid}
data-disabled={context.disabled ? "" : undefined} data-disabled={context.disabled ? "" : undefined}
data-dragging={dragOver ? "" : undefined} data-dragging={dragOver ? "" : undefined}
//aria-disabled={context.disabled}
//aria-invalid={invalid}
data-invalid={invalid ? "" : undefined} data-invalid={invalid ? "" : undefined}
data-slot="file-upload-dropzone" data-slot="file-upload-dropzone"
dir={context.dir} dir={context.dir}
id={context.dropzoneId}
role="region"
{...dropzoneProps} {...dropzoneProps}
ref={forwardedRef}
tabIndex={context.disabled ? undefined : 0}
className={cn( className={cn(
"data-[dragging]:border-primary data-[invalid]:border-destructive data-[invalid]:ring-destructive/20 hover:bg-accent/30 focus-visible:border-ring/50 relative flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed p-6 outline-hidden transition-colors select-none data-[disabled]:pointer-events-none", "data-[dragging]:border-primary data-[invalid]:border-destructive data-[invalid]:ring-destructive/20 hover:bg-accent/30 focus-visible:border-ring/50 relative flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed p-6 outline-hidden transition-colors select-none data-[disabled]:pointer-events-none",
className, className,
@ -766,6 +764,8 @@ const FileUploadDropzone = forwardRef<HTMLDivElement, FileUploadDropzoneProps>(
onDragOver={onDragOver} onDragOver={onDragOver}
onDrop={onDrop} onDrop={onDrop}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
ref={forwardedRef}
tabIndex={context.disabled ? undefined : 0}
/> />
); );
}, },
@ -797,14 +797,14 @@ const FileUploadTrigger = forwardRef<HTMLButtonElement, FileUploadTriggerProps>(
return ( return (
<TriggerPrimitive <TriggerPrimitive
type="button"
aria-controls={context.inputId} aria-controls={context.inputId}
data-disabled={context.disabled ? "" : undefined} data-disabled={context.disabled ? "" : undefined}
data-slot="file-upload-trigger" data-slot="file-upload-trigger"
type="button"
{...triggerProps} {...triggerProps}
ref={forwardedRef}
disabled={context.disabled} disabled={context.disabled}
onClick={onClick} onClick={onClick}
ref={forwardedRef}
/> />
); );
}, },
@ -838,20 +838,20 @@ const FileUploadList = forwardRef<HTMLDivElement, FileUploadListProps>(
return ( return (
<ListPrimitive <ListPrimitive
role="list"
id={context.listId}
//aria-orientation={orientation}
data-orientation={orientation} data-orientation={orientation}
data-slot="file-upload-list" data-slot="file-upload-list"
//aria-orientation={orientation}
data-state={shouldRender ? "active" : "inactive"} data-state={shouldRender ? "active" : "inactive"}
dir={context.dir} dir={context.dir}
id={context.listId}
role="list"
{...listProps} {...listProps}
ref={forwardedRef}
className={cn( className={cn(
"data-[state=active]:animate-in data-[state=inactive]:animate-out data-[state=active]:fade-in-0 data-[state=inactive]:fade-out-0 data-[state=active]:slide-in-from-top-2 data-[state=inactive]:slide-out-to-top-2 flex flex-col gap-2", "data-[state=active]:animate-in data-[state=inactive]:animate-out data-[state=active]:fade-in-0 data-[state=inactive]:fade-out-0 data-[state=active]:slide-in-from-top-2 data-[state=inactive]:slide-out-to-top-2 flex flex-col gap-2",
orientation === "horizontal" && "flex-row overflow-x-auto p-1.5", orientation === "horizontal" && "flex-row overflow-x-auto p-1.5",
className, className,
)} )}
ref={forwardedRef}
/> />
); );
}, },
@ -904,12 +904,12 @@ const FileUploadItem = forwardRef<HTMLDivElement, FileUploadItemProps>(
const itemContext = useMemo( const itemContext = useMemo(
() => ({ () => ({
id,
fileState, fileState,
id,
messageId,
nameId, nameId,
sizeId, sizeId,
statusId, statusId,
messageId,
}), }),
[id, fileState, statusId, nameId, sizeId, messageId], [id, fileState, statusId, nameId, sizeId, messageId],
); );
@ -929,25 +929,25 @@ const FileUploadItem = forwardRef<HTMLDivElement, FileUploadItemProps>(
return ( return (
<FileUploadItemContext.Provider value={itemContext}> <FileUploadItemContext.Provider value={itemContext}>
<ItemPrimitive <ItemPrimitive
role="listitem"
id={id}
aria-setsize={fileCount}
aria-posinset={fileIndex}
aria-describedby={`${nameId} ${sizeId} ${statusId} ${ aria-describedby={`${nameId} ${sizeId} ${statusId} ${
fileState.error ? messageId : "" fileState.error ? messageId : ""
}`} }`}
aria-labelledby={nameId} aria-labelledby={nameId}
aria-posinset={fileIndex}
aria-setsize={fileCount}
data-slot="file-upload-item" data-slot="file-upload-item"
dir={context.dir} dir={context.dir}
id={id}
role="listitem"
{...itemProps} {...itemProps}
ref={forwardedRef}
className={cn( className={cn(
"relative flex items-center gap-2.5 rounded-md border p-3 has-[_[data-slot=file-upload-progress]]:flex-col has-[_[data-slot=file-upload-progress]]:items-start", "relative flex items-center gap-2.5 rounded-md border p-3 has-[_[data-slot=file-upload-progress]]:flex-col has-[_[data-slot=file-upload-progress]]:items-start",
className, className,
)} )}
ref={forwardedRef}
> >
{props.children} {props.children}
<span id={statusId} className="sr-only"> <span className="sr-only" id={statusId}>
{statusText} {statusText}
</span> </span>
</ItemPrimitive> </ItemPrimitive>
@ -1042,13 +1042,13 @@ const FileUploadItemPreview = forwardRef<
return ( return (
// biome-ignore lint/a11y/noNoninteractiveElementInteractions: <intended> // biome-ignore lint/a11y/noNoninteractiveElementInteractions: <intended>
<img <img
src={URL.createObjectURL(file)}
alt={file.name} alt={file.name}
className="size-full rounded-sm object-cover" className="size-full rounded-sm object-cover"
onLoad={(event) => { onLoad={(event) => {
if (!(event.target instanceof HTMLImageElement)) return; if (!(event.target instanceof HTMLImageElement)) return;
URL.revokeObjectURL(event.target.src); URL.revokeObjectURL(event.target.src);
}} }}
src={URL.createObjectURL(file)}
/> />
); );
} }
@ -1067,12 +1067,12 @@ const FileUploadItemPreview = forwardRef<
aria-labelledby={itemContext.nameId} aria-labelledby={itemContext.nameId}
data-slot="file-upload-preview" data-slot="file-upload-preview"
{...previewProps} {...previewProps}
ref={forwardedRef}
className={cn( className={cn(
"relative flex size-10 shrink-0 items-center justify-center rounded-md", "relative flex size-10 shrink-0 items-center justify-center rounded-md",
isImage ? "object-cover" : "bg-accent/50 [&>svg]:size-7", isImage ? "object-cover" : "bg-accent/50 [&>svg]:size-7",
className, className,
)} )}
ref={forwardedRef}
> >
{onPreviewRender(itemContext.fileState.file)} {onPreviewRender(itemContext.fileState.file)}
{children} {children}
@ -1103,27 +1103,27 @@ const FileUploadItemMetadata = forwardRef<
data-slot="file-upload-metadata" data-slot="file-upload-metadata"
dir={context.dir} dir={context.dir}
{...metadataProps} {...metadataProps}
ref={forwardedRef}
className={cn("flex min-w-0 flex-1 flex-col", className)} className={cn("flex min-w-0 flex-1 flex-col", className)}
ref={forwardedRef}
> >
{children ?? ( {children ?? (
<> <>
<span <span
id={itemContext.nameId}
className="truncate text-sm font-medium" className="truncate text-sm font-medium"
id={itemContext.nameId}
> >
{itemContext.fileState.file.name} {itemContext.fileState.file.name}
</span> </span>
<span <span
id={itemContext.sizeId}
className="text-muted-foreground text-xs" className="text-muted-foreground text-xs"
id={itemContext.sizeId}
> >
{formatBytes(itemContext.fileState.file.size)} {formatBytes(itemContext.fileState.file.size)}
</span> </span>
{itemContext.fileState.error && ( {itemContext.fileState.error && (
<span <span
id={itemContext.messageId}
className="text-destructive text-xs" className="text-destructive text-xs"
id={itemContext.messageId}
> >
{itemContext.fileState.error} {itemContext.fileState.error}
</span> </span>
@ -1162,45 +1162,45 @@ const FileUploadItemProgress = forwardRef<
return ( return (
<ItemProgressPrimitive <ItemProgressPrimitive
role="progressbar" aria-labelledby={itemContext.nameId}
aria-valuemin={0}
aria-valuemax={100} aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={itemContext.fileState.progress} aria-valuenow={itemContext.fileState.progress}
aria-valuetext={`${itemContext.fileState.progress}%`} aria-valuetext={`${itemContext.fileState.progress}%`}
aria-labelledby={itemContext.nameId}
data-slot="file-upload-progress" data-slot="file-upload-progress"
role="progressbar"
{...progressProps} {...progressProps}
ref={forwardedRef}
className={cn( className={cn(
"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2",
className, className,
)} )}
ref={forwardedRef}
> >
<svg <svg
className="rotate-[-90deg] transform" className="rotate-[-90deg] transform"
width={size}
height={size}
viewBox={`0 0 ${size} ${size}`}
fill="none" fill="none"
height={size}
stroke="currentColor" stroke="currentColor"
viewBox={`0 0 ${size} ${size}`}
width={size}
> >
<title>Progress</title> <title>Progress</title>
<circle <circle
className="text-primary/20" className="text-primary/20"
strokeWidth="2"
cx={size / 2} cx={size / 2}
cy={size / 2} cy={size / 2}
r={(size - 4) / 2} r={(size - 4) / 2}
strokeWidth="2"
/> />
<circle <circle
className="text-primary transition-all" className="text-primary transition-all"
strokeWidth="2"
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
cx={size / 2} cx={size / 2}
cy={size / 2} cy={size / 2}
r={(size - 4) / 2} r={(size - 4) / 2}
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
strokeWidth="2"
/> />
</svg> </svg>
</ItemProgressPrimitive> </ItemProgressPrimitive>
@ -1209,19 +1209,19 @@ const FileUploadItemProgress = forwardRef<
return ( return (
<ItemProgressPrimitive <ItemProgressPrimitive
role="progressbar" aria-labelledby={itemContext.nameId}
aria-valuemin={0}
aria-valuemax={100} aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={itemContext.fileState.progress} aria-valuenow={itemContext.fileState.progress}
aria-valuetext={`${itemContext.fileState.progress}%`} aria-valuetext={`${itemContext.fileState.progress}%`}
aria-labelledby={itemContext.nameId}
data-slot="file-upload-progress" data-slot="file-upload-progress"
role="progressbar"
{...progressProps} {...progressProps}
ref={forwardedRef}
className={cn( className={cn(
"bg-primary/20 relative h-1.5 w-full overflow-hidden rounded-full", "bg-primary/20 relative h-1.5 w-full overflow-hidden rounded-full",
className, className,
)} )}
ref={forwardedRef}
> >
<div <div
className="bg-primary h-full w-full flex-1 transition-all" className="bg-primary h-full w-full flex-1 transition-all"
@ -1255,8 +1255,8 @@ const FileUploadItemDelete = forwardRef<
if (!itemContext.fileState || event.defaultPrevented) return; if (!itemContext.fileState || event.defaultPrevented) return;
store.dispatch({ store.dispatch({
variant: "REMOVE_FILE",
file: itemContext.fileState.file, file: itemContext.fileState.file,
variant: "REMOVE_FILE",
}); });
}, },
[store, itemContext.fileState, propsRef.current?.onClick], [store, itemContext.fileState, propsRef.current?.onClick],
@ -1268,13 +1268,13 @@ const FileUploadItemDelete = forwardRef<
return ( return (
<ItemDeletePrimitive <ItemDeletePrimitive
type="button"
aria-controls={itemContext.id} aria-controls={itemContext.id}
aria-describedby={itemContext.nameId} aria-describedby={itemContext.nameId}
data-slot="file-upload-item-delete" data-slot="file-upload-item-delete"
type="button"
{...deleteProps} {...deleteProps}
ref={forwardedRef}
onClick={onClick} onClick={onClick}
ref={forwardedRef}
/> />
); );
}); });
@ -1315,14 +1315,14 @@ const FileUploadClear = forwardRef<HTMLButtonElement, FileUploadClearProps>(
return ( return (
<ClearPrimitive <ClearPrimitive
type="button"
aria-controls={context.listId} aria-controls={context.listId}
data-slot="file-upload-clear"
data-disabled={isDisabled ? "" : undefined} data-disabled={isDisabled ? "" : undefined}
data-slot="file-upload-clear"
type="button"
{...clearProps} {...clearProps}
ref={forwardedRef}
disabled={isDisabled} disabled={isDisabled}
onClick={onClick} onClick={onClick}
ref={forwardedRef}
/> />
); );
}, },

View file

@ -53,11 +53,11 @@ const useFormField = () => {
const { id } = itemContext; const { id } = itemContext;
return { return {
formDescriptionId: `${id}-form-item-description`,
formItemId: `${id}-form-item`,
formMessageId: `${id}-form-item-message`,
id, id,
name: fieldContext.name, name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState, ...fieldState,
}; };
}; };
@ -78,7 +78,7 @@ const FormItem = React.forwardRef<
return ( return (
<FormItemContext.Provider value={{ id }}> <FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} /> <div className={cn("space-y-2", className)} ref={ref} {...props} />
</FormItemContext.Provider> </FormItemContext.Provider>
); );
}); });
@ -92,9 +92,9 @@ const FormLabel = React.forwardRef<
return ( return (
<Label <Label
ref={ref}
className={cn(error && "text-red-500 dark:text-red-600", className)} className={cn(error && "text-red-500 dark:text-red-600", className)}
htmlFor={formItemId} htmlFor={formItemId}
ref={ref}
{...props} {...props}
/> />
); );
@ -110,14 +110,14 @@ const FormControl = React.forwardRef<
return ( return (
<Slot <Slot
ref={ref}
id={formItemId}
aria-describedby={ aria-describedby={
!error !error
? `${formDescriptionId}` ? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}` : `${formDescriptionId} ${formMessageId}`
} }
aria-invalid={!!error} aria-invalid={!!error}
id={formItemId}
ref={ref}
{...props} {...props}
/> />
); );
@ -132,12 +132,12 @@ const FormDescription = React.forwardRef<
return ( return (
<p <p
ref={ref}
id={formDescriptionId}
className={cn( className={cn(
"text-sm text-neutral-500 dark:text-neutral-400", "text-sm text-neutral-500 dark:text-neutral-400",
className, className,
)} )}
id={formDescriptionId}
ref={ref}
{...props} {...props}
/> />
); );
@ -157,12 +157,12 @@ const FormMessage = React.forwardRef<
return ( return (
<p <p
ref={ref}
id={formMessageId}
className={cn( className={cn(
"text-sm font-medium text-red-500 dark:text-red-600", "text-sm font-medium text-red-500 dark:text-red-600",
className, className,
)} )}
id={formMessageId}
ref={ref}
{...props} {...props}
> >
{body} {body}

View file

@ -7,8 +7,8 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => { ({ className, type, ...props }, ref) => {
return ( return (
<input <input
type={type}
ref={ref} ref={ref}
type={type}
{...props} {...props}
className={cn( className={cn(
`border-border bg-background text-primary placeholder:text-muted-foreground hover:border-muted-foreground focus:border-foreground focus:ring-foreground w-full rounded-lg border px-3 py-2 font-medium shadow-xs outline-hidden transition-all duration-200 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50`, `border-border bg-background text-primary placeholder:text-muted-foreground hover:border-muted-foreground focus:border-foreground focus:ring-foreground w-full rounded-lg border px-3 py-2 font-medium shadow-xs outline-hidden transition-all duration-200 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50`,

View file

@ -15,15 +15,15 @@ const LoadingButton = ({
}: ButtonProps & { loading?: boolean }) => { }: ButtonProps & { loading?: boolean }) => {
return ( return (
<Button <Button
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ className, size, variant }))}
{...props} {...props}
> >
{loading && ( {loading && (
<LoaderCircle <LoaderCircle
aria-hidden="true"
className="-ms-1 me-2 animate-spin" className="-ms-1 me-2 animate-spin"
size={16} size={16}
strokeWidth={2} strokeWidth={2}
aria-hidden="true"
/> />
)} )}
{props.children} {props.children}

View file

@ -81,15 +81,15 @@ export function MultiSelect({
return ( return (
<MultiSelectContext <MultiSelectContext
value={{ value={{
open,
setOpen,
selectedValues: values ? new Set(values) : selectedValues,
toggleValue,
items, items,
onItemAdded, onItemAdded,
open,
selectedValues: values ? new Set(values) : selectedValues,
setOpen,
toggleValue,
}} }}
> >
<Popover open={open} onOpenChange={setOpen}> <Popover onOpenChange={setOpen} open={open}>
{children} {children}
</Popover> </Popover>
</MultiSelectContext> </MultiSelectContext>
@ -110,13 +110,13 @@ export function MultiSelectTrigger({
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
{...props} {...props}
variant={props.variant ?? "outline"}
role={props.role ?? "combobox"}
aria-expanded={props["aria-expanded"] ?? open} aria-expanded={props["aria-expanded"] ?? open}
className={cn( className={cn(
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 border-input data-[placeholder]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex h-auto min-h-9 w-fit items-center justify-between gap-2 overflow-hidden rounded-md border bg-transparent px-3 py-1.5 text-sm whitespace-nowrap shadow-xs outline-hidden transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 border-input data-[placeholder]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex h-auto min-h-9 w-fit items-center justify-between gap-2 overflow-hidden rounded-md border bg-transparent px-3 py-1.5 text-sm whitespace-nowrap shadow-xs outline-hidden transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className, className,
)} )}
role={props.role ?? "combobox"}
variant={props.variant ?? "outline"}
> >
{children} {children}
<ChevronsUpDownIcon className="size-4 shrink-0 opacity-50" /> <ChevronsUpDownIcon className="size-4 shrink-0 opacity-50" />
@ -197,26 +197,17 @@ export function MultiSelectValue({
return ( return (
<div <div
{...props} {...props}
ref={valueRef}
className={cn( className={cn(
"flex w-full gap-1.5 overflow-hidden", "flex w-full gap-1.5 overflow-hidden",
shouldWrap && "h-full flex-wrap", shouldWrap && "h-full flex-wrap",
className, className,
)} )}
ref={valueRef}
> >
{[...selectedValues] {[...selectedValues]
.filter((value) => items.has(value)) .filter((value) => items.has(value))
.map((value) => ( .map((value) => (
<Badge <Badge
ref={(el) => {
if (el == null) return;
itemsRef.current.add(el);
return () => {
itemsRef.current.delete(el);
};
}}
variant="outline"
className="group flex items-center gap-1" className="group flex items-center gap-1"
key={value} key={value}
onClick={ onClick={
@ -227,6 +218,15 @@ export function MultiSelectValue({
} }
: undefined : undefined
} }
ref={(el) => {
if (el == null) return;
itemsRef.current.add(el);
return () => {
itemsRef.current.delete(el);
};
}}
variant="outline"
> >
{items.get(value)} {items.get(value)}
{clickToRemove && ( {clickToRemove && (
@ -235,11 +235,11 @@ export function MultiSelectValue({
</Badge> </Badge>
))} ))}
<Badge <Badge
ref={overflowRef}
style={{ style={{
display: overflowAmount > 0 && !shouldWrap ? "block" : "none", display: overflowAmount > 0 && !shouldWrap ? "block" : "none",
}} }}
variant="outline" variant="outline"
ref={overflowRef}
> >
+{overflowAmount} +{overflowAmount}
</Badge> </Badge>
@ -275,8 +275,8 @@ export function MultiSelectContent({
) : ( ) : (
// biome-ignore lint/a11y/noAriaHiddenOnFocusable: <intended> // biome-ignore lint/a11y/noAriaHiddenOnFocusable: <intended>
<button <button
autoFocus
aria-hidden="true" aria-hidden="true"
autoFocus
className="sr-only" className="sr-only"
type="button" type="button"
/> />
@ -315,11 +315,11 @@ export function MultiSelectItem({
return ( return (
<CommandItem <CommandItem
{...props} {...props}
value={value}
onSelect={(v) => { onSelect={(v) => {
toggleValue(v); toggleValue(v);
onSelect?.(v); onSelect?.(v);
}} }}
value={value}
> >
<CheckIcon <CheckIcon
className={cn("mr-2 size-4", isSelected ? "opacity-100" : "opacity-0")} className={cn("mr-2 size-4", isSelected ? "opacity-100" : "opacity-0")}

View file

@ -45,45 +45,45 @@ const VirtualizedMenuList = (
const virtualizer = useVirtualizer({ const virtualizer = useVirtualizer({
count, count,
getScrollElement: () => parentRef.current,
estimateSize: () => 45, estimateSize: () => 45,
getScrollElement: () => parentRef.current,
}); });
const items = virtualizer.getVirtualItems(); const items = virtualizer.getVirtualItems();
return ( return (
<div <div
ref={parentRef}
className="List" className="List"
ref={parentRef}
style={{ style={{
contain: "strict",
height: `${virtualizer.getTotalSize()}px`, height: `${virtualizer.getTotalSize()}px`,
maxHeight: "300px", maxHeight: "300px",
width: "auto",
overflowY: "auto", overflowY: "auto",
contain: "strict", width: "auto",
}} }}
> >
<div <div
style={{ style={{
height: virtualizer.getTotalSize(), height: virtualizer.getTotalSize(),
width: "100%",
position: "relative", position: "relative",
width: "100%",
}} }}
> >
<div <div
style={{ style={{
left: 0,
position: "absolute", position: "absolute",
top: 0, top: 0,
left: 0,
width: "100%",
transform: `translateY(${items[0]?.start ?? 0}px)`, transform: `translateY(${items[0]?.start ?? 0}px)`,
width: "100%",
}} }}
> >
{items.map((virtualRow) => ( {items.map((virtualRow) => (
<div <div
key={virtualRow.key}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
className={virtualRow.index % 2 ? "ListItemOdd" : "ListItemEven"} className={virtualRow.index % 2 ? "ListItemOdd" : "ListItemEven"}
data-index={virtualRow.index}
key={virtualRow.key}
ref={virtualizer.measureElement}
> >
{rows[virtualRow.index]} {rows[virtualRow.index]}
</div> </div>
@ -129,14 +129,45 @@ export const MultiSelect = ({
return ( return (
<ReactSelect <ReactSelect
menuPlacement={menuPlacement} classNamePrefix="select"
classNames={{
control: () =>
cn(
"mt-2 w-full rounded-lg h-min-[42px] dark:bg-primary bg-white shadow-xs outline-hidden text-foreground dark:text-white",
),
input: () => cn("*:ring-0 focus:*:ring-0"),
menu: () =>
cn(
`dark:bg-black text-foreground bg-white dark:text-white border shadow-xs outline-hidden
dark:border-foreground border-neutral-300 overflow-hidden`,
menuOpen
? "animate-in fade-in-0 zoom-in-90"
: "animate-out fade-out-0 zoom-out-90",
),
multiValue: () =>
cn(
"overflow-hidden dark:bg-black rounded-lg text-primary bg-white dark:text-white border border-neutral-300 dark:border-foreground shadow-xs outline-hidden",
),
multiValueLabel: () =>
cn(
"dark:bg-black rounded-lg text-primary bg-white dark:text-white outline-hidden",
),
multiValueRemove: () => cn("rounded-s-none rounded-e-md"),
option: () =>
cn(
"dark:bg-black text-foreground bg-white dark:text-white border-b border-neutral-300 dark:border-foreground shadow-xs outline-hidden hover:text-primary hover:bg-neutral-200 dark:hover:bg-neutral-700",
),
singleValue: () => cn("text-primary font-medium"),
valueContainer: () => cn("min-h-[38px]"),
}}
components={{ MenuList: VirtualizedMenuList }} components={{ MenuList: VirtualizedMenuList }}
defaultValue={defaultValue}
inputId={elementId} inputId={elementId}
isMulti={isMulti}
isDisabled={disabled}
instanceId={elementId} instanceId={elementId}
isDisabled={disabled}
isMulti={isMulti}
menuPlacement={menuPlacement}
name={elementName} name={elementName}
options={options}
onChange={(values) => { onChange={(values) => {
if (isMulti) { if (isMulti) {
const valore = values as ListOption[]; const valore = values as ListOption[];
@ -146,40 +177,34 @@ export const MultiSelect = ({
onValueChange(valore); onValueChange(valore);
} }
}} }}
defaultValue={defaultValue}
placeholder={placeholder}
onMenuOpen={() => setMenuOpen(true)}
onMenuClose={() => setMenuOpen(false)} onMenuClose={() => setMenuOpen(false)}
onMenuOpen={() => setMenuOpen(true)}
options={options}
placeholder={placeholder}
styles={{ styles={{
container: (base, state) => container: (base, state) =>
({ ({
...base, ...base,
":hover .select__dropdown-indicator": {
color: state.isFocused ? "rgb(161,161,170)" : "rgb(161,161,170)",
},
":hover .select__indicator-separator": { ":hover .select__indicator-separator": {
backgroundColor: state.isFocused backgroundColor: state.isFocused
? "rgb(161,161,170)" ? "rgb(161,161,170)"
: "rgb(161,161,170)", : "rgb(161,161,170)",
}, },
":hover .select__dropdown-indicator": {
color: state.isFocused ? "rgb(161,161,170)" : "rgb(161,161,170)",
},
}) as CSSObjectWithLabel, }) as CSSObjectWithLabel,
control: (base, state) => control: (base, state) =>
({ ({
...base, ...base,
minHeight: "42px",
borderRadius: "0.5rem",
boxShadow: undefined,
border: state.isFocused "& .select__dropdown-indicator": {
? "1px solid rgb(161,161,170)" color: state.isFocused
: "1px solid rgb(212 ,212 ,216)",
"&:hover": {
borderColor: state.isFocused
? "rgb(161,161,170)" ? "rgb(161,161,170)"
: "rgb(161 ,161 ,170)", : "rgb(212 ,212 ,216)",
transition: "none",
}, },
"& .select__indicator-separator": { "& .select__indicator-separator": {
@ -188,12 +213,23 @@ export const MultiSelect = ({
: "rgb(212 ,212 ,216)", : "rgb(212 ,212 ,216)",
}, },
"& .select__dropdown-indicator": { "&:hover": {
color: state.isFocused borderColor: state.isFocused
? "rgb(161,161,170)" ? "rgb(161,161,170)"
: "rgb(212 ,212 ,216)", : "rgb(161 ,161 ,170)",
transition: "none",
}, },
border: state.isFocused
? "1px solid rgb(161,161,170)"
: "1px solid rgb(212 ,212 ,216)",
borderRadius: "0.5rem",
boxShadow: undefined,
minHeight: "42px",
}) as CSSObjectWithLabel,
menu: (base) =>
({
...base,
borderRadius: "0.5rem",
}) as CSSObjectWithLabel, }) as CSSObjectWithLabel,
multiValue: (base) => multiValue: (base) =>
({ ({
@ -206,21 +242,9 @@ export const MultiSelect = ({
...base, ...base,
borderRadius: "0.5rem", borderRadius: "0.5rem",
}) as CSSObjectWithLabel, }) as CSSObjectWithLabel,
menu: (base) =>
({
...base,
borderRadius: "0.5rem",
}) as CSSObjectWithLabel,
option: (base, state) => option: (base, state) =>
({ ({
...base, ...base,
backgroundColor: undefined,
color: state.isSelected ? undefined : undefined,
"&:hover": {
backgroundColor: undefined,
color: state.isSelected ? undefined : undefined,
},
"&:active": { "&:active": {
backgroundColor: "rgb(161, 161, 170)", backgroundColor: "rgb(161, 161, 170)",
@ -230,39 +254,15 @@ export const MultiSelect = ({
"&:focus": { "&:focus": {
backgroundColor: undefined, backgroundColor: undefined,
}, },
"&:hover": {
backgroundColor: undefined,
color: state.isSelected ? undefined : undefined,
},
backgroundColor: undefined,
color: state.isSelected ? undefined : undefined,
}) as CSSObjectWithLabel, }) as CSSObjectWithLabel,
}} }}
classNames={{
valueContainer: () => cn("min-h-[38px]"),
control: () =>
cn(
"mt-2 w-full rounded-lg h-min-[42px] dark:bg-primary bg-white shadow-xs outline-hidden text-foreground dark:text-white",
),
option: () =>
cn(
"dark:bg-black text-foreground bg-white dark:text-white border-b border-neutral-300 dark:border-foreground shadow-xs outline-hidden hover:text-primary hover:bg-neutral-200 dark:hover:bg-neutral-700",
),
menu: () =>
cn(
`dark:bg-black text-foreground bg-white dark:text-white border shadow-xs outline-hidden
dark:border-foreground border-neutral-300 overflow-hidden`,
menuOpen
? "animate-in fade-in-0 zoom-in-90"
: "animate-out fade-out-0 zoom-out-90",
),
multiValueLabel: () =>
cn(
"dark:bg-black rounded-lg text-primary bg-white dark:text-white outline-hidden",
),
multiValue: () =>
cn(
"overflow-hidden dark:bg-black rounded-lg text-primary bg-white dark:text-white border border-neutral-300 dark:border-foreground shadow-xs outline-hidden",
),
multiValueRemove: () => cn("rounded-s-none rounded-e-md"),
input: () => cn("*:ring-0 focus:*:ring-0"),
singleValue: () => cn("text-primary font-medium"),
}}
classNamePrefix="select"
/> />
); );
}; };
@ -271,7 +271,7 @@ type UnpackOptions = {
options: string[]; options: string[];
}; };
export const UnpackOptions = ({ options }: UnpackOptions) => { export const UnpackOptions = ({ options }: UnpackOptions) => {
return options.map((v, idx) => ({ value: idx.toString(), label: v })); return options.map((v, idx) => ({ label: v, value: idx.toString() }));
}; };
type RepackValues = { type RepackValues = {

View file

@ -32,7 +32,7 @@ function Dialog({ children }: { children: ReactNode }) {
return ( return (
<DialogContext.Provider value={{ innerOpen, setInnerOpen }}> <DialogContext.Provider value={{ innerOpen, setInnerOpen }}>
<DialogPrimitive.Root open={outerOpen} onOpenChange={setOuterOpen}> <DialogPrimitive.Root onOpenChange={setOuterOpen} open={outerOpen}>
{children} {children}
</DialogPrimitive.Root> </DialogPrimitive.Root>
</DialogContext.Provider> </DialogContext.Provider>
@ -48,11 +48,11 @@ const DialogOverlay = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
ref={ref}
className={cn( className={cn(
"bg-background/40 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 backdrop-blur-sm", "bg-background/40 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 backdrop-blur-sm",
className, className,
)} )}
ref={ref}
{...props} {...props}
/> />
)); ));
@ -69,12 +69,12 @@ const DialogContent = forwardRef<
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref}
className={cn( className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:rounded-lg", "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:rounded-lg",
context.innerOpen && "translate-y-[-55%] scale-[0.97]", context.innerOpen && "translate-y-[-55%] scale-[0.97]",
className, className,
)} )}
ref={ref}
{...props} {...props}
> >
{children} {children}
@ -108,8 +108,8 @@ function InnerDialog({ children }: { children: ReactNode }) {
return ( return (
<DialogPrimitive.Root <DialogPrimitive.Root
open={context.innerOpen}
onOpenChange={context.setInnerOpen} onOpenChange={context.setInnerOpen}
open={context.innerOpen}
> >
{children} {children}
</DialogPrimitive.Root> </DialogPrimitive.Root>
@ -177,14 +177,6 @@ const InnerDialogContent = forwardRef<
return ( return (
<DialogPortal> <DialogPortal>
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
style={{
transform: `translate(-50%, calc(-50% + ${currentY}px))`,
transition: isDragging ? "none" : "transform 0.3s ease-out",
}}
className={cn( className={cn(
"bg-background fixed top-[50%] left-[50%] z-[60] grid w-full max-w-lg translate-x-[-50%] translate-y-[-45%] gap-4 rounded-lg border p-6 shadow-lg duration-200", "bg-background fixed top-[50%] left-[50%] z-[60] grid w-full max-w-lg translate-x-[-50%] translate-y-[-45%] gap-4 rounded-lg border p-6 shadow-lg duration-200",
isClosingByDrag isClosingByDrag
@ -203,6 +195,14 @@ const InnerDialogContent = forwardRef<
draggable && "", draggable && "",
className, className,
)} )}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
ref={ref}
style={{
transform: `translate(-50%, calc(-50% + ${currentY}px))`,
transition: isDragging ? "none" : "transform 0.3s ease-out",
}}
{...props} {...props}
> >
<div ref={contentRef}>{children}</div> <div ref={contentRef}>{children}</div>
@ -247,11 +247,11 @@ const InnerDialogTitle = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Title> ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref}
className={cn( className={cn(
"text-lg leading-none font-semibold tracking-tight", "text-lg leading-none font-semibold tracking-tight",
className, className,
)} )}
ref={ref}
{...props} {...props}
/> />
)); ));
@ -262,8 +262,8 @@ const InnerDialogDescription = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Description> ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Description <DialogPrimitive.Description
ref={ref}
className={cn("text-muted-foreground text-sm", className)} className={cn("text-muted-foreground text-sm", className)}
ref={ref}
{...props} {...props}
/> />
)); ));
@ -299,11 +299,11 @@ const DialogTitle = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Title> ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref}
className={cn( className={cn(
"text-lg leading-none font-semibold tracking-tight", "text-lg leading-none font-semibold tracking-tight",
className, className,
)} )}
ref={ref}
{...props} {...props}
/> />
)); ));
@ -314,8 +314,8 @@ const DialogDescription = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Description> ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Description <DialogPrimitive.Description
ref={ref}
className={cn("text-muted-foreground text-sm", className)} className={cn("text-muted-foreground text-sm", className)}
ref={ref}
{...props} {...props}
/> />
)); ));

View file

@ -9,6 +9,21 @@ type TextStaggeredFadeProps = {
hide?: boolean; hide?: boolean;
}; };
// Helper function to properly split text including emojis
const splitTextWithEmojis = (text: string): string[] => {
// Use Intl.Segmenter if available (modern browsers)
if (typeof Intl !== "undefined" && Intl.Segmenter) {
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
return Array.from(segmenter.segment(text), (segment) => segment.segment);
}
return Array.from(
text.matchAll(
/\p{Extended_Pictographic}|\p{Emoji_Component}|\p{Emoji_Modifier}|\p{Emoji_Modifier_Base}|\p{Emoji_Presentation}|./gu,
),
).map((match) => match[0]);
};
export const StaggeredFade: React.FC<TextStaggeredFadeProps> = ({ export const StaggeredFade: React.FC<TextStaggeredFadeProps> = ({
text, text,
className = "", className = "",
@ -17,30 +32,30 @@ export const StaggeredFade: React.FC<TextStaggeredFadeProps> = ({
const variants = { const variants = {
hidden: { opacity: 0 }, hidden: { opacity: 0 },
show: (i: number) => ({ show: (i: number) => ({
y: 0,
opacity: 1, opacity: 1,
transition: { delay: i * 0.07 }, transition: { delay: i * 0.07 },
y: 0,
}), }),
}; };
const letters = text.split(""); const letters = splitTextWithEmojis(text);
const ref = React.useRef(null); const ref = React.useRef(null);
const isInView = useInView(ref, { once: true }); const isInView = useInView(ref, { once: true });
return ( return (
<motion.h2 <motion.h2
ref={ref}
initial="hidden"
animate={isInView ? (!hide ? "show" : "hidden") : ""} animate={isInView ? (!hide ? "show" : "hidden") : ""}
variants={variants}
viewport={{ once: true }}
className={cn( className={cn(
"text-center text-xl font-bold tracking-tighter md:leading-[4rem]", "text-center text-xl font-bold tracking-tighter md:leading-[4rem]",
className, className,
)} )}
initial="hidden"
ref={ref}
variants={variants}
viewport={{ once: true }}
> >
{letters.map((word, i) => ( {letters.map((word, i) => (
<motion.span key={`${word}-${i}`} variants={variants} custom={i}> <motion.span custom={i} key={`${word}-${i}`} variants={variants}>
{word} {word}
</motion.span> </motion.span>
))} ))}

View file

@ -43,23 +43,23 @@ export const EtichetteDisplayer = ({
<div className={cn("flex flex-wrap gap-2", className)}> <div className={cn("flex flex-wrap gap-2", className)}>
{data.map((etichetta) => ( {data.map((etichetta) => (
<Etichetta <Etichetta
className="text-xs"
color_hex={etichetta.color_hex}
key={etichetta.id_etichetta} key={etichetta.id_etichetta}
title={etichetta.title} title={etichetta.title}
color_hex={etichetta.color_hex}
className="text-xs"
/> />
))} ))}
<button <button
type="button"
onClick={() => setOpenEdit(true)}
className="flex items-center justify-center rounded-lg border p-1" className="flex items-center justify-center rounded-lg border p-1"
onClick={() => setOpenEdit(true)}
type="button"
> >
<Plus className="size-4" /> <Plus className="size-4" />
</button> </button>
<EtichetteModal <EtichetteModal
chatId={chatId} chatId={chatId}
open={openEdit}
onOpenChange={handleOpen} onOpenChange={handleOpen}
open={openEdit}
/> />
</div> </div>
); );
@ -93,7 +93,7 @@ export const EtichetteModal = ({
}); });
if (!data) return null; if (!data) return null;
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog onOpenChange={onOpenChange} open={open}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Etichette Attive</DialogTitle> <DialogTitle>Etichette Attive</DialogTitle>
@ -102,10 +102,10 @@ export const EtichetteModal = ({
<div className="space-y-5"> <div className="space-y-5">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{data.map((etichetta, idx) => ( {data.map((etichetta, idx) => (
<div key={idx} className="flex gap-2"> <div className="flex gap-2" key={idx}>
<Etichetta <Etichetta
title={etichetta.title}
color_hex={etichetta.color_hex} color_hex={etichetta.color_hex}
title={etichetta.title}
/> />
<Button <Button
onClick={() => { onClick={() => {
@ -179,9 +179,9 @@ export const Etichetta = ({
function hexToRgb(hex: string): { r: number; g: number; b: number } { function hexToRgb(hex: string): { r: number; g: number; b: number } {
const bigint = parseInt(hex.slice(1), 16); const bigint = parseInt(hex.slice(1), 16);
return { return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255, b: bigint & 255,
g: (bigint >> 8) & 255,
r: (bigint >> 16) & 255,
}; };
} }

View file

@ -17,17 +17,17 @@ export default function FailedAnnuncioLoading() {
<p className="text-neutral-600">{t.annuncio_load_fail.sottotitolo}</p> <p className="text-neutral-600">{t.annuncio_load_fail.sottotitolo}</p>
<div className="flex flex-wrap items-center justify-center gap-3"> <div className="flex flex-wrap items-center justify-center gap-3">
<button <button
type="button" className="block rounded-lg bg-rose-600 px-4 py-2 font-medium text-white duration-150 hover:bg-rose-500 active:bg-rose-700"
onClick={() => { onClick={() => {
router.back(); router.back();
}} }}
className="block rounded-lg bg-rose-600 px-4 py-2 font-medium text-white duration-150 hover:bg-rose-500 active:bg-rose-700" type="button"
> >
{t.annuncio_load_fail.CTA} {t.annuncio_load_fail.CTA}
</button> </button>
<Link <Link
href="/annunci"
className="block rounded-lg border px-4 py-2 font-medium text-neutral-700 duration-150 hover:bg-neutral-50 active:bg-neutral-100" className="block rounded-lg border px-4 py-2 font-medium text-neutral-700 duration-150 hover:bg-neutral-50 active:bg-neutral-100"
href="/annunci"
> >
{t.annuncio_load_fail.home} {t.annuncio_load_fail.home}
</Link> </Link>

View file

@ -10,9 +10,9 @@ export const Footer = () => {
<div className="mx-auto max-w-6xl px-4 py-6 sm:px-6 lg:px-8"> <div className="mx-auto max-w-6xl px-4 py-6 sm:px-6 lg:px-8">
<div className="text-center"> <div className="text-center">
<Link <Link
href="/"
aria-label="footerLogo" aria-label="footerLogo"
className="flex items-center justify-center text-3xl font-bold tracking-wide antialiased" className="flex items-center justify-center text-3xl font-bold tracking-wide antialiased"
href="/"
> >
<LogoSvg className="h-12 w-auto [&>g]:fill-red-500 dark:[&>g]:fill-neutral-100 [&>text]:fill-neutral-600 [&>text]:stroke-neutral-600 dark:[&>text]:fill-neutral-100 dark:[&>text]:stroke-neutral-100" /> <LogoSvg className="h-12 w-auto [&>g]:fill-red-500 dark:[&>g]:fill-neutral-100 [&>text]:fill-neutral-600 [&>text]:stroke-neutral-600 dark:[&>text]:fill-neutral-100 dark:[&>text]:stroke-neutral-100" />
</Link> </Link>
@ -22,9 +22,9 @@ export const Footer = () => {
<ul className="flex flex-wrap justify-center gap-6 md:gap-8 lg:gap-12"> <ul className="flex flex-wrap justify-center gap-6 md:gap-8 lg:gap-12">
<li> <li>
<Link <Link
aria-label="footerGuida"
className="text-sm" className="text-sm"
href="/guida" href="/guida"
aria-label="footerGuida"
> >
{t.footer.Guida} {t.footer.Guida}
</Link> </Link>
@ -32,9 +32,9 @@ export const Footer = () => {
<li> <li>
<Link <Link
aria-label="footerChiSiamo"
className="text-sm" className="text-sm"
href="/chi-siamo" href="/chi-siamo"
aria-label="footerChiSiamo"
> >
{t.footer["Chi Siamo"]} {t.footer["Chi Siamo"]}
</Link> </Link>
@ -42,18 +42,18 @@ export const Footer = () => {
<li> <li>
<Link <Link
aria-label="footerTerminiCondizioni"
className="text-sm" className="text-sm"
href="/termini-condizioni" href="/termini-condizioni"
aria-label="footerTerminiCondizioni"
> >
{t.termini_condizioni_title} {t.termini_condizioni_title}
</Link> </Link>
</li> </li>
<li> <li>
<Link <Link
aria-label="footerPrivacyPolicy"
className="text-sm" className="text-sm"
href="/privacy-policy" href="/privacy-policy"
aria-label="footerPrivacyPolicy"
> >
{t.privacy_policy_title} {t.privacy_policy_title}
</Link> </Link>
@ -82,8 +82,8 @@ export const MiniFooter = () => {
<div className="text-center"> <div className="text-center">
<Link <Link
aria-label="footerLogo" aria-label="footerLogo"
href="/"
className="text-2xl font-bold tracking-wide antialiased" className="text-2xl font-bold tracking-wide antialiased"
href="/"
> >
Infoalloggi.it Infoalloggi.it
</Link> </Link>

View file

@ -52,9 +52,9 @@ type MapProps = {
} & (GroupProps | SingleProps); } & (GroupProps | SingleProps);
const defaults = { const defaults = {
zoom: 15,
width: "500px",
height: "500px", height: "500px",
width: "500px",
zoom: 15,
}; };
const MedianPoint = (pos: posData[]) => { const MedianPoint = (pos: posData[]) => {
@ -92,20 +92,20 @@ const MapComp = ({
case "price": case "price":
return ( return (
<CustomMarker <CustomMarker
position={pos.pos}
eventHandlers={pos.onClick && { click: pos.onClick }} eventHandlers={pos.onClick && { click: pos.onClick }}
title={pos.markerTxt}
icon={ icon={
<MapMarker <MapMarker
color="primary"
label={ label={
<span className="inline-block whitespace-nowrap"> <span className="inline-block whitespace-nowrap">
{pos.markerTxt} {pos.markerTxt}
</span> </span>
} }
color="primary"
size="sm" size="sm"
/> />
} }
position={pos.pos}
title={pos.markerTxt}
> >
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>} {pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</CustomMarker> </CustomMarker>
@ -114,8 +114,8 @@ const MapComp = ({
case "precise": case "precise":
return ( return (
<Marker <Marker
position={pos.pos}
eventHandlers={pos.onClick && { click: pos.onClick }} eventHandlers={pos.onClick && { click: pos.onClick }}
position={pos.pos}
title={pos.markerTxt} title={pos.markerTxt}
> >
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>} {pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
@ -125,9 +125,9 @@ const MapComp = ({
return ( return (
<Circle <Circle
center={pos.pos} center={pos.pos}
eventHandlers={pos.onClick && { click: pos.onClick }}
pathOptions={{ color: "red" }} pathOptions={{ color: "red" }}
radius={markerRadius || 100} radius={markerRadius || 100}
eventHandlers={pos.onClick && { click: pos.onClick }}
> >
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>} {pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</Circle> </Circle>
@ -137,22 +137,22 @@ const MapComp = ({
<> <>
<Circle <Circle
center={pos.pos} center={pos.pos}
pathOptions={{ color: "#ec4899" }}
fillOpacity={1}
radius={markerRadius || 100}
eventHandlers={pos.onClick && { click: pos.onClick }} eventHandlers={pos.onClick && { click: pos.onClick }}
fillOpacity={1}
pathOptions={{ color: "#ec4899" }}
radius={markerRadius || 100}
> >
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>} {pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</Circle> </Circle>
<CustomMarker <CustomMarker
position={pos.pos}
eventHandlers={pos.onClick && { click: pos.onClick }} eventHandlers={pos.onClick && { click: pos.onClick }}
title={pos.markerTxt}
icon={ icon={
<div className="relative flex size-10 items-center justify-center rounded-full bg-pink-500 p-2"> <div className="relative flex size-10 items-center justify-center rounded-full bg-pink-500 p-2">
<Home className="absolute size-3/4 stroke-white" /> <Home className="absolute size-3/4 stroke-white" />
</div> </div>
} }
position={pos.pos}
title={pos.markerTxt}
> >
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>} {pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</CustomMarker> </CustomMarker>
@ -167,24 +167,24 @@ const MapComp = ({
return ( return (
<MapContainer <MapContainer
key={hashKey}
center={ center={
autocenter ? MedianPoint(isGroup ? groupData : [posix]) : posix.pos autocenter ? MedianPoint(isGroup ? groupData : [posix]) : posix.pos
} }
zoom={zoom} className={cn("")}
key={hashKey}
scrollWheelZoom={scrollWheelZoom} scrollWheelZoom={scrollWheelZoom}
style={{ style={{
borderRadius: "8px",
height: height, height: height,
width: width, width: width,
borderRadius: "8px",
}} }}
className={cn("")} zoom={zoom}
> >
<APIs /> <APIs />
<TileLayer <TileLayer
url="/api/tiles/{s}/{z}/{x}/{y}"
//url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
//url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
url="/api/tiles/{s}/{z}/{x}/{y}"
/> />
{isGroup ? ( {isGroup ? (

View file

@ -248,9 +248,9 @@ const ComponentMarker = forwardRef<LeafletMarker, BaseMarkerProps>(
...(iconAnchor ? { iconAnchor } : []), ...(iconAnchor ? { iconAnchor } : []),
...(popupAnchor ? { popupAnchor } : []), ...(popupAnchor ? { popupAnchor } : []),
...(tooltipAnchor ? { tooltipAnchor } : []), ...(tooltipAnchor ? { tooltipAnchor } : []),
pane,
attribution, attribution,
className, className,
pane,
}); });
}, iconDeps); }, iconDeps);
@ -370,9 +370,9 @@ export const Marker = forwardRef<LeafletMarker, MarkerProps>(
if (validElement) { if (validElement) {
return ( return (
<ComponentMarker <ComponentMarker
ref={ref}
icon={Icon}
componentIconOpts={componentIconOpts} componentIconOpts={componentIconOpts}
icon={Icon}
ref={ref}
{...otherProps} {...otherProps}
/> />
); );
@ -381,9 +381,9 @@ export const Marker = forwardRef<LeafletMarker, MarkerProps>(
if (validComponent) { if (validComponent) {
return ( return (
<ComponentMarker <ComponentMarker
ref={ref}
icon={<Icon />}
componentIconOpts={componentIconOpts} componentIconOpts={componentIconOpts}
icon={<Icon />}
ref={ref}
{...otherProps} {...otherProps}
/> />
); );
@ -391,8 +391,8 @@ export const Marker = forwardRef<LeafletMarker, MarkerProps>(
return ( return (
<ReactLeafletMarker <ReactLeafletMarker
ref={ref}
icon={Icon as ReactLeafletMarkerProps["icon"]} icon={Icon as ReactLeafletMarkerProps["icon"]}
ref={ref}
{...otherProps} {...otherProps}
/> />
); );

View file

@ -16,54 +16,54 @@ export default function MapMarker({
size = "md", size = "md",
}: MapMarkerProps) { }: MapMarkerProps) {
const colorClasses = { const colorClasses = {
primary: {
rectangle: "bg-[#242428] text-white",
triangle: "border-t-[#242428]",
},
red: {
rectangle: "bg-red-500 text-white",
triangle: "border-t-red-500",
},
blue: { blue: {
rectangle: "bg-blue-500 text-white", rectangle: "bg-blue-500 text-white",
triangle: "border-t-blue-500", triangle: "border-t-blue-500",
}, },
green: {
rectangle: "bg-green-500 text-white",
triangle: "border-t-green-500",
},
yellow: {
rectangle: "bg-yellow-500 text-black",
triangle: "border-t-yellow-500",
},
purple: {
rectangle: "bg-purple-500 text-white",
triangle: "border-t-purple-500",
},
gray: { gray: {
rectangle: "bg-gray-500 text-white", rectangle: "bg-gray-500 text-white",
triangle: "border-t-gray-500", triangle: "border-t-gray-500",
}, },
green: {
rectangle: "bg-green-500 text-white",
triangle: "border-t-green-500",
},
primary: {
rectangle: "bg-[#242428] text-white",
triangle: "border-t-[#242428]",
},
purple: {
rectangle: "bg-purple-500 text-white",
triangle: "border-t-purple-500",
},
red: {
rectangle: "bg-red-500 text-white",
triangle: "border-t-red-500",
},
yellow: {
rectangle: "bg-yellow-500 text-black",
triangle: "border-t-yellow-500",
},
}; };
const sizeClasses = { const sizeClasses = {
sm: { lg: {
container: "h-8", container: "h-16",
rectangle: "px-2 py-1 rounded-md", rectangle: "px-4 py-3 rounded-xl",
triangle: "border-l-[8px] border-r-[8px] border-t-[8px]", text: "text-base",
text: "text-xs", triangle: "border-l-[12px] border-r-[12px] border-t-[12px]",
}, },
md: { md: {
container: "h-12", container: "h-12",
rectangle: "px-3 py-2 rounded-lg", rectangle: "px-3 py-2 rounded-lg",
triangle: "border-l-[10px] border-r-[10px] border-t-[10px]",
text: "text-sm", text: "text-sm",
triangle: "border-l-[10px] border-r-[10px] border-t-[10px]",
}, },
lg: { sm: {
container: "h-16", container: "h-8",
rectangle: "px-4 py-3 rounded-xl", rectangle: "px-2 py-1 rounded-md",
triangle: "border-l-[12px] border-r-[12px] border-t-[12px]", text: "text-xs",
text: "text-base", triangle: "border-l-[8px] border-r-[8px] border-t-[8px]",
}, },
}; };

View file

@ -25,10 +25,10 @@ export const LangToggle = () => {
return ( return (
<LoadingButton <LoadingButton
aria-label="Change Language" aria-label="Change Language"
loading={busy}
variant="outline"
className="text-primary flex w-fit items-center gap-2" className="text-primary flex w-fit items-center gap-2"
loading={busy}
onClick={() => handleLangChange(locale === "it" ? "en" : "it")} onClick={() => handleLangChange(locale === "it" ? "en" : "it")}
variant="outline"
> >
{locale === "it" ? ( {locale === "it" ? (
<> <>

View file

@ -64,7 +64,7 @@ export const UserHeaderSection = ({
{!session || !session.user ? ( {!session || !session.user ? (
<Button <Button
aria-label="Login Button" aria-label="Login Button"
variant="ghost" className="flex h-[46px] w-full items-center justify-center gap-x-1 rounded-lg p-2"
onClick={async () => { onClick={async () => {
await router.push({ await router.push({
pathname: "/login", pathname: "/login",
@ -72,24 +72,24 @@ export const UserHeaderSection = ({
}); });
setSidebar(false); setSidebar(false);
}} }}
className="flex h-[46px] w-full items-center justify-center gap-x-1 rounded-lg p-2" variant="ghost"
> >
{inSidebar && t.auth.login.titolo} {inSidebar && t.auth.login.titolo}
<LogIn className="size-5" /> <LogIn className="size-5" />
</Button> </Button>
) : ( ) : (
<DropdownMenu open={open} onOpenChange={setOpen}> <DropdownMenu onOpenChange={setOpen} open={open}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
aria-label="User Profile Button" aria-label="User Profile Button"
variant="ghost"
className="flex h-[46px] w-full items-center justify-center gap-x-1 rounded-full p-2 font-medium" className="flex h-[46px] w-full items-center justify-center gap-x-1 rounded-full p-2 font-medium"
variant="ghost"
> >
<UserAvatar <UserAvatar
username={session.user.username}
userId={session.user.id}
className="size-10" className="size-10"
userId={session.user.id}
username={session.user.username}
/> />
{inSidebar ? ( {inSidebar ? (
<span>{session.user.username}</span> <span>{session.user.username}</span>
@ -100,7 +100,7 @@ export const UserHeaderSection = ({
)} )}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount> <DropdownMenuContent align="end" className="w-56" forceMount>
{session.user.isAdmin ? ( {session.user.isAdmin ? (
<> <>
<DropdownMenuLabel className="font-normal"> <DropdownMenuLabel className="font-normal">
@ -120,8 +120,8 @@ export const UserHeaderSection = ({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
className="flex cursor-pointer items-center gap-3 px-2 py-1.5"
aria-label="Logout Button" aria-label="Logout Button"
className="flex cursor-pointer items-center gap-3 px-2 py-1.5"
onClick={() => { onClick={() => {
logout(); logout();
}} }}
@ -179,16 +179,16 @@ const Items = ({
return ( return (
<DropdownMenuGroup> <DropdownMenuGroup>
{items?.map((item, idx) => ( {items?.map((item, idx) => (
<DropdownMenuItem key={idx} className="p-0"> <DropdownMenuItem className="p-0" key={idx}>
<Link <Link
aria-label={`Link to ${item.title}`} aria-label={`Link to ${item.title}`}
onClick={() => setOpen(false)}
href={item.href}
className="flex h-full w-full items-center gap-3 px-2 py-1.5" className="flex h-full w-full items-center gap-3 px-2 py-1.5"
href={item.href}
onClick={() => setOpen(false)}
> >
<IconMatrix <IconMatrix
type={item.icon.icon}
className={cn("size-6", item.icon.className)} className={cn("size-6", item.icon.className)}
type={item.icon.icon}
/> />
{item.title} {item.title}

View file

@ -14,8 +14,8 @@ export function MainNav({ pathname }: MainNavProps) {
<div className="mr-2 hidden gap-6 md:flex"> <div className="mr-2 hidden gap-6 md:flex">
<Link <Link
aria-label="mainNavLink" aria-label="mainNavLink"
href="/"
className="flex shrink-0 items-center font-medium md:mb-0" className="flex shrink-0 items-center font-medium md:mb-0"
href="/"
> >
<LogoSvg className="[&>text]:fill-primary [&>text]:stroke-primary h-8 w-auto [&>g]:fill-red-500" /> <LogoSvg className="[&>text]:fill-primary [&>text]:stroke-primary h-8 w-auto [&>g]:fill-red-500" />
</Link> </Link>
@ -24,12 +24,12 @@ export function MainNav({ pathname }: MainNavProps) {
{t.nav.mainNav.map((item) => ( {t.nav.mainNav.map((item) => (
<Link <Link
aria-label={item.title} aria-label={item.title}
key={item.href}
href={item.href}
className={cn( className={cn(
"hover:text-primary/80 transition-colors", "hover:text-primary/80 transition-colors",
pathname === item.href ? "text-primary" : "text-primary/70", pathname === item.href ? "text-primary" : "text-primary/70",
)} )}
href={item.href}
key={item.href}
> >
{item.title} {item.title}
</Link> </Link>

View file

@ -41,19 +41,19 @@ export function MobileNav({
const verySmallScreen = useMediaQuery("(max-width: 320px)"); const verySmallScreen = useMediaQuery("(max-width: 320px)");
return ( return (
<> <>
<Sheet open={open} onOpenChange={setOpen}> <Sheet onOpenChange={setOpen} open={open}>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button <Button
aria-label="Toggle Mobile Menu" aria-label="Toggle Mobile Menu"
variant="ghost"
className="mr-2 size-8 p-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden [&_svg]:size-6" className="mr-2 size-8 p-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden [&_svg]:size-6"
variant="ghost"
> >
<AlignLeft /> <AlignLeft />
<span className="sr-only">Toggle Menu</span> <span className="sr-only">Toggle Menu</span>
</Button> </Button>
</SheetTrigger> </SheetTrigger>
<SheetContent side="left" className="z-500 p-0"> <SheetContent className="z-500 p-0" side="left">
<SheetHeader> <SheetHeader>
<SheetTitle className="sr-only">Mobile Menu</SheetTitle> <SheetTitle className="sr-only">Mobile Menu</SheetTitle>
<SheetDescription className="sr-only"> <SheetDescription className="sr-only">
@ -62,9 +62,9 @@ export function MobileNav({
</SheetHeader> </SheetHeader>
<div className="flex h-full flex-col space-y-2 overflow-y-auto px-3 pb-4"> <div className="flex h-full flex-col space-y-2 overflow-y-auto px-3 pb-4">
<MobileLink <MobileLink
href="/"
aria-label="mainNavLinkHome" aria-label="mainNavLinkHome"
className="ml-2 flex items-center gap-3" className="ml-2 flex items-center gap-3"
href="/"
onOpenChange={setOpen} onOpenChange={setOpen}
> >
<Home className="size-6" />{" "} <Home className="size-6" />{" "}
@ -88,18 +88,18 @@ export function MobileNav({
<div className="text-primary flex h-full grow flex-col gap-3"> <div className="text-primary flex h-full grow flex-col gap-3">
{t.nav.mainNav.map((item) => ( {t.nav.mainNav.map((item) => (
<MobileLink <MobileLink
key={item.href}
href={item.href}
onOpenChange={setOpen}
className={cn( className={cn(
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium", "flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted", item.href === pathname && "bg-muted",
)} )}
href={item.href}
key={item.href}
onOpenChange={setOpen}
> >
<IconMatrix <IconMatrix
className="size-5"
key={item.title} key={item.title}
type={item.icon} type={item.icon}
className="size-5"
/>{" "} />{" "}
{item.title} {item.title}
</MobileLink> </MobileLink>
@ -109,10 +109,10 @@ export function MobileNav({
<div className="flex h-fit flex-col gap-4 pb-2"> <div className="flex h-fit flex-col gap-4 pb-2">
<UserHeaderSection <UserHeaderSection
inSidebar
open={userOpen} open={userOpen}
setOpen={setUserOpen} setOpen={setUserOpen}
setSidebar={setOpen} setSidebar={setOpen}
inSidebar
/> />
<div className="flex items-center justify-center gap-6"> <div className="flex items-center justify-center gap-6">
@ -125,9 +125,9 @@ export function MobileNav({
</SheetContent> </SheetContent>
</Sheet> </Sheet>
<Link <Link
href="/"
className="ml-3 flex shrink-0 items-center md:hidden"
aria-label="mainNavLink" aria-label="mainNavLink"
className="ml-3 flex shrink-0 items-center md:hidden"
href="/"
> >
{verySmallScreen ? ( {verySmallScreen ? (
<ASvg className="h-8 w-auto" /> <ASvg className="h-8 w-auto" />
@ -158,6 +158,7 @@ function MobileLink({
return ( return (
<Link <Link
aria-label="mobileLink" aria-label="mobileLink"
className={cn(className)}
href={href} href={href}
onClick={(e) => { onClick={(e) => {
if (disabled) { if (disabled) {
@ -165,13 +166,10 @@ function MobileLink({
e.stopPropagation(); e.stopPropagation();
onOpenChange?.(false); onOpenChange?.(false);
} else { } else {
router.push( router.push(href.toString());
href.toString(),
);
onOpenChange?.(false); onOpenChange?.(false);
} }
}} }}
className={cn(className)}
{...props} {...props}
> >
{children} {children}
@ -203,7 +201,7 @@ const AreaRiservataLinks = ({
item.href === pathname && "bg-muted", item.href === pathname && "bg-muted",
)} )}
> >
<IconMatrix type={item.icon} className="size-5" /> <IconMatrix className="size-5" type={item.icon} />
{item.title} {item.title}
</div> </div>
@ -212,14 +210,14 @@ const AreaRiservataLinks = ({
{item.items.map((item) => { {item.items.map((item) => {
return ( return (
<MobileLink <MobileLink
key={item.title}
href={item.href}
onOpenChange={setOpen}
className={cn( className={cn(
`hover:text-accent-foreground mx-4 block w-full border-l px-6 py-2 duration-150 hover:border-neutral-800`, `hover:text-accent-foreground mx-4 block w-full border-l px-6 py-2 duration-150 hover:border-neutral-800`,
item.href === pathname && item.href === pathname &&
"border-neutral-900 text-neutral-900", "border-neutral-900 text-neutral-900",
)} )}
href={item.href}
key={item.title}
onOpenChange={setOpen}
> >
{item.title} {item.title}
</MobileLink> </MobileLink>
@ -232,30 +230,30 @@ const AreaRiservataLinks = ({
} }
return ( return (
<MobileLink <MobileLink
key={item.title}
href={item.href}
onOpenChange={setOpen}
className={cn( className={cn(
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium", "flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted", item.href === pathname && "bg-muted",
)} )}
href={item.href}
key={item.title}
onOpenChange={setOpen}
> >
<IconMatrix type={item.icon} className="size-5" /> <IconMatrix className="size-5" type={item.icon} />
{item.title} {item.title}
</MobileLink> </MobileLink>
); );
}) })
: t.nav.areaRiservataUserNav.map((item) => ( : t.nav.areaRiservataUserNav.map((item) => (
<MobileLink <MobileLink
key={item.title}
href={item.href}
onOpenChange={setOpen}
className={cn( className={cn(
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium", "flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted", item.href === pathname && "bg-muted",
)} )}
href={item.href}
key={item.title}
onOpenChange={setOpen}
> >
<IconMatrix type={item.icon} className="size-5" /> <IconMatrix className="size-5" type={item.icon} />
{item.title} {item.title}
</MobileLink> </MobileLink>
))} ))}
@ -294,7 +292,7 @@ export const ARMinimizableLinks = ({
minimized && "justify-center", minimized && "justify-center",
)} )}
> >
<IconMatrix type={item.icon} className="size-5" /> <IconMatrix className="size-5" type={item.icon} />
{!minimized && item.title} {!minimized && item.title}
</div> </div>
@ -303,9 +301,6 @@ export const ARMinimizableLinks = ({
{item.items.map((item) => { {item.items.map((item) => {
return ( return (
<MobileLink <MobileLink
key={item.title}
href={item.href}
onOpenChange={setOpen}
className={cn( className={cn(
`hover:text-accent-foreground mx-4 block w-full border-l px-6 py-2 duration-150 hover:border-neutral-800`, `hover:text-accent-foreground mx-4 block w-full border-l px-6 py-2 duration-150 hover:border-neutral-800`,
item.href === pathname && item.href === pathname &&
@ -313,6 +308,9 @@ export const ARMinimizableLinks = ({
minimized && "hidden", minimized && "hidden",
)} )}
disabled={minimized} disabled={minimized}
href={item.href}
key={item.title}
onOpenChange={setOpen}
> >
{item.title} {item.title}
</MobileLink> </MobileLink>
@ -325,34 +323,34 @@ export const ARMinimizableLinks = ({
} }
return ( return (
<MobileLink <MobileLink
key={item.title}
href={item.href}
onOpenChange={setOpen}
className={cn( className={cn(
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium", "flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted", item.href === pathname && "bg-muted",
minimized && "justify-center", minimized && "justify-center",
)} )}
disabled={minimized} disabled={minimized}
href={item.href}
key={item.title}
onOpenChange={setOpen}
> >
<IconMatrix type={item.icon} className="size-5" /> <IconMatrix className="size-5" type={item.icon} />
{!minimized && item.title} {!minimized && item.title}
</MobileLink> </MobileLink>
); );
}) })
: t.nav.areaRiservataUserNav.map((item) => ( : t.nav.areaRiservataUserNav.map((item) => (
<MobileLink <MobileLink
key={item.title}
href={item.href}
onOpenChange={setOpen}
className={cn( className={cn(
"flex items-center gap-3 rounded-lg px-2 py-1 font-medium", "flex items-center gap-3 rounded-lg px-2 py-1 font-medium",
item.href === pathname && "bg-muted", item.href === pathname && "bg-muted",
minimized && "justify-center", minimized && "justify-center",
)} )}
disabled={minimized} disabled={minimized}
href={item.href}
key={item.title}
onOpenChange={setOpen}
> >
<IconMatrix type={item.icon} className="size-5" /> <IconMatrix className="size-5" type={item.icon} />
{!minimized && item.title} {!minimized && item.title}
</MobileLink> </MobileLink>
))} ))}

View file

@ -24,9 +24,9 @@ export function RicercaCommand() {
}, []); }, []);
return ( return (
<> <>
<Dialog open={open} onOpenChange={setOpen}> <Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" className="p-2" aria-label="ricercaBtn"> <Button aria-label="ricercaBtn" className="p-2" variant="ghost">
<Search className="size-5" /> <Search className="size-5" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
@ -37,8 +37,8 @@ export function RicercaCommand() {
</DialogHeader> </DialogHeader>
<div className="relative mx-auto flex w-full gap-2"> <div className="relative mx-auto flex w-full gap-2">
<CodiceBox <CodiceBox
key={"codiceBox"}
className="outline-foreground focus:ring-foreground rounded-none rounded-t-md outline placeholder:text-center focus:border-none focus:outline-hidden" className="outline-foreground focus:ring-foreground rounded-none rounded-t-md outline placeholder:text-center focus:border-none focus:outline-hidden"
key={"codiceBox"}
optionBoxClassName="rounded-none rounded-b-md mt-0 border-b-0 border-x-0" optionBoxClassName="rounded-none rounded-b-md mt-0 border-b-0 border-x-0"
/> />
</div> </div>

View file

@ -12,7 +12,7 @@ export const SettingsPopover = () => {
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="ghost" className="p-2" aria-label="Settings"> <Button aria-label="Settings" className="p-2" variant="ghost">
<Settings className="size-5" /> <Settings className="size-5" />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>

View file

@ -25,11 +25,11 @@ export function SiteHeader() {
<div className="mx-auto flex h-14 w-full items-center pr-3 pl-4 md:pr-4 md:pl-8"> <div className="mx-auto flex h-14 w-full items-center pr-3 pl-4 md:pr-4 md:pl-8">
<MainNav pathname={pathname} /> <MainNav pathname={pathname} />
<MobileNav <MobileNav
pathname={pathname}
open={openSidebar} open={openSidebar}
pathname={pathname}
setOpen={setOpenSidebar} setOpen={setOpenSidebar}
userOpen={openUserMenuSidebar}
setUserOpen={setOpenUserMenuSidebar} setUserOpen={setOpenUserMenuSidebar}
userOpen={openUserMenuSidebar}
/> />
<div className="float-end flex flex-1 items-center justify-end gap-0.5"> <div className="float-end flex flex-1 items-center justify-end gap-0.5">

View file

@ -7,20 +7,20 @@ export const ThemeSwitch = () => {
const { theme, setTheme } = useTheme(); const { theme, setTheme } = useTheme();
const spring: Transition = { const spring: Transition = {
type: "spring",
stiffness: 700,
damping: 30, damping: 30,
stiffness: 700,
type: "spring",
}; };
return ( return (
<button <button
aria-label="Toggle Theme" aria-label="Toggle Theme"
type="button"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
className={cn( className={cn(
`flex h-10 w-20 rounded-full bg-neutral-200 p-[5px] shadow-inner hover:cursor-pointer dark:bg-neutral-700`, `flex h-10 w-20 rounded-full bg-neutral-200 p-[5px] shadow-inner hover:cursor-pointer dark:bg-neutral-700`,
theme === "light" && "place-content-end", theme === "light" && "place-content-end",
)} )}
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
type="button"
> >
<motion.div <motion.div
className={cn( className={cn(

View file

@ -14,23 +14,23 @@ export default function PaymentStatus({
title, title,
}: PaymentStatusProps) { }: PaymentStatusProps) {
const statusConfig = { const statusConfig = {
success: {
icon: CheckCircle,
color: "text-green-500",
bgColor: "bg-green-50",
borderColor: "border-green-200",
},
processing: {
icon: Loader2,
color: "text-blue-500",
bgColor: "bg-blue-50",
borderColor: "border-blue-200",
},
error: { error: {
icon: AlertCircle,
color: "text-red-500",
bgColor: "bg-red-50", bgColor: "bg-red-50",
borderColor: "border-red-200", borderColor: "border-red-200",
color: "text-red-500",
icon: AlertCircle,
},
processing: {
bgColor: "bg-blue-50",
borderColor: "border-blue-200",
color: "text-blue-500",
icon: Loader2,
},
success: {
bgColor: "bg-green-50",
borderColor: "border-green-200",
color: "text-green-500",
icon: CheckCircle,
}, },
}; };

View file

@ -37,19 +37,19 @@ const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps> = React.memo(
>(({ className, onChange, ...props }, ref) => { >(({ className, onChange, ...props }, ref) => {
return ( return (
<RPNInput.default <RPNInput.default
ref={ref}
className={cn( className={cn(
"[&_input]:ph-input-fix flex h-auto w-full flex-1 [&_*]:!h-auto", "[&_input]:ph-input-fix flex h-auto w-full flex-1 [&_*]:!h-auto",
className, className,
)} )}
flagComponent={FlagComponent}
countrySelectComponent={CountrySelect} countrySelectComponent={CountrySelect}
inputComponent={InputComponent}
smartCaret={false}
defaultCountry="IT" defaultCountry="IT"
flagComponent={FlagComponent}
focusInputOnCountrySelection
inputComponent={InputComponent}
international international
labels={EN} labels={EN}
focusInputOnCountrySelection onChange={(value) => onChange?.(value || ("" as RPNInput.Value))}
ref={ref}
/** /**
* Handles the onChange event. * Handles the onChange event.
* *
@ -59,7 +59,7 @@ const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps> = React.memo(
* *
* @param {E164Number | undefined} value - The entered value * @param {E164Number | undefined} value - The entered value
*/ */
onChange={(value) => onChange?.(value || ("" as RPNInput.Value))} smartCaret={false}
{...props} {...props}
/> />
); );
@ -111,8 +111,8 @@ const CountrySelect = React.memo(
const virtualizer = useVirtualizer({ const virtualizer = useVirtualizer({
count: filteredCountries.length, count: filteredCountries.length,
getScrollElement: () => parentNode,
estimateSize: () => 35, estimateSize: () => 35,
getScrollElement: () => parentNode,
overscan: 5, overscan: 5,
}); });
@ -129,13 +129,13 @@ const CountrySelect = React.memo(
}; };
return ( return (
<Popover open={open} onOpenChange={setOpen}> <Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
role="combobox"
variant="outline"
aria-expanded={open} aria-expanded={open}
className="flex gap-1 rounded-md rounded-e-none border-r-0 px-3 py-2 focus:z-10" className="flex gap-1 rounded-md rounded-e-none border-r-0 px-3 py-2 focus:z-10"
role="combobox"
variant="outline"
> >
<FlagComponent <FlagComponent
country={selectedCountry} country={selectedCountry}
@ -155,9 +155,9 @@ const CountrySelect = React.memo(
<PopoverContent className="w-[300px] p-0"> <PopoverContent className="w-[300px] p-0">
<Command shouldFilter={false}> <Command shouldFilter={false}>
<CommandInput <CommandInput
placeholder="Search country..."
onValueChange={handleSearch}
className="border-none ring-0 focus:ring-0 focus-visible:ring-0" className="border-none ring-0 focus:ring-0 focus-visible:ring-0"
onValueChange={handleSearch}
placeholder="Search country..."
/> />
<ScrollArea className="h-[400px] overflow-auto"> <ScrollArea className="h-[400px] overflow-auto">
@ -166,8 +166,8 @@ const CountrySelect = React.memo(
<div <div
style={{ style={{
height: `${virtualizer.getTotalSize()}px`, height: `${virtualizer.getTotalSize()}px`,
width: "100%",
position: "relative", position: "relative",
width: "100%",
}} }}
> >
{virtualizer {virtualizer
@ -178,16 +178,16 @@ const CountrySelect = React.memo(
return ( return (
<CommandItem <CommandItem
key={country.value} key={country.value}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${size}px`,
transform: `translateY(${start}px)`,
}}
// biome-ignore lint/style/noNonNullAssertion: <exists> // biome-ignore lint/style/noNonNullAssertion: <exists>
onSelect={() => onChange(country.value!)} onSelect={() => onChange(country.value!)}
style={{
height: `${size}px`,
left: 0,
position: "absolute",
top: 0,
transform: `translateY(${start}px)`,
width: "100%",
}}
value={country.value} value={country.value}
> >
<FlagComponent <FlagComponent

View file

@ -37,26 +37,25 @@ export const PricingChoice = () => {
return ( return (
<div className="text-primary flex flex-col items-center justify-center gap-4"> <div className="text-primary flex flex-col items-center justify-center gap-4">
<h2 className="text-3xl font-semibold">Scegli il tuo servizio</h2> <h2 className="text-3xl font-semibold">Scegli il tuo servizio</h2>
<Tabs defaultValue="transitorio" className="mb-2 w-full max-w-xl"> <Tabs className="mb-2 w-full max-w-xl" defaultValue="transitorio">
<TabsList className="flex h-auto w-full items-center justify-between gap-1 p-1"> <TabsList className="flex h-auto w-full items-center justify-between gap-1 p-1">
<TabsTrigger <TabsTrigger
value="transitorio"
className="bg-transitorio [&[data-state=active]]:bg-transitorio w-full cursor-pointer px-2 text-xl sm:px-3" className="bg-transitorio [&[data-state=active]]:bg-transitorio w-full cursor-pointer px-2 text-xl sm:px-3"
value="transitorio"
> >
Transitorio Transitorio
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="stabile"
className="bg-stabile [&[data-state=active]]:bg-stabile w-full cursor-pointer px-2 text-xl sm:px-3" className="bg-stabile [&[data-state=active]]:bg-stabile w-full cursor-pointer px-2 text-xl sm:px-3"
value="stabile"
> >
Stabile Stabile
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="transitorio" className="w-full"> <TabsContent className="w-full" value="transitorio">
<PricingComponent <PricingComponent
title={t.prezzi.titolo_transitori} className="border-transitorio bg-transitorio"
cta={t.pricing_cmp.cta_breve} cta={t.pricing_cmp.cta_breve}
pricingData={pricingBreve}
opzioni={[ opzioni={[
t.pricing_cmp.breve_option1, t.pricing_cmp.breve_option1,
t.pricing_cmp.breve_option2, t.pricing_cmp.breve_option2,
@ -64,21 +63,22 @@ export const PricingChoice = () => {
t.pricing_cmp.breve_option4, t.pricing_cmp.breve_option4,
t.pricing_cmp.breve_option5, t.pricing_cmp.breve_option5,
]} ]}
className="border-transitorio bg-transitorio" pricingData={pricingBreve}
tipo={TipologiaPosizioneEnum.Transitorio} tipo={TipologiaPosizioneEnum.Transitorio}
title={t.prezzi.titolo_transitori}
/> />
</TabsContent> </TabsContent>
<TabsContent value="stabile"> <TabsContent value="stabile">
<PricingComponent <PricingComponent
title={t.prezzi.titolo_stabile} className="border-stabile bg-stabile"
cta={t.pricing_cmp.cta_stabile} cta={t.pricing_cmp.cta_stabile}
pricingData={pricingStabile}
opzioni={[ opzioni={[
t.pricing_cmp.stabile_option1, t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2, t.pricing_cmp.stabile_option2,
]} ]}
className="border-stabile bg-stabile" pricingData={pricingStabile}
tipo={TipologiaPosizioneEnum.Stabile} tipo={TipologiaPosizioneEnum.Stabile}
title={t.prezzi.titolo_stabile}
/> />
</TabsContent> </TabsContent>
</Tabs> </Tabs>
@ -173,9 +173,9 @@ export const PricingComponent = ({
data-role="amount" data-role="amount"
> >
<AnimatedNumber <AnimatedNumber
value={secondPayment}
format={customFormat} format={customFormat}
precision={secondPayment % 1 === 0 ? 0 : 2} precision={secondPayment % 1 === 0 ? 0 : 2}
value={secondPayment}
/> />
</p> </p>
</div> </div>
@ -196,8 +196,8 @@ export const PricingComponent = ({
</div> </div>
) : ( ) : (
<Link <Link
href={"/prezzi#contratti"}
className="flex flex-col justify-center leading-none sm:h-16" className="flex flex-col justify-center leading-none sm:h-16"
href={"/prezzi#contratti"}
> >
<Button className="flex items-center gap-2"> <Button className="flex items-center gap-2">
Più info <ExternalLink className="size-4" /> Più info <ExternalLink className="size-4" />
@ -209,17 +209,17 @@ export const PricingComponent = ({
<div className="flex w-full flex-wrap items-center justify-center gap-2"> <div className="flex w-full flex-wrap items-center justify-center gap-2">
{opzioni.map((opzione, idx) => ( {opzioni.map((opzione, idx) => (
<Button <Button
key={idx}
size="default"
className={cn( className={cn(
"xs:h-10 xs:px-4 xs:py-2 h-9 rounded-md border-none px-3", "xs:h-10 xs:px-4 xs:py-2 h-9 rounded-md border-none px-3",
idx === Value ? "animate-in fade-in-0" : "", idx === Value ? "animate-in fade-in-0" : "",
)} )}
variant={idx === Value ? "default" : "outline"} key={idx}
onClick={() => { onClick={() => {
ClearCarousel(); ClearCarousel();
setValue(idx); setValue(idx);
}} }}
size="default"
variant={idx === Value ? "default" : "outline"}
> >
{opzione} {opzione}
</Button> </Button>
@ -282,24 +282,24 @@ export const PricingComponentConsulenza = ({
<div className="xs:gap-4 flex w-full items-center justify-center gap-0"> <div className="xs:gap-4 flex w-full items-center justify-center gap-0">
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<p className="xs:text-6xl text-3xl font-semibold"> <p className="xs:text-6xl text-3xl font-semibold">
<AnimatedNumber value={p || 0} format={customFormat} /> <AnimatedNumber format={customFormat} value={p || 0} />
</p> </p>
</div> </div>
</div> </div>
<div className="flex w-full flex-wrap items-center justify-center gap-2"> <div className="flex w-full flex-wrap items-center justify-center gap-2">
{opzioni.map((opzione, idx) => ( {opzioni.map((opzione, idx) => (
<Button <Button
key={idx}
size="default"
className={cn( className={cn(
"xs:h-10 xs:px-4 xs:py-2 h-9 rounded-md border-none px-3", "xs:h-10 xs:px-4 xs:py-2 h-9 rounded-md border-none px-3",
idx === Value ? "animate-in fade-in-0" : "", idx === Value ? "animate-in fade-in-0" : "",
)} )}
variant={idx === Value ? "default" : "outline"} key={idx}
onClick={() => { onClick={() => {
ClearCarousel(); ClearCarousel();
setValue(idx); setValue(idx);
}} }}
size="default"
variant={idx === Value ? "default" : "outline"}
> >
{opzione} {opzione}
</Button> </Button>
@ -340,20 +340,19 @@ export const PrezziShow = () => {
principali: Affitto Transitorio e Affitto Stabile. principali: Affitto Transitorio e Affitto Stabile.
</p> </p>
</div> </div>
<Tabs defaultValue="transitorio" className="w-full sm:w-3/5"> <Tabs className="w-full sm:w-3/5" defaultValue="transitorio">
<TabsList className="flex h-auto w-full items-center justify-between"> <TabsList className="flex h-auto w-full items-center justify-between">
<TabsTrigger value="transitorio" className="w-full text-xl"> <TabsTrigger className="w-full text-xl" value="transitorio">
Affitto Transitorio Affitto Transitorio
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="stabile" className="w-full text-xl"> <TabsTrigger className="w-full text-xl" value="stabile">
Affitto Stabile Affitto Stabile
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="transitorio"> <TabsContent value="transitorio">
<PricingComponent <PricingComponent
title={t.prezzi.titolo_transitori} className="border-transitorio bg-transitorio"
cta={t.pricing_cmp.cta_breve} cta={t.pricing_cmp.cta_breve}
pricingData={pricingBreve}
opzioni={[ opzioni={[
t.pricing_cmp.breve_option1, t.pricing_cmp.breve_option1,
t.pricing_cmp.breve_option2, t.pricing_cmp.breve_option2,
@ -361,21 +360,22 @@ export const PrezziShow = () => {
t.pricing_cmp.breve_option4, t.pricing_cmp.breve_option4,
t.pricing_cmp.breve_option5, t.pricing_cmp.breve_option5,
]} ]}
pricingData={pricingBreve}
tipo={TipologiaPosizioneEnum.Transitorio} tipo={TipologiaPosizioneEnum.Transitorio}
className="border-transitorio bg-transitorio" title={t.prezzi.titolo_transitori}
/> />
</TabsContent> </TabsContent>
<TabsContent value="stabile"> <TabsContent value="stabile">
<PricingComponent <PricingComponent
title={t.prezzi.titolo_stabile} className="border-stabile bg-stabile"
cta={t.pricing_cmp.cta_stabile} cta={t.pricing_cmp.cta_stabile}
pricingData={pricingStabile}
opzioni={[ opzioni={[
t.pricing_cmp.stabile_option1, t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2, t.pricing_cmp.stabile_option2,
]} ]}
className="border-stabile bg-stabile" pricingData={pricingStabile}
tipo={TipologiaPosizioneEnum.Stabile} tipo={TipologiaPosizioneEnum.Stabile}
title={t.prezzi.titolo_stabile}
/> />
</TabsContent> </TabsContent>
</Tabs> </Tabs>

View file

@ -44,15 +44,15 @@ export const ProgressRedirect = ({ href }: { href: string }) => {
}, [progress]); }, [progress]);
return ( return (
<div className="flex flex-col items-center justify-center gap-4"> <div className="flex flex-col items-center justify-center gap-4">
<Progress value={progress} className="h-3" /> <Progress className="h-3" value={progress} />
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Stai venendo reindirizzato... oppure Stai venendo reindirizzato... oppure
</p> </p>
<Link <Link
href={href}
className="font-semibold underline underline-offset-1" className="font-semibold underline underline-offset-1"
href={href}
> >
Clicca qui Clicca qui
</Link> </Link>

View file

@ -76,6 +76,10 @@ export const AnnuncioActions = () => {
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate } = api.servizio.removeAnnuncioServizio.useMutation({ const { mutate } = api.servizio.removeAnnuncioServizio.useMutation({
onError: (error) => {
console.error(error);
toast.error("Errore durante la rimozione dell'annuncio");
},
onSuccess: async () => { onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ userId }); await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getCompatibileAnnunci.invalidate({ await utils.servizio.getCompatibileAnnunci.invalidate({
@ -83,10 +87,6 @@ export const AnnuncioActions = () => {
}); });
toast.success("Annuncio rimosso"); toast.success("Annuncio rimosso");
}, },
onError: (error) => {
console.error(error);
toast.error("Errore durante la rimozione dell'annuncio");
},
}); });
if ((!isAdmin && data.open_contatti_at == null) || isAdmin) { if ((!isAdmin && data.open_contatti_at == null) || isAdmin) {
@ -117,29 +117,29 @@ export const AnnuncioActions = () => {
> >
<Button <Button
aria-label="Edit Annuncio" aria-label="Edit Annuncio"
variant="secondary"
className="flex w-full items-center gap-2" className="flex w-full items-center gap-2"
size="sm" size="sm"
variant="secondary"
> >
<Wrench className="size-6" /> <Wrench className="size-6" />
</Button> </Button>
</Link> </Link>
<Confirm <Confirm
title="Elimina annuncio"
description="Sei sicuro di voler eliminare l'annuncio?" description="Sei sicuro di voler eliminare l'annuncio?"
onConfirm={() => { onConfirm={() => {
mutate({ mutate({
servizioId,
annuncioId, annuncioId,
servizioId,
}); });
}} }}
title="Elimina annuncio"
> >
<Button <Button
aria-label="Delete Annuncio" aria-label="Delete Annuncio"
variant="destructive"
className="flex items-center gap-2" className="flex items-center gap-2"
size="sm" size="sm"
variant="destructive"
> >
<Trash2 className="size-6" /> <Trash2 className="size-6" />
</Button> </Button>
@ -153,7 +153,6 @@ export const AnnuncioActions = () => {
}; };
const ContrattoImportSchema = z.object({ const ContrattoImportSchema = z.object({
gestionale_id: z.number().nullable(),
contratto_decorrenza: z.string().nullable(), contratto_decorrenza: z.string().nullable(),
contratto_scadenza: z.string().nullable(), contratto_scadenza: z.string().nullable(),
contratto_tipo: z contratto_tipo: z
@ -167,6 +166,7 @@ const ContrattoImportSchema = z.object({
"Commerciale", "Commerciale",
]) ])
.nullable(), .nullable(),
gestionale_id: z.number().nullable(),
}); });
interface ContrattoImportObj interface ContrattoImportObj
@ -212,7 +212,6 @@ const ContrattoImport = ({
}) => { }) => {
const { data } = useServizioAnnuncio(); const { data } = useServizioAnnuncio();
const [contratto, setContratto] = useState<ContrattoImportObj | undefined>({ const [contratto, setContratto] = useState<ContrattoImportObj | undefined>({
gestionale_id: data.gestionale_id,
contratto_decorrenza: data.contratto_decorrenza contratto_decorrenza: data.contratto_decorrenza
? new Date(data.contratto_decorrenza) ? new Date(data.contratto_decorrenza)
: null, : null,
@ -220,6 +219,7 @@ const ContrattoImport = ({
? new Date(data.contratto_scadenza) ? new Date(data.contratto_scadenza)
: null, : null,
contratto_tipo: data.contratto_tipo as ContrattoImportObj["contratto_tipo"], contratto_tipo: data.contratto_tipo as ContrattoImportObj["contratto_tipo"],
gestionale_id: data.gestionale_id,
}); });
const handleSave = () => { const handleSave = () => {
handleUpdate({ handleUpdate({
@ -236,12 +236,12 @@ const ContrattoImport = ({
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Button <Button
aria-label="Import Contratto" aria-label="Import Contratto"
className="flex items-center gap-2"
onClick={async () => { onClick={async () => {
const clipboardText = await navigator.clipboard.readText(); const clipboardText = await navigator.clipboard.readText();
setContratto(parseContrattoImport(clipboardText)); setContratto(parseContrattoImport(clipboardText));
}} }}
className="flex items-center gap-2"
variant="info" variant="info"
> >
<ClipboardPaste className="size-4" /> <ClipboardPaste className="size-4" />
@ -249,15 +249,15 @@ const ContrattoImport = ({
</Button> </Button>
<Button <Button
aria-label="Reset Contratto" aria-label="Reset Contratto"
className="flex items-center gap-2"
onClick={async () => { onClick={async () => {
setContratto({ setContratto({
gestionale_id: null,
contratto_decorrenza: null, contratto_decorrenza: null,
contratto_scadenza: null, contratto_scadenza: null,
contratto_tipo: null, contratto_tipo: null,
gestionale_id: null,
}); });
}} }}
className="flex items-center gap-2"
variant="destructive" variant="destructive"
> >
<Trash2 className="size-4" /> <Trash2 className="size-4" />
@ -271,6 +271,7 @@ const ContrattoImport = ({
{contratto.gestionale_id}{" "} {contratto.gestionale_id}{" "}
<Button <Button
aria-label="Edit Gestionale ID" aria-label="Edit Gestionale ID"
className="h-6 px-1.5 [&_svg]:size-4"
onClick={() => { onClick={() => {
const newValue = prompt( const newValue = prompt(
"Modifica l'ID gestionale del contratto:", "Modifica l'ID gestionale del contratto:",
@ -284,7 +285,6 @@ const ContrattoImport = ({
); );
} }
}} }}
className="h-6 px-1.5 [&_svg]:size-4"
size="sm" size="sm"
> >
<Pen /> <Pen />
@ -298,6 +298,7 @@ const ContrattoImport = ({
)}{" "} )}{" "}
<Button <Button
aria-label="Edit Decorrenza" aria-label="Edit Decorrenza"
className="h-6 px-1.5 [&_svg]:size-4"
onClick={() => { onClick={() => {
const newValue = prompt( const newValue = prompt(
"Modifica la decorrenza del contratto (DD/MM/YYYY):", "Modifica la decorrenza del contratto (DD/MM/YYYY):",
@ -318,7 +319,6 @@ const ContrattoImport = ({
); );
} }
}} }}
className="h-6 px-1.5 [&_svg]:size-4"
size="sm" size="sm"
> >
<Pen /> <Pen />
@ -332,6 +332,7 @@ const ContrattoImport = ({
)}{" "} )}{" "}
<Button <Button
aria-label="Edit Scadenza" aria-label="Edit Scadenza"
className="h-6 px-1.5 [&_svg]:size-4"
onClick={() => { onClick={() => {
const newValue = prompt( const newValue = prompt(
"Modifica la scadenza del contratto (DD/MM/YYYY):", "Modifica la scadenza del contratto (DD/MM/YYYY):",
@ -352,7 +353,6 @@ const ContrattoImport = ({
); );
} }
}} }}
className="h-6 px-1.5 [&_svg]:size-4"
size="sm" size="sm"
> >
<Pen /> <Pen />
@ -363,6 +363,7 @@ const ContrattoImport = ({
{contratto.contratto_tipo}{" "} {contratto.contratto_tipo}{" "}
<Button <Button
aria-label="Edit Tipo di contratto" aria-label="Edit Tipo di contratto"
className="h-6 px-1.5 [&_svg]:size-4"
onClick={() => { onClick={() => {
const newValue = prompt( const newValue = prompt(
"Modifica il tipo di contratto:", "Modifica il tipo di contratto:",
@ -385,7 +386,6 @@ const ContrattoImport = ({
); );
} }
}} }}
className="h-6 px-1.5 [&_svg]:size-4"
size="sm" size="sm"
> >
<Pen /> <Pen />
@ -421,6 +421,9 @@ const AdminContratto = () => {
const { mutate: updateServizio } = const { mutate: updateServizio } =
api.servizio.updateServizioAnnunci.useMutation({ api.servizio.updateServizioAnnunci.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ userId }); await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getServizio.invalidate({ await utils.servizio.getServizio.invalidate({
@ -429,28 +432,25 @@ const AdminContratto = () => {
toast.success("Dati salvati con successo"); toast.success("Dati salvati con successo");
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
const handleUpdate = (upd: ServizioAnnunciUpdate) => { const handleUpdate = (upd: ServizioAnnunciUpdate) => {
updateServizio({ updateServizio({
servizioId,
annuncioId, annuncioId,
data: { data: {
...upd, ...upd,
}, },
servizioId,
}); });
}; };
return ( return (
<Credenza open={open} onOpenChange={setOpen}> <Credenza onOpenChange={setOpen} open={open}>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
variant={data.doc_contratto_added ? "success" : "secondary"}
className="flex w-fit items-center gap-2"
aria-label="Open Contratto Section" aria-label="Open Contratto Section"
className="flex w-fit items-center gap-2"
variant={data.doc_contratto_added ? "success" : "secondary"}
> >
{data.doc_contratto_added ? ( {data.doc_contratto_added ? (
<CircleCheck className="size-6" /> <CircleCheck className="size-6" />
@ -479,7 +479,7 @@ const AdminContratto = () => {
<CredenzaFooter> <CredenzaFooter>
<CredenzaClose asChild> <CredenzaClose asChild>
<Button variant="outline" aria-label="Close Credenza"> <Button aria-label="Close Credenza" variant="outline">
Chiudi Chiudi
</Button> </Button>
</CredenzaClose> </CredenzaClose>
@ -515,13 +515,13 @@ const ContrattoSection = ({
const handleSave = () => { const handleSave = () => {
handleUpdate({ handleUpdate({
doc_contratto_ref: selectedDoc,
doc_contratto_added: selectedDoc != null, doc_contratto_added: selectedDoc != null,
doc_contratto_ref: selectedDoc,
}); });
if (selectedDoc) { if (selectedDoc) {
setUserStorage({ setUserStorage({
userId,
fileId: selectedDoc, fileId: selectedDoc,
userId,
}); });
} }
}; };
@ -542,15 +542,15 @@ const ContrattoSection = ({
<DocInfo storageId={selectedDoc}> <DocInfo storageId={selectedDoc}>
<Button <Button
aria-label="Reset Contratto" aria-label="Reset Contratto"
variant="destructive"
className="flex items-center gap-3" className="flex items-center gap-3"
onClick={() => { onClick={() => {
handleUpdate({ handleUpdate({
doc_contratto_ref: null,
doc_contratto_added: false, doc_contratto_added: false,
doc_contratto_ref: null,
}); });
setSelectedDoc(null); setSelectedDoc(null);
}} }}
variant="destructive"
> >
<Trash2 className="size-6" /> <Trash2 className="size-6" />
</Button> </Button>
@ -558,11 +558,11 @@ const ContrattoSection = ({
</div> </div>
)} )}
<DocCombo <DocCombo
current={selectedDoc}
files={files} files={files}
onSelect={(f) => { onSelect={(f) => {
setSelectedDoc(f.id); setSelectedDoc(f.id);
}} }}
current={selectedDoc}
/> />
<UploadModal isAdmin userId={userId} /> <UploadModal isAdmin userId={userId} />
</CardContent> </CardContent>
@ -607,13 +607,13 @@ const RicevutaSection = ({
const handleSave = () => { const handleSave = () => {
handleUpdate({ handleUpdate({
doc_registrazione_ref: selectedDoc,
doc_registrazione_added: selectedDoc != null, doc_registrazione_added: selectedDoc != null,
doc_registrazione_ref: selectedDoc,
}); });
if (selectedDoc) { if (selectedDoc) {
setUserStorage({ setUserStorage({
userId,
fileId: selectedDoc, fileId: selectedDoc,
userId,
}); });
} }
}; };
@ -634,15 +634,15 @@ const RicevutaSection = ({
<DocInfo storageId={selectedDoc}> <DocInfo storageId={selectedDoc}>
<Button <Button
aria-label="Reset Ricevuta" aria-label="Reset Ricevuta"
variant="destructive"
className="flex items-center gap-3" className="flex items-center gap-3"
onClick={() => { onClick={() => {
handleUpdate({ handleUpdate({
doc_registrazione_ref: null,
doc_registrazione_added: false, doc_registrazione_added: false,
doc_registrazione_ref: null,
}); });
setSelectedDoc(null); setSelectedDoc(null);
}} }}
variant="destructive"
> >
<Trash2 className="size-6" /> <Trash2 className="size-6" />
</Button> </Button>
@ -650,11 +650,11 @@ const RicevutaSection = ({
</div> </div>
)} )}
<DocCombo <DocCombo
current={selectedDoc}
files={files} files={files}
onSelect={(f) => { onSelect={(f) => {
setSelectedDoc(f.id); setSelectedDoc(f.id);
}} }}
current={selectedDoc}
/> />
<UploadModal isAdmin userId={userId} /> <UploadModal isAdmin userId={userId} />
</CardContent> </CardContent>
@ -683,6 +683,9 @@ const AdminLavoraConferma = () => {
const { mutate: updateServizio } = const { mutate: updateServizio } =
api.servizio.updateServizioAnnunci.useMutation({ api.servizio.updateServizioAnnunci.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ userId }); await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getServizio.invalidate({ await utils.servizio.getServizio.invalidate({
@ -691,28 +694,25 @@ const AdminLavoraConferma = () => {
toast.success("Dati salvati con successo"); toast.success("Dati salvati con successo");
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
const handleUpdate = (upd: ServizioAnnunciUpdate) => { const handleUpdate = (upd: ServizioAnnunciUpdate) => {
updateServizio({ updateServizio({
servizioId,
annuncioId, annuncioId,
data: { data: {
...upd, ...upd,
}, },
servizioId,
}); });
}; };
return ( return (
<Credenza open={open} onOpenChange={setOpen}> <Credenza onOpenChange={setOpen} open={open}>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
variant={data.hasConfermaAdmin ? "success" : "secondary"}
className="flex w-fit items-center gap-2"
aria-label="Open Lavora Conferma Section" aria-label="Open Lavora Conferma Section"
className="flex w-fit items-center gap-2"
variant={data.hasConfermaAdmin ? "success" : "secondary"}
> >
{data.hasConfermaAdmin ? ( {data.hasConfermaAdmin ? (
<CircleCheck className="size-6" /> <CircleCheck className="size-6" />
@ -741,7 +741,7 @@ const AdminLavoraConferma = () => {
<CredenzaFooter> <CredenzaFooter>
<CredenzaClose asChild> <CredenzaClose asChild>
<Button variant="outline" aria-label="Close Credenza"> <Button aria-label="Close Credenza" variant="outline">
Chiudi Chiudi
</Button> </Button>
</CredenzaClose> </CredenzaClose>
@ -790,14 +790,14 @@ const DocSection = ({
<DocInfo storageId={data.doc_conferma_ref}> <DocInfo storageId={data.doc_conferma_ref}>
<Button <Button
aria-label="Reset Conferma" aria-label="Reset Conferma"
variant="destructive"
className="flex items-center gap-3" className="flex items-center gap-3"
onClick={() => { onClick={() => {
handleUpdate({ handleUpdate({
doc_conferma_ref: null,
doc_conferma_added: false, doc_conferma_added: false,
doc_conferma_ref: null,
}); });
}} }}
variant="destructive"
> >
<Trash2 /> <Trash2 />
</Button> </Button>
@ -806,19 +806,19 @@ const DocSection = ({
)} )}
<div className="flex flex-wrap items-center gap-3"> <div className="flex flex-wrap items-center gap-3">
<DocCombo <DocCombo
current={data.doc_conferma_ref}
files={files} files={files}
onSelect={(f) => { onSelect={(f) => {
handleUpdate({ handleUpdate({
doc_conferma_ref: f.id,
doc_conferma_added: true, doc_conferma_added: true,
doc_conferma_ref: f.id,
}); });
setUserStorage({ setUserStorage({
userId,
fileId: f.id, fileId: f.id,
userId,
}); });
}} }}
current={data.doc_conferma_ref}
/> />
<UploadModal isAdmin userId={userId} /> <UploadModal isAdmin userId={userId} />
</div> </div>
@ -845,15 +845,15 @@ const DocInfo = ({
<div className="flex w-full flex-1"> <div className="flex w-full flex-1">
<Link <Link
aria-label="Open Selected File" aria-label="Open Selected File"
href={`/area-riservata/allegato-view/${storageId}`}
target="_blank"
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: "outline", variant: "outline",
}), }),
"flex w-fit grow items-center gap-2", "flex w-fit grow items-center gap-2",
)} )}
href={`/area-riservata/allegato-view/${storageId}`}
id="selected-file" id="selected-file"
target="_blank"
> >
{file_info.ext && <ExtIcon ext={file_info.ext} />} {file_info.ext && <ExtIcon ext={file_info.ext} />}
<span className="max-w-40 truncate sm:max-w-80"> <span className="max-w-40 truncate sm:max-w-80">
@ -880,14 +880,14 @@ const DocCombo = ({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
return ( return (
<Popover open={open} onOpenChange={setOpen}> <Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
aria-label="Select Existing File"
variant="outline"
role="combobox"
aria-expanded={open} aria-expanded={open}
aria-label="Select Existing File"
className="w-auto justify-between" className="w-auto justify-between"
role="combobox"
variant="outline"
> >
Seleziona file esistente... Seleziona file esistente...
<ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" /> <ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
@ -902,7 +902,6 @@ const DocCombo = ({
{files.map((file) => ( {files.map((file) => (
<CommandItem <CommandItem
key={file.id} key={file.id}
value={file.id}
onSelect={(currentValue) => { onSelect={(currentValue) => {
if (currentValue !== current) { if (currentValue !== current) {
// biome-ignore lint/style/noNonNullAssertion: <exists> // biome-ignore lint/style/noNonNullAssertion: <exists>
@ -910,6 +909,7 @@ const DocCombo = ({
} }
setOpen(false); setOpen(false);
}} }}
value={file.id}
> >
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
{file.ext && <ExtIcon ext={file.ext} />} {file.ext && <ExtIcon ext={file.ext} />}
@ -985,12 +985,12 @@ const Caparra = ({
<div className="flex flex-wrap items-center gap-3"> <div className="flex flex-wrap items-center gap-3">
<Button <Button
aria-label="Import Caparra" aria-label="Import Caparra"
className="flex items-center gap-2"
onClick={async () => { onClick={async () => {
const clipboardText = await navigator.clipboard.readText(); const clipboardText = await navigator.clipboard.readText();
setCaparra(parseCaparra(clipboardText)); setCaparra(parseCaparra(clipboardText));
}} }}
className="flex items-center gap-2"
variant="info" variant="info"
> >
<ClipboardPaste className="size-4" /> <ClipboardPaste className="size-4" />
@ -998,6 +998,7 @@ const Caparra = ({
</Button> </Button>
<Button <Button
aria-label="Reset Caparra" aria-label="Reset Caparra"
className="flex items-center gap-2"
onClick={async () => { onClick={async () => {
setCaparra({ setCaparra({
caparra_causale: null, caparra_causale: null,
@ -1006,7 +1007,6 @@ const Caparra = ({
caparra_intestazione: null, caparra_intestazione: null,
}); });
}} }}
className="flex items-center gap-2"
variant="destructive" variant="destructive"
> >
<Trash2 className="size-4" /> <Trash2 className="size-4" />
@ -1019,6 +1019,7 @@ const Caparra = ({
{caparra.caparra_intestazione}{" "} {caparra.caparra_intestazione}{" "}
<Button <Button
aria-label="Edit Intestazione" aria-label="Edit Intestazione"
className="h-6 px-1.5 [&_svg]:size-4"
onClick={() => { onClick={() => {
const newValue = prompt( const newValue = prompt(
"Modifica l'intestazione della caparra:", "Modifica l'intestazione della caparra:",
@ -1032,7 +1033,6 @@ const Caparra = ({
); );
} }
}} }}
className="h-6 px-1.5 [&_svg]:size-4"
size="sm" size="sm"
> >
<Pen /> <Pen />
@ -1043,6 +1043,7 @@ const Caparra = ({
{caparra.caparra_iban}{" "} {caparra.caparra_iban}{" "}
<Button <Button
aria-label="Edit IBAN" aria-label="Edit IBAN"
className="h-6 px-1.5 [&_svg]:size-4"
onClick={() => { onClick={() => {
const newValue = prompt( const newValue = prompt(
"Modifica l'IBAN della caparra:", "Modifica l'IBAN della caparra:",
@ -1054,7 +1055,6 @@ const Caparra = ({
); );
} }
}} }}
className="h-6 px-1.5 [&_svg]:size-4"
size="sm" size="sm"
> >
<Pen /> <Pen />
@ -1065,6 +1065,7 @@ const Caparra = ({
{caparra.caparra_importo}{" "} {caparra.caparra_importo}{" "}
<Button <Button
aria-label="Edit Importo" aria-label="Edit Importo"
className="h-6 px-1.5 [&_svg]:size-4"
onClick={() => { onClick={() => {
const newValue = prompt( const newValue = prompt(
"Modifica l'importo della caparra:", "Modifica l'importo della caparra:",
@ -1076,7 +1077,6 @@ const Caparra = ({
); );
} }
}} }}
className="h-6 px-1.5 [&_svg]:size-4"
size="sm" size="sm"
> >
<Pen /> <Pen />
@ -1087,6 +1087,7 @@ const Caparra = ({
{caparra.caparra_causale}{" "} {caparra.caparra_causale}{" "}
<Button <Button
aria-label="Edit Causale" aria-label="Edit Causale"
className="h-6 px-1.5 [&_svg]:size-4"
onClick={() => { onClick={() => {
const newValue = prompt( const newValue = prompt(
"Modifica la causale della caparra:", "Modifica la causale della caparra:",
@ -1098,7 +1099,6 @@ const Caparra = ({
); );
} }
}} }}
className="h-6 px-1.5 [&_svg]:size-4"
size="sm" size="sm"
> >
<Pen /> <Pen />
@ -1183,8 +1183,8 @@ const SendConferma = ({
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch <Switch
aria-label="Conferma Lavorata" aria-label="Conferma Lavorata"
id="conferma"
checked={check} checked={check}
id="conferma"
onCheckedChange={(c) => { onCheckedChange={(c) => {
setCheck(c); setCheck(c);
}} }}
@ -1213,6 +1213,10 @@ export const AddButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
const { servizioId, userId } = useServizio(); const { servizioId, userId } = useServizio();
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate } = api.servizio.addServizioAnnunci.useMutation({ const { mutate } = api.servizio.addServizioAnnunci.useMutation({
onError: (error) => {
console.error(error);
toast.error("Errore durante l'aggiunta dell'annuncio");
},
onSuccess: async () => { onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ userId }); await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getAnnunciAvailableToAdd.invalidate({ await utils.servizio.getAnnunciAvailableToAdd.invalidate({
@ -1227,29 +1231,25 @@ export const AddButton = ({ annuncioId }: { annuncioId: AnnunciId }) => {
toast.success("Annuncio aggiunto alla ricerca"); toast.success("Annuncio aggiunto alla ricerca");
}, },
onError: (error) => {
console.error(error);
toast.error("Errore durante l'aggiunta dell'annuncio");
},
}); });
const buttonRef = useRef<AnimatedButtonRef>(null); const buttonRef = useRef<AnimatedButtonRef>(null);
return ( return (
<AnimatedButton <AnimatedButton
ref={buttonRef} className="group flex w-full gap-2"
sparklesIcon="thumbs-up"
sparklesNumber={15}
sparklesRadius={60}
sparklesClassName="fill-yellow-500 stroke-transparent"
sparklesScale={[1, 1.5]}
onClick={async () => { onClick={async () => {
buttonRef.current?.sparkle(); buttonRef.current?.sparkle();
//wait for the animation to finish //wait for the animation to finish
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
mutate({ servizioId, annunci: [annuncioId] }); mutate({ annunci: [annuncioId], servizioId });
}} }}
ref={buttonRef}
sparklesClassName="fill-yellow-500 stroke-transparent"
sparklesIcon="thumbs-up"
sparklesNumber={15}
sparklesRadius={60}
sparklesScale={[1, 1.5]}
variant="info" variant="info"
className="group flex w-full gap-2"
> >
<Plus className="size-5" /> <Plus className="size-5" />
<span>Aggiungi agli annunci da contattare</span> <span>Aggiungi agli annunci da contattare</span>

View file

@ -34,9 +34,9 @@ export const AnnuncioCard = ({ className }: { className?: string }) => {
return ( return (
<BasicAnnuncioCard <BasicAnnuncioCard
data={data}
className={className}
actions={<AnnuncioActions />} actions={<AnnuncioActions />}
className={className}
data={data}
interactions={<Interactions />} interactions={<Interactions />}
/> />
); );
@ -77,16 +77,16 @@ export const BasicAnnuncioCard = ({
<CardContent className="px-4"> <CardContent className="px-4">
<div className="flex w-full flex-col gap-4 md:flex-row md:gap-10"> <div className="flex w-full flex-col gap-4 md:flex-row md:gap-10">
<ImageFlbk <ImageFlbk
alt={data.codice}
className="h-auto w-full rounded-md object-contain sm:max-w-xs"
height={1080}
priority
src={ src={
(data.url_immagini && (data.url_immagini &&
`/go-api/images/get/${data.url_immagini[0]}`) || `/go-api/images/get/${data.url_immagini[0]}`) ||
"/fallback-image.png" "/fallback-image.png"
} }
priority
width={1920} width={1920}
height={1080}
className="h-auto w-full rounded-md object-contain sm:max-w-xs"
alt={data.codice}
/> />
<div className="flex w-full flex-col gap-5"> <div className="flex w-full flex-col gap-5">
{interactions} {interactions}
@ -95,11 +95,11 @@ export const BasicAnnuncioCard = ({
<TipoBadge tipo={data.tipo} /> <TipoBadge tipo={data.tipo} />
<span> <span>
{handleConsegna({ {handleConsegna({
aggiornamento: t.card.in_aggiornamento,
consegna: data.consegna, consegna: data.consegna,
consegna_da: t.card.consegna_da, consegna_da: t.card.consegna_da,
mesi: t.preferenze.mesi, mesi: t.preferenze.mesi,
subito: t.card.consegna_subito, subito: t.card.consegna_subito,
aggiornamento: t.card.in_aggiornamento,
})} })}
</span> </span>
<span>{formatCurrency(data.prezzo / 1e2)}</span> <span>{formatCurrency(data.prezzo / 1e2)}</span>
@ -110,12 +110,12 @@ export const BasicAnnuncioCard = ({
<AnnuncioDettaglio data={data} /> <AnnuncioDettaglio data={data} />
{data.stato !== "Sospeso" && data.web && ( {data.stato !== "Sospeso" && data.web && (
<Link <Link
href={`/annuncio/${data.codice}`}
aria-label="Pagina Annuncio" aria-label="Pagina Annuncio"
href={`/annuncio/${data.codice}`}
> >
<Button <Button
variant="outline"
className="flex items-center gap-2" className="flex items-center gap-2"
variant="outline"
> >
<ExternalLink className="size-6" /> <ExternalLink className="size-6" />
</Button> </Button>
@ -173,9 +173,9 @@ const AnnuncioDettaglio = ({
<Credenza> <Credenza>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
variant="outline"
className="flex items-center gap-2"
aria-label="Dettagli Annuncio" aria-label="Dettagli Annuncio"
className="flex items-center gap-2"
variant="outline"
> >
<UnfoldVertical className="size-6" /> Dettagli annuncio <UnfoldVertical className="size-6" /> Dettagli annuncio
</Button> </Button>
@ -197,21 +197,21 @@ const AnnuncioDettaglio = ({
<CarouselItem key={img}> <CarouselItem key={img}>
<ImageFlbk <ImageFlbk
alt={`${data.codice} - ${idx}`} alt={`${data.codice} - ${idx}`}
width={400} className={"h-80 w-full object-contain sm:h-[35rem]"}
height={200} height={200}
priority={idx === 0} priority={idx === 0}
src={`/go-api/images/get/${img}`} src={`/go-api/images/get/${img}`}
className={"h-80 w-full object-contain sm:h-[35rem]"} width={400}
/> />
</CarouselItem> </CarouselItem>
))} ))}
</CarouselContent> </CarouselContent>
<div <div
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
onClick={(e) => e.preventDefault()}
onKeyDown={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}
role="button" role="button"
tabIndex={0} tabIndex={0}
onClick={(e) => e.preventDefault()}
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
> >
<CarouselPrevious <CarouselPrevious
className="left-2 cursor-pointer" className="left-2 cursor-pointer"

View file

@ -22,6 +22,7 @@ export const FileSection = ({ storageId }: { storageId: StorageindexId }) => {
/> />
<Button <Button
className="underline underline-offset-1 after:content-['_↗']"
onClick={() => onClick={() =>
handleDownload({ handleDownload({
file: fileInfos, file: fileInfos,
@ -29,7 +30,6 @@ export const FileSection = ({ storageId }: { storageId: StorageindexId }) => {
}) })
} }
variant="link" variant="link"
className="underline underline-offset-1 after:content-['_↗']"
> >
{t.file_section.download} {t.file_section.download}
</Button> </Button>

View file

@ -63,8 +63,8 @@ export const Interactions = () => {
href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`} href={`/servizio/conferma-immobile/${servizioId}/${annuncioId}`}
> >
<Button <Button
variant="success"
className="flex w-fit items-center gap-2" className="flex w-fit items-center gap-2"
variant="success"
> >
<Pointer className="size-6" /> <Pointer className="size-6" />
Confermato - Procedi ora Confermato - Procedi ora
@ -75,8 +75,8 @@ export const Interactions = () => {
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="success"
className="flex w-fit cursor-not-allowed items-center gap-2 opacity-50" className="flex w-fit cursor-not-allowed items-center gap-2 opacity-50"
variant="success"
> >
<Pointer className="size-6" /> <Pointer className="size-6" />
Confermato - Procedi ora Confermato - Procedi ora
@ -96,7 +96,7 @@ export const Interactions = () => {
aria-label="Conferma immobile" aria-label="Conferma immobile"
href={`/servizio/riapri-conferma/${servizioId}/${annuncioId}`} href={`/servizio/riapri-conferma/${servizioId}/${annuncioId}`}
> >
<Button variant="success" className="flex w-fit items-center gap-2"> <Button className="flex w-fit items-center gap-2" variant="success">
<Pointer className="size-6" /> <Pointer className="size-6" />
Rivedi la conferma Rivedi la conferma
</Button> </Button>
@ -234,14 +234,14 @@ export const SaldoButton = ({
return ( return (
<Button <Button
aria-label="Procedi al saldo" aria-label="Procedi al saldo"
variant={variant}
className={cn("flex w-fit items-center gap-2", className)} className={cn("flex w-fit items-center gap-2", className)}
onClick={() => { onClick={() => {
mutate({ mutate({
servizioId,
annuncioId, annuncioId,
servizioId,
}); });
}} }}
variant={variant}
> >
<PackageCheck className="size-6" /> <PackageCheck className="size-6" />
Procedi al saldo Procedi al saldo
@ -275,14 +275,14 @@ const SaldoConsulenzaButton = ({
return ( return (
<Button <Button
aria-label="Procedi al saldo consulenza" aria-label="Procedi al saldo consulenza"
variant={variant}
className={cn("flex w-fit items-center gap-2", className)} className={cn("flex w-fit items-center gap-2", className)}
onClick={() => { onClick={() => {
mutate({ mutate({
servizioId,
annuncioId, annuncioId,
servizioId,
}); });
}} }}
variant={variant}
> >
<PackageCheck className="size-6" /> <PackageCheck className="size-6" />
Procedi al saldo della consulenza Procedi al saldo della consulenza
@ -297,12 +297,12 @@ const AnnuncioConfirm = () => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate, isPending } = api.servizio.userConfirmImmobile.useMutation({ const { mutate, isPending } = api.servizio.userConfirmImmobile.useMutation({
onMutate: () => {
toast.loading("Conferma in corso", { id: "updateRichiestaToast" });
},
onError: (err) => { onError: (err) => {
toast.error(err.message, { id: "updateRichiestaToast" }); toast.error(err.message, { id: "updateRichiestaToast" });
}, },
onMutate: () => {
toast.loading("Conferma in corso", { id: "updateRichiestaToast" });
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Richiesta confermata con successo", { toast.success("Richiesta confermata con successo", {
id: "updateRichiestaToast", id: "updateRichiestaToast",
@ -313,11 +313,11 @@ const AnnuncioConfirm = () => {
}, },
}); });
return ( return (
<Credenza open={open} onOpenChange={setOpen}> <Credenza onOpenChange={setOpen} open={open}>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
className="flex w-fit items-center gap-2"
aria-label="Conferma immobile" aria-label="Conferma immobile"
className="flex w-fit items-center gap-2"
> >
<Handshake className="size-6" /> Conferma immobile <Handshake className="size-6" /> Conferma immobile
</Button> </Button>
@ -336,13 +336,13 @@ const AnnuncioConfirm = () => {
<LoadingButton <LoadingButton
aria-label="Conferma immobile" aria-label="Conferma immobile"
loading={isPending} loading={isPending}
variant="success"
onClick={() => { onClick={() => {
mutate({ mutate({
annuncioId, annuncioId,
servizioId, servizioId,
}); });
}} }}
variant="success"
> >
{t.conferma} {t.conferma}
</LoadingButton> </LoadingButton>
@ -361,8 +361,8 @@ const ConfermaLavorazioneModal = () => {
<Credenza> <Credenza>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
className="flex w-fit items-center gap-2"
aria-label="Conferma in lavorazione" aria-label="Conferma in lavorazione"
className="flex w-fit items-center gap-2"
> >
<CircleCheck className="size-7" /> <CircleCheck className="size-7" />
Conferma in lavorazione Conferma in lavorazione
@ -381,10 +381,10 @@ const ConfermaLavorazioneModal = () => {
{data.user_confirmed_at && {data.user_confirmed_at &&
new Date(data.user_confirmed_at).toLocaleString("it", { new Date(data.user_confirmed_at).toLocaleString("it", {
day: "2-digit", day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
month: "2-digit",
year: "numeric",
})} })}
</p> </p>
<p> <p>
@ -396,7 +396,7 @@ const ConfermaLavorazioneModal = () => {
<CredenzaFooter> <CredenzaFooter>
<CredenzaClose asChild> <CredenzaClose asChild>
<Button variant="outline" aria-label="Chiudi modal credenza"> <Button aria-label="Chiudi modal credenza" variant="outline">
{t.chiudi} {t.chiudi}
</Button> </Button>
</CredenzaClose> </CredenzaClose>
@ -416,6 +416,11 @@ const ContattiProprietarioModal = () => {
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate } = api.servizio.unlockServizioContatti.useMutation({ const { mutate } = api.servizio.unlockServizioContatti.useMutation({
onError: (err) => {
toast.error(err.message, {
id: "unlock-contacts",
});
},
onMutate: () => { onMutate: () => {
toast.loading("Caricamento ...", { toast.loading("Caricamento ...", {
id: "unlock-contacts", id: "unlock-contacts",
@ -436,11 +441,6 @@ const ContattiProprietarioModal = () => {
}); });
setShowContacts(true); setShowContacts(true);
}, },
onError: (err) => {
toast.error(err.message, {
id: "unlock-contacts",
});
},
}); });
const d = new Date(); const d = new Date();
@ -452,18 +452,18 @@ const ContattiProprietarioModal = () => {
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
{showContacts ? ( {showContacts ? (
<Button <Button
variant="success"
className="flex w-fit items-center gap-2"
aria-label="Vedi contatti proprietario" aria-label="Vedi contatti proprietario"
className="flex w-fit items-center gap-2"
variant="success"
> >
<CircleUser className="size-7" /> <CircleUser className="size-7" />
Vedi i contatti Vedi i contatti
</Button> </Button>
) : ( ) : (
<Button <Button
variant="warning"
className="flex w-fit items-center gap-2"
aria-label="Contatta proprietario" aria-label="Contatta proprietario"
className="flex w-fit items-center gap-2"
variant="warning"
> >
<CircleUser className="size-7" /> <CircleUser className="size-7" />
Ottieni i contatti Ottieni i contatti
@ -504,19 +504,19 @@ const ContattiProprietarioModal = () => {
{!showContacts && ( {!showContacts && (
<Button <Button
aria-label="Contatta proprietario" aria-label="Contatta proprietario"
variant="success"
onClick={() => onClick={() =>
mutate({ mutate({
servizioId,
annuncioId, annuncioId,
servizioId,
}) })
} }
variant="success"
> >
{t.accetta} {t.accetta}
</Button> </Button>
)} )}
<CredenzaClose asChild> <CredenzaClose asChild>
<Button variant="outline" aria-label="Chiudi modal credenza"> <Button aria-label="Chiudi modal credenza" variant="outline">
{t.chiudi} {t.chiudi}
</Button> </Button>
</CredenzaClose> </CredenzaClose>
@ -575,8 +575,8 @@ const ContattiProprietarioData = () => {
{t.contatto.nome}: {data.propData.locatore} {t.contatto.nome}: {data.propData.locatore}
</p> </p>
<a <a
href={`tel:${data.propData.numero}`}
className="flex items-center gap-2" className="flex items-center gap-2"
href={`tel:${data.propData.numero}`}
> >
<Phone />: <Phone />:
<span className="text-blue-600 underline">{data.propData.numero}</span> <span className="text-blue-600 underline">{data.propData.numero}</span>
@ -589,10 +589,10 @@ const ContattiProprietarioData = () => {
) )
</p> </p>
<a <a
target="_blank"
rel="noreferrer"
href={navigationUrl}
className="border-border flex w-fit items-center gap-2 rounded-md border px-3 py-2" className="border-border flex w-fit items-center gap-2 rounded-md border px-3 py-2"
href={navigationUrl}
rel="noreferrer"
target="_blank"
> >
<Navigation /> <span>{t.contatto.apri_nav}</span> <Navigation /> <span>{t.contatto.apri_nav}</span>
</a> </a>
@ -601,10 +601,10 @@ const ContattiProprietarioData = () => {
{t.contatto.sbloccato_il}:{" "} {t.contatto.sbloccato_il}:{" "}
{data.opened_at.toLocaleString("it", { {data.opened_at.toLocaleString("it", {
day: "2-digit", day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
month: "2-digit",
year: "numeric",
})} })}
</p> </p>
<div className="flex items-center gap-2 rounded-md bg-sky-200 p-2"> <div className="flex items-center gap-2 rounded-md bg-sky-200 p-2">

View file

@ -46,9 +46,9 @@ export const ServizioContainer = ({
return ( return (
<ServizioProvider <ServizioProvider
isAdmin={isAdmin} isAdmin={isAdmin}
userId={userId}
servizioId={servizio.servizio_id}
servizio={servizio} servizio={servizio}
servizioId={servizio.servizio_id}
userId={userId}
> >
<ServizioMain /> <ServizioMain />
</ServizioProvider> </ServizioProvider>
@ -91,13 +91,13 @@ export const ServizioContent = () => {
const { servizio, isAdmin, userId } = useServizio(); const { servizio, isAdmin, userId } = useServizio();
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({ const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio modificato con successo"); toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId }); await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
const router = useRouter(); const router = useRouter();
@ -114,6 +114,15 @@ export const ServizioContent = () => {
return ( return (
<ServizioCard <ServizioCard
cardClassName="bg-neutral-100 border-neutral-300" cardClassName="bg-neutral-100 border-neutral-300"
content={
<>
<p className="text-lg font-medium">Servizio interrotto</p>
<p>
Non è possibile riutilizzare questo servizio, poichè è stato
interrotto.
</p>
</>
}
header={ header={
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
@ -126,24 +135,15 @@ export const ServizioContent = () => {
<p> <p>
aggiunto il{" "} aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", { {servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit", day: "2-digit",
month: "2-digit",
year: "numeric",
})} })}
</p> </p>
</CardDescription> </CardDescription>
</div> </div>
</> </>
} }
content={
<>
<p className="text-lg font-medium">Servizio interrotto</p>
<p>
Non è possibile riutilizzare questo servizio, poichè è stato
interrotto.
</p>
</>
}
/> />
); );
} }
@ -151,6 +151,40 @@ export const ServizioContent = () => {
if (!servizio.isOkAcconto || servizio.decorrenza === null) { if (!servizio.isOkAcconto || servizio.decorrenza === null) {
return ( return (
<ServizioCard <ServizioCard
content={
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-3">
<Link
aria-label="Attiva Servizio"
href={`/servizio/onboard/${servizio.servizio_id}`}
>
<Button>Procedi all&apos;attivazione</Button>
</Link>
{isAdmin && (
<Button
onClick={() =>
updateServizio({
data: {
decorrenza: new Date(),
isOkAcconto: true,
},
servizioId: servizio.servizio_id,
})
}
variant="success"
>
Attiva Manualmente (Azione Admin)
</Button>
)}
</div>
{isAdmin && (
<AnnunciSelezionati
annunci={servizio.annunci}
isConfirming={false}
/>
)}
</div>
}
header={ header={
<div className="flex w-full flex-col gap-2"> <div className="flex w-full flex-col gap-2">
{/* <div className="flex w-full flex-col gap-4"> {/* <div className="flex w-full flex-col gap-4">
@ -168,40 +202,6 @@ export const ServizioContent = () => {
<ServizioActions2 /> <ServizioActions2 />
</div> </div>
} }
content={
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-3">
<Link
href={`/servizio/onboard/${servizio.servizio_id}`}
aria-label="Attiva Servizio"
>
<Button>Procedi all&apos;attivazione</Button>
</Link>
{isAdmin && (
<Button
onClick={() =>
updateServizio({
servizioId: servizio.servizio_id,
data: {
isOkAcconto: true,
decorrenza: new Date(),
},
})
}
variant="success"
>
Attiva Manualmente (Azione Admin)
</Button>
)}
</div>
{isAdmin && (
<AnnunciSelezionati
isConfirming={false}
annunci={servizio.annunci}
/>
)}
</div>
}
/> />
); );
} }
@ -212,6 +212,17 @@ export const ServizioContent = () => {
if (servizio.isOkSaldo) { if (servizio.isOkSaldo) {
return ( return (
<ServizioCard <ServizioCard
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
header={ header={
<> <>
{/* <div className="flex w-full flex-wrap items-center gap-2"> {/* <div className="flex w-full flex-wrap items-center gap-2">
@ -235,17 +246,6 @@ export const ServizioContent = () => {
<ServizioActions2 /> <ServizioActions2 />
</> </>
} }
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
/> />
); );
} }
@ -263,6 +263,68 @@ export const ServizioContent = () => {
return ( return (
<ServizioCard <ServizioCard
content={
<>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<UserCircle className="text-primary size-6" />
<span className="font-medium">Contatti sbloccati</span>
</div>
<span className="text-sm">{openContattiCount} / 10</span>
</div>
<Progress className="h-2" value={openContattiCount * 10} />
</div>
<ServizioDuration decorrenza={servizio.decorrenza} />
{annuncioConfermato.length > 0 && (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<PackageCheck className="text-primary size-6" />
<span className="font-medium">Saldi in attesa:</span>
</div>
{servizio.annunci
.filter((a) => a.accettato_conferma_at !== null)
.map((a) => (
<div
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
key={a.annunci_id}
>
<h3>
{a.codice} - confermato il:{" "}
{/** biome-ignore lint/style/noNonNullAssertion: <already checked above> */}
{new Date(a.accettato_conferma_at!).toLocaleDateString(
"it",
{
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
month: "2-digit",
year: "numeric",
},
)}
</h3>
<SaldoButton
annuncioId={a.annunci_id}
className="border-none"
variant={"outline"}
/>
</div>
))}
</div>
)}
<div className="space-y-2">
<AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati
annunci={annunciNotInConferma}
isConfirming={annunciInConferma.length > 0}
/>
<AnnunciCompatibili />
</div>
</>
}
header={ header={
<> <>
{/* <div className="flex w-full flex-col gap-2"> {/* <div className="flex w-full flex-col gap-2">
@ -286,68 +348,6 @@ export const ServizioContent = () => {
<ServizioActions2 /> <ServizioActions2 />
</> </>
} }
content={
<>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<UserCircle className="text-primary size-6" />
<span className="font-medium">Contatti sbloccati</span>
</div>
<span className="text-sm">{openContattiCount} / 10</span>
</div>
<Progress value={openContattiCount * 10} className="h-2" />
</div>
<ServizioDuration decorrenza={servizio.decorrenza} />
{annuncioConfermato.length > 0 && (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<PackageCheck className="text-primary size-6" />
<span className="font-medium">Saldi in attesa:</span>
</div>
{servizio.annunci
.filter((a) => a.accettato_conferma_at !== null)
.map((a) => (
<div
key={a.annunci_id}
className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
>
<h3>
{a.codice} - confermato il:{" "}
{/** biome-ignore lint/style/noNonNullAssertion: <already checked above> */}
{new Date(a.accettato_conferma_at!).toLocaleDateString(
"it",
{
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
},
)}
</h3>
<SaldoButton
variant={"outline"}
className="border-none"
annuncioId={a.annunci_id}
/>
</div>
))}
</div>
)}
<div className="space-y-2">
<AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati
isConfirming={annunciInConferma.length > 0}
annunci={annunciNotInConferma}
/>
<AnnunciCompatibili />
</div>
</>
}
/> />
); );
}; };
@ -356,13 +356,13 @@ const ServizioMain = () => {
const { servizio, isAdmin, userId } = useServizio(); const { servizio, isAdmin, userId } = useServizio();
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({ const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio modificato con successo"); toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId }); await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
const router = useRouter(); const router = useRouter();
@ -379,6 +379,15 @@ const ServizioMain = () => {
return ( return (
<ServizioCard <ServizioCard
cardClassName="bg-neutral-100 border-neutral-300" cardClassName="bg-neutral-100 border-neutral-300"
content={
<>
<p className="text-lg font-medium">Servizio interrotto</p>
<p>
Non è possibile riutilizzare questo servizio, poichè è stato
interrotto.
</p>
</>
}
header={ header={
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
@ -391,24 +400,15 @@ const ServizioMain = () => {
<p> <p>
aggiunto il{" "} aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", { {servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit", day: "2-digit",
month: "2-digit",
year: "numeric",
})} })}
</p> </p>
</CardDescription> </CardDescription>
</div> </div>
</> </>
} }
content={
<>
<p className="text-lg font-medium">Servizio interrotto</p>
<p>
Non è possibile riutilizzare questo servizio, poichè è stato
interrotto.
</p>
</>
}
/> />
); );
} }
@ -416,6 +416,40 @@ const ServizioMain = () => {
if (!servizio.isOkAcconto || servizio.decorrenza === null) { if (!servizio.isOkAcconto || servizio.decorrenza === null) {
return ( return (
<ServizioCard <ServizioCard
content={
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-3">
<Link
aria-label="Attiva Servizio"
href={`/servizio/onboard/${servizio.servizio_id}`}
>
<Button>Procedi all&apos;attivazione</Button>
</Link>
{isAdmin && (
<Button
onClick={() =>
updateServizio({
data: {
decorrenza: new Date(),
isOkAcconto: true,
},
servizioId: servizio.servizio_id,
})
}
variant="success"
>
Attiva Manualmente (Azione Admin)
</Button>
)}
</div>
{isAdmin && (
<AnnunciSelezionati
annunci={servizio.annunci}
isConfirming={false}
/>
)}
</div>
}
header={ header={
<> <>
<div className="flex w-full flex-col gap-4"> <div className="flex w-full flex-col gap-4">
@ -430,40 +464,6 @@ const ServizioMain = () => {
<ServizioActions /> <ServizioActions />
</> </>
} }
content={
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-3">
<Link
href={`/servizio/onboard/${servizio.servizio_id}`}
aria-label="Attiva Servizio"
>
<Button>Procedi all&apos;attivazione</Button>
</Link>
{isAdmin && (
<Button
onClick={() =>
updateServizio({
servizioId: servizio.servizio_id,
data: {
isOkAcconto: true,
decorrenza: new Date(),
},
})
}
variant="success"
>
Attiva Manualmente (Azione Admin)
</Button>
)}
</div>
{isAdmin && (
<AnnunciSelezionati
isConfirming={false}
annunci={servizio.annunci}
/>
)}
</div>
}
/> />
); );
} }
@ -474,6 +474,17 @@ const ServizioMain = () => {
if (servizio.isOkSaldo) { if (servizio.isOkSaldo) {
return ( return (
<ServizioCard <ServizioCard
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
header={ header={
<> <>
<div className="flex w-full flex-wrap items-center gap-2"> <div className="flex w-full flex-wrap items-center gap-2">
@ -486,9 +497,9 @@ const ServizioMain = () => {
<p> <p>
aggiunto il{" "} aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", { {servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit", day: "2-digit",
month: "2-digit",
year: "numeric",
})} })}
</p> </p>
</CardDescription> </CardDescription>
@ -497,17 +508,6 @@ const ServizioMain = () => {
<ServizioActions /> <ServizioActions />
</> </>
} }
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
/> />
); );
} }
@ -525,29 +525,6 @@ const ServizioMain = () => {
return ( return (
<ServizioCard <ServizioCard
header={
<>
<div className="flex w-full flex-col gap-2">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 space-y-2 pl-2">
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})}
</p>
</CardDescription>
</div>
<ServizioActions />
</>
}
content={ content={
<> <>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
@ -558,7 +535,7 @@ const ServizioMain = () => {
</div> </div>
<span className="text-sm">{openContattiCount} / 10</span> <span className="text-sm">{openContattiCount} / 10</span>
</div> </div>
<Progress value={openContattiCount * 10} className="h-2" /> <Progress className="h-2" value={openContattiCount * 10} />
</div> </div>
<ServizioDuration decorrenza={servizio.decorrenza} /> <ServizioDuration decorrenza={servizio.decorrenza} />
@ -573,8 +550,8 @@ const ServizioMain = () => {
.filter((a) => a.accettato_conferma_at !== null) .filter((a) => a.accettato_conferma_at !== null)
.map((a) => ( .map((a) => (
<div <div
key={a.annunci_id}
className="flex items-center gap-2 rounded-md bg-sky-400 p-2" className="flex items-center gap-2 rounded-md bg-sky-400 p-2"
key={a.annunci_id}
> >
<h3> <h3>
{a.codice} - confermato il:{" "} {a.codice} - confermato il:{" "}
@ -582,18 +559,18 @@ const ServizioMain = () => {
{new Date(a.accettato_conferma_at!).toLocaleDateString( {new Date(a.accettato_conferma_at!).toLocaleDateString(
"it", "it",
{ {
year: "numeric",
month: "2-digit",
day: "2-digit", day: "2-digit",
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
month: "2-digit",
year: "numeric",
}, },
)} )}
</h3> </h3>
<SaldoButton <SaldoButton
variant={"outline"}
className="border-none"
annuncioId={a.annunci_id} annuncioId={a.annunci_id}
className="border-none"
variant={"outline"}
/> />
</div> </div>
))} ))}
@ -603,13 +580,36 @@ const ServizioMain = () => {
<div className="space-y-2"> <div className="space-y-2">
<AnnunciInConferma annunci={annunciInConferma} /> <AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati <AnnunciSelezionati
isConfirming={annunciInConferma.length > 0}
annunci={annunciNotInConferma} annunci={annunciNotInConferma}
isConfirming={annunciInConferma.length > 0}
/> />
<AnnunciCompatibili /> <AnnunciCompatibili />
</div> </div>
</> </>
} }
header={
<>
<div className="flex w-full flex-col gap-2">
<CardTitle className="flex items-center gap-2 text-xl sm:text-2xl">
<Package className="text-primary size-6" />
Cerco Affitto {servizio.tipologia}
</CardTitle>
<CardDescription className="text-foreground/70 space-y-2 pl-2">
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
day: "2-digit",
month: "2-digit",
year: "numeric",
})}
</p>
</CardDescription>
</div>
<ServizioActions />
</>
}
/> />
); );
}; };

View file

@ -21,9 +21,9 @@ export const ServizioDuration = ({
} }
return ( return (
<Accordion <Accordion
type="single"
collapsible collapsible
defaultValue={isDesktop ? "validity" : undefined} defaultValue={isDesktop ? "validity" : undefined}
type="single"
> >
<AccordionItem value="validity"> <AccordionItem value="validity">
<AccordionTrigger> <AccordionTrigger>
@ -35,8 +35,8 @@ export const ServizioDuration = ({
<AccordionContent> <AccordionContent>
<ServiceDurationDisplay <ServiceDurationDisplay
decorrenza={decorrenza} decorrenza={decorrenza}
scadenza={add(decorrenza, { days: 60 })}
recessoDays={14} recessoDays={14}
scadenza={add(decorrenza, { days: 60 })}
/> />
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
@ -70,7 +70,7 @@ function ServiceDurationDisplay({
<CardContent className="flex flex-col gap-5 px-1"> <CardContent className="flex flex-col gap-5 px-1">
<div className="flex w-full flex-col justify-between gap-4 sm:flex-row sm:items-center"> <div className="flex w-full flex-col justify-between gap-4 sm:flex-row sm:items-center">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<CalendarDays className="text-primary h-6 w-6" aria-hidden="true" /> <CalendarDays aria-hidden="true" className="text-primary h-6 w-6" />
<div> <div>
<div className="text-muted-foreground text-sm">Decorrenza</div> <div className="text-muted-foreground text-sm">Decorrenza</div>
<div className="text-foreground text-lg font-semibold"> <div className="text-foreground text-lg font-semibold">
@ -84,8 +84,8 @@ function ServiceDurationDisplay({
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<CalendarClock <CalendarClock
className="text-primary h-6 w-6"
aria-hidden="true" aria-hidden="true"
className="text-primary h-6 w-6"
/> />
<div> <div>
<div className="text-muted-foreground text-sm">Scadenza</div> <div className="text-muted-foreground text-sm">Scadenza</div>
@ -99,7 +99,7 @@ function ServiceDurationDisplay({
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Hourglass className="text-primary h-6 w-6" aria-hidden="true" /> <Hourglass aria-hidden="true" className="text-primary h-6 w-6" />
<div> <div>
<div className="text-muted-foreground text-sm"> <div className="text-muted-foreground text-sm">
Periodo di recesso Periodo di recesso
@ -114,9 +114,9 @@ function ServiceDurationDisplay({
{/* Progress Bar */} {/* Progress Bar */}
<div className="pt-2"> <div className="pt-2">
<Progress <Progress
value={progressPercentage}
className="h-2"
aria-label="Service duration progress" aria-label="Service duration progress"
className="h-2"
value={progressPercentage}
/> />
<div className="text-muted-foreground mt-1 text-right text-sm"> <div className="text-muted-foreground mt-1 text-right text-sm">
{remainingDaysText} {remainingDaysText}

View file

@ -128,6 +128,10 @@ export const AddAnnuncio = ({
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate } = api.servizio.addServizioAnnunci.useMutation({ const { mutate } = api.servizio.addServizioAnnunci.useMutation({
onError: (error) => {
console.error(error);
toast.error("Errore durante l'aggiunta dell'annuncio");
},
onSuccess: async () => { onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ userId }); await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
await utils.servizio.getAnnunciAvailableToAdd.invalidate({ await utils.servizio.getAnnunciAvailableToAdd.invalidate({
@ -141,22 +145,18 @@ export const AddAnnuncio = ({
setOpenModal(false); setOpenModal(false);
setSelectedAnnunci([]); setSelectedAnnunci([]);
}, },
onError: (error) => {
console.error(error);
toast.error("Errore durante l'aggiunta dell'annuncio");
},
}); });
return ( return (
<Dialog open={openModal} onOpenChange={setOpenModal}> <Dialog onOpenChange={setOpenModal} open={openModal}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button
aria-label="Add Annuncio" aria-label="Add Annuncio"
variant="outline" className="items-center gap-2"
onClick={() => { onClick={() => {
setSelectedAnnunci([]); setSelectedAnnunci([]);
}} }}
className="items-center gap-2" variant="outline"
> >
<Plus /> <Plus />
Annuncio Annuncio
@ -181,40 +181,40 @@ export const AddAnnuncio = ({
<LoadingPage /> <LoadingPage />
) : ( ) : (
<MultiSelect <MultiSelect
defaultValue={undefined}
elementId="select-annuncio" elementId="select-annuncio"
elementName="annuncio" elementName="annuncio"
isMulti={true} isMulti={true}
placeholder="" onValueChange={(value) => {
defaultValue={undefined} setSelectedAnnunci(
value.map((v) => ({
label: v.label,
value: parseInt(v.value) as AnnunciId,
})),
);
}}
options={ options={
codici?.map((cod) => ({ codici?.map((cod) => ({
label: cod.codice, label: cod.codice,
value: cod.id.toString(), value: cod.id.toString(),
})) || [] })) || []
} }
onValueChange={(value) => { placeholder=""
setSelectedAnnunci(
value.map((v) => ({
value: parseInt(v.value) as AnnunciId,
label: v.label,
})),
);
}}
/> />
)} )}
</div> </div>
<DialogFooter> <DialogFooter>
<Button <Button
aria-label="Cancel Add Annuncio" aria-label="Cancel Add Annuncio"
type="button" disabled={!selectedAnnunci.length}
onClick={() => { onClick={() => {
if (!selectedAnnunci.length) return; if (!selectedAnnunci.length) return;
mutate({ mutate({
servizioId,
annunci: selectedAnnunci.map((annuncio) => annuncio.value), annunci: selectedAnnunci.map((annuncio) => annuncio.value),
servizioId,
}); });
}} }}
disabled={!selectedAnnunci.length} type="button"
> >
Aggiungi Aggiungi
</Button> </Button>
@ -236,14 +236,14 @@ const InterruzioneServizio = () => {
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate } = api.servizio.interruzioneServizio.useMutation({ const { mutate } = api.servizio.interruzioneServizio.useMutation({
onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
toast.success("Interruzione richiesta");
},
onError: (error) => { onError: (error) => {
console.error(error); console.error(error);
toast.error("Errore durante la richiesta di interruzione"); toast.error("Errore durante la richiesta di interruzione");
}, },
onSuccess: async () => {
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
toast.success("Interruzione richiesta");
},
}); });
return ( return (
@ -251,9 +251,9 @@ const InterruzioneServizio = () => {
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button <Button
aria-label="Request Interruzione" aria-label="Request Interruzione"
variant="outline"
className="items-center gap-2" className="items-center gap-2"
disabled={!beforeInterruzioneLimit} disabled={!beforeInterruzioneLimit}
variant="outline"
> >
<ClockAlert /> <span>Interruzione</span> <ClockAlert /> <span>Interruzione</span>
</Button> </Button>
@ -291,6 +291,9 @@ const EditPreferenze = () => {
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate: update } = api.servizio.updateServizio.useMutation({ const { mutate: update } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio modificato con successo"); toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId }); await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
@ -300,22 +303,19 @@ const EditPreferenze = () => {
}); });
setOpenEditPreferenze(false); setOpenEditPreferenze(false);
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
function onSubmit(fields: FormValues) { function onSubmit(fields: FormValues) {
update({ update({
servizioId,
data: { data: {
...fields, ...fields,
}, },
servizioId,
}); });
} }
return ( return (
<Credenza open={openEditPreferenze} onOpenChange={setOpenEditPreferenze}> <Credenza onOpenChange={setOpenEditPreferenze} open={openEditPreferenze}>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
aria-label="Preferenze di ricerca" aria-label="Preferenze di ricerca"
@ -338,8 +338,8 @@ const EditPreferenze = () => {
) : ( ) : (
<FormNewServizio <FormNewServizio
initialData={data} initialData={data}
onSubmit={onSubmit}
isLimited={!isAdmin} isLimited={!isAdmin}
onSubmit={onSubmit}
/> />
)} )}
</CredenzaBody> </CredenzaBody>
@ -355,6 +355,9 @@ const EditServizioAdmin = () => {
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate: update } = api.servizio.updateServizio.useMutation({ const { mutate: update } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio modificato con successo"); toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId }); await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
@ -364,22 +367,19 @@ const EditServizioAdmin = () => {
}); });
setOpen(false); setOpen(false);
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
function onSubmit(fields: EditFormValues) { function onSubmit(fields: EditFormValues) {
update({ update({
servizioId,
data: { data: {
...fields, ...fields,
}, },
servizioId,
}); });
} }
return ( return (
<Credenza open={open} onOpenChange={setOpen}> <Credenza onOpenChange={setOpen} open={open}>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button <Button
aria-label="Modifica Servizio Admin" aria-label="Modifica Servizio Admin"
@ -404,7 +404,7 @@ const EditServizioAdmin = () => {
)} )}
<Collapsible> <Collapsible>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<Button variant="warning" className="items-center gap-2"> <Button className="items-center gap-2" variant="warning">
<Bug /> Debug Info Servizio <Bug /> Debug Info Servizio
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>

View file

@ -32,8 +32,8 @@ export const AnnunciInConferma = ({
} }
return ( return (
<Accordion type="single" collapsible defaultValue="in-conferma"> <Accordion collapsible defaultValue="in-conferma" type="single">
<AccordionItem value="in-conferma" className="space-y-4"> <AccordionItem className="space-y-4" value="in-conferma">
<AccordionTrigger className="rounded-md border-2 border-green-500 bg-white p-2"> <AccordionTrigger className="rounded-md border-2 border-green-500 bg-white p-2">
Annunci in conferma: Annunci in conferma:
</AccordionTrigger> </AccordionTrigger>
@ -62,11 +62,11 @@ export const AnnunciSelezionati = ({
}) => { }) => {
return ( return (
<Accordion <Accordion
type="single"
collapsible collapsible
defaultValue={!isConfirming ? "not-in-conferma" : undefined} defaultValue={!isConfirming ? "not-in-conferma" : undefined}
type="single"
> >
<AccordionItem value="not-in-conferma" className="space-y-4"> <AccordionItem className="space-y-4" value="not-in-conferma">
<AccordionTrigger className="cursor-pointer rounded-md border-2 border-sky-500 bg-white p-2"> <AccordionTrigger className="cursor-pointer rounded-md border-2 border-sky-500 bg-white p-2">
Annunci Selezionati: Annunci Selezionati:
</AccordionTrigger> </AccordionTrigger>
@ -86,8 +86,8 @@ export const AnnunciSelezionati = ({
Nessun annuncio selezionato, fai una ricerca nella pagina{" "} Nessun annuncio selezionato, fai una ricerca nella pagina{" "}
<Link <Link
aria-label="Annunci Compatibili" aria-label="Annunci Compatibili"
href="/annunci"
className="inline-flex items-center gap-1 text-blue-500 underline" className="inline-flex items-center gap-1 text-blue-500 underline"
href="/annunci"
> >
Annunci Annunci
<ExternalLink className="size-4" /> <ExternalLink className="size-4" />
@ -116,11 +116,11 @@ export const AnnunciCompatibili = () => {
return ( return (
<Accordion <Accordion
type="single"
collapsible collapsible
defaultValue={!data || data.length === 0 ? "compatibili" : undefined} defaultValue={!data || data.length === 0 ? "compatibili" : undefined}
type="single"
> >
<AccordionItem value="compatibili" className="space-y-4"> <AccordionItem className="space-y-4" value="compatibili">
<AccordionTrigger className="cursor-pointer rounded-md border-2 border-neutral-500 bg-white p-2"> <AccordionTrigger className="cursor-pointer rounded-md border-2 border-neutral-500 bg-white p-2">
Annunci Compatibili: Annunci Compatibili:
</AccordionTrigger> </AccordionTrigger>
@ -137,9 +137,8 @@ export const AnnunciCompatibili = () => {
data.map((a) => { data.map((a) => {
return ( return (
<BasicAnnuncioCard <BasicAnnuncioCard
key={a.id}
data={a}
className="border-2 border-neutral-500" className="border-2 border-neutral-500"
data={a}
interactions={ interactions={
<> <>
{isAdmin ? ( {isAdmin ? (
@ -152,6 +151,7 @@ export const AnnunciCompatibili = () => {
)} )}
</> </>
} }
key={a.id}
/> />
); );
}) })
@ -180,8 +180,8 @@ export const AnnunciRichiesti = ({ userId }: { userId: UsersId }) => {
if (!data || data.length === 0) return null; if (!data || data.length === 0) return null;
return ( return (
<Accordion type="single" collapsible> <Accordion collapsible type="single">
<AccordionItem value="compatibili" className="space-y-4"> <AccordionItem className="space-y-4" value="compatibili">
<AccordionTrigger className="cursor-pointer rounded-md border-2 border-pink-500 bg-white p-2"> <AccordionTrigger className="cursor-pointer rounded-md border-2 border-pink-500 bg-white p-2">
Annunci Richiesti: Annunci Richiesti:
</AccordionTrigger> </AccordionTrigger>
@ -200,19 +200,19 @@ export const AnnunciRichiesti = ({ userId }: { userId: UsersId }) => {
<BasicAnnuncioCard <BasicAnnuncioCard
className="w-full border-2 border-pink-500" className="w-full border-2 border-pink-500"
data={a} data={a}
key={a.id}
interactions={ interactions={
<Button <Button
className="flex items-center gap-2"
onClick={() => { onClick={() => {
deleteAnnuncio({ annuncioId: a.id, userId }); deleteAnnuncio({ annuncioId: a.id, userId });
}} }}
className="flex items-center gap-2"
variant="destructive" variant="destructive"
> >
Rimuovi Rimuovi
<Trash2 className="size-4" /> <Trash2 className="size-4" />
</Button> </Button>
} }
key={a.id}
/> />
); );
}) })

View file

@ -16,17 +16,17 @@ export const Status500 = () => {
<p className="text-neutral-600">{t[500].sottotitolo}</p> <p className="text-neutral-600">{t[500].sottotitolo}</p>
<div className="flex flex-wrap items-center justify-center gap-3"> <div className="flex flex-wrap items-center justify-center gap-3">
<button <button
type="button" className="block rounded-lg bg-red-600 px-4 py-2 font-medium text-white duration-150 hover:bg-red-500 active:bg-red-700"
onClick={() => { onClick={() => {
router.back(); router.back();
}} }}
className="block rounded-lg bg-red-600 px-4 py-2 font-medium text-white duration-150 hover:bg-red-500 active:bg-red-700" type="button"
> >
{t[500].CTA} {t[500].CTA}
</button> </button>
<Link <Link
href="/"
className="block rounded-lg border px-4 py-2 font-medium text-neutral-700 duration-150 hover:bg-neutral-50 active:bg-neutral-100" className="block rounded-lg border px-4 py-2 font-medium text-neutral-700 duration-150 hover:bg-neutral-50 active:bg-neutral-100"
href="/"
> >
{t[500].home} {t[500].home}
</Link> </Link>

View file

@ -45,11 +45,11 @@ export function SubscriptionStatus({
<div> <div>
Error - <em>{errorMsg}</em> Error - <em>{errorMsg}</em>
<button <button
type="button" className="hover:underline"
onClick={() => { onClick={() => {
reset(); reset();
}} }}
className="hover:underline" type="button"
> >
Try Again Try Again
</button> </button>

View file

@ -38,48 +38,48 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
ry={13.1} ry={13.1}
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_27"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#3a2c6d]" className="fill-[#3a2c6d]"
d="M454.85 237.69c.91-1.46 7.86-2.42 10.15 3.47.86 2.23.73 7.94 1.46 11.52s2.43 5.62 3 8.53-.38 7.78-7.39 8.65-12.4-1.6-14.06-3.84-1.69-7 0-9.53 3-4.63 3-8Z" d="M454.85 237.69c.91-1.46 7.86-2.42 10.15 3.47.86 2.23.73 7.94 1.46 11.52s2.43 5.62 3 8.53-.38 7.78-7.39 8.65-12.4-1.6-14.06-3.84-1.69-7 0-9.53 3-4.63 3-8Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_27"
/> />
<path <path
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M446.53 262.33S444.88 274 448 282.21s2.25-2.13 2.25-2.13l-.75-2.46s-.62-9.5-.36-11.83.2-4.33.36-4.52-.61-.56-.94-.61-2.03 1.67-2.03 1.67Z" d="M446.53 262.33S444.88 274 448 282.21s2.25-2.13 2.25-2.13l-.75-2.46s-.62-9.5-.36-11.83.2-4.33.36-4.52-.61-.56-.94-.61-2.03 1.67-2.03 1.67Z"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_28"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#3a2c6d]" className="fill-[#3a2c6d]"
d="M459.44 236.68s-5.23-1.51-8.4 1.32a7.77 7.77 0 0 0-2.24 7.75c.42 1.65 1.27 3.91 2.58 4s8.06-13.07 8.06-13.07Z" d="M459.44 236.68s-5.23-1.51-8.4 1.32a7.77 7.77 0 0 0-2.24 7.75c.42 1.65 1.27 3.91 2.58 4s8.06-13.07 8.06-13.07Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_28"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_29"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M461.44 251s-.34 5.83-.23 6.26 2.28.84 2.9 1.35-3.17 6.42-5.1 7.3-7.88-1.36-8.29-4.33c-.38-2.72 1.7-4.84 2.51-5.58a12.41 12.41 0 0 1 1.64 0l.17-2.82c2.61-.93 6.4-2.18 6.4-2.18Z" d="M461.44 251s-.34 5.83-.23 6.26 2.28.84 2.9 1.35-3.17 6.42-5.1 7.3-7.88-1.36-8.29-4.33c-.38-2.72 1.7-4.84 2.51-5.58a12.41 12.41 0 0 1 1.64 0l.17-2.82c2.61-.93 6.4-2.18 6.4-2.18Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_29"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_30"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#e4e4e7]" className="fill-[#e4e4e7]"
d="M462.24 257.77c1.64.26 3.51 1.13 3.77 2a19.33 19.33 0 0 1-.47 9.73c-1.42 3.75-2.37 6.14-3.19 7.76 0 0-7.09 3-12.8-.51 0 0-.4-4-.42-6.17-5.28-4.6-.77-10.28 3.7-14.69a10.85 10.85 0 0 1 1.61 0s-4 4.72-1.21 7.4c5.93-1.79 7.11-4.45 9.01-5.52Z" d="M462.24 257.77c1.64.26 3.51 1.13 3.77 2a19.33 19.33 0 0 1-.47 9.73c-1.42 3.75-2.37 6.14-3.19 7.76 0 0-7.09 3-12.8-.51 0 0-.4-4-.42-6.17-5.28-4.6-.77-10.28 3.7-14.69a10.85 10.85 0 0 1 1.61 0s-4 4.72-1.21 7.4c5.93-1.79 7.11-4.45 9.01-5.52Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_30"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_31"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M450.55 243.72c.59-3.75 3.29-6.58 7.16-6.35a7.43 7.43 0 0 1 3.73 13.63 7.54 7.54 0 0 1-.59 1.63c-1.37 1.6-5 2.61-6.21 2.62-1 0-1.93-.95-2.9-2.56-2.03-3.4-1.51-7.12-1.19-8.97Z" d="M450.55 243.72c.59-3.75 3.29-6.58 7.16-6.35a7.43 7.43 0 0 1 3.73 13.63 7.54 7.54 0 0 1-.59 1.63c-1.37 1.6-5 2.61-6.21 2.62-1 0-1.93-.95-2.9-2.56-2.03-3.4-1.51-7.12-1.19-8.97Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_31"
/> />
<path <path
className="fill-[#3a2c6d]" className="fill-[#3a2c6d]"
d="M458.9 237.44c2.71.53 4.94 1.29 5.68 3.95.5 1.84 1.16 5.33.53 6.8l-.38 1.07-3.29 1.74s-1.53-1.28-1.05-5a2.8 2.8 0 0 0-.46-1.42 4.12 4.12 0 0 1-.46-1.13 4.25 4.25 0 0 0-2.12-2.3c-2.45-1.37-6-.19-6.52.31a7.11 7.11 0 0 1 8.07-4.02Z" d="M458.9 237.44c2.71.53 4.94 1.29 5.68 3.95.5 1.84 1.16 5.33.53 6.8l-.38 1.07-3.29 1.74s-1.53-1.28-1.05-5a2.8 2.8 0 0 0-.46-1.42 4.12 4.12 0 0 1-.46-1.13 4.25 4.25 0 0 0-2.12-2.3c-2.45-1.37-6-.19-6.52.31a7.11 7.11 0 0 1 8.07-4.02Z"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_32"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M466.2 285c.61-3.8 1.75-6.26 1.22-8.35a75.85 75.85 0 0 0-4.62-12.3c-1.2-2.36-.83-4 .41-4.94 1.4-1 2.73-.59 4 2 1.59 3.29 3.37 6.67 4.24 11.57a25.78 25.78 0 0 1-.12 9.57c-.54 3.41-1.62 5.33-2.91 8.53-.36.92-2.74-2.84-2.22-6.08Z" d="M466.2 285c.61-3.8 1.75-6.26 1.22-8.35a75.85 75.85 0 0 0-4.62-12.3c-1.2-2.36-.83-4 .41-4.94 1.4-1 2.73-.59 4 2 1.59 3.29 3.37 6.67 4.24 11.57a25.78 25.78 0 0 1-.12 9.57c-.54 3.41-1.62 5.33-2.91 8.53-.36.92-2.74-2.84-2.22-6.08Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_32"
/> />
<path <path
className="fill-[#38226d]" className="fill-[#38226d]"
@ -119,10 +119,10 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
ry={13.1} ry={13.1}
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_33"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M98.48 386.23c2 .57 3.12 2.76 4.86 7.45a39.25 39.25 0 0 1 2.09 13.22c-.56 4.42-2.39 7.86-6.81 11.44l-2.11-3.87s4.33-3.82 4.77-8.24c.25-2.48-2.66-10.84-2.66-11.07s-.14-8.93-.14-8.93Z" d="M98.48 386.23c2 .57 3.12 2.76 4.86 7.45a39.25 39.25 0 0 1 2.09 13.22c-.56 4.42-2.39 7.86-6.81 11.44l-2.11-3.87s4.33-3.82 4.77-8.24c.25-2.48-2.66-10.84-2.66-11.07s-.14-8.93-.14-8.93Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_33"
/> />
<path <path
className="fill-[#38226d]" className="fill-[#38226d]"
@ -141,36 +141,36 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
d="M94.7 484.44c-1.15-.25-3.26-1.67-6.12-2.36-1.09-.27-3-1.47-3.17-2.9-.25 1.64 2 3.1 3.17 3.39 2.86.69 5 2.12 6.12 2.36a2.9 2.9 0 0 0 2.64-.37 1.67 1.67 0 0 0 .37-.69 3.4 3.4 0 0 1-3.01.57Z" d="M94.7 484.44c-1.15-.25-3.26-1.67-6.12-2.36-1.09-.27-3-1.47-3.17-2.9-.25 1.64 2 3.1 3.17 3.39 2.86.69 5 2.12 6.12 2.36a2.9 2.9 0 0 0 2.64-.37 1.67 1.67 0 0 0 .37-.69 3.4 3.4 0 0 1-3.01.57Z"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_34"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M99.42 463.6a21.09 21.09 0 0 0-1.24-7.15s.82-9.79 1.45-16.22c.92-9.48 2.38-10.42 2-16.18-.38-5.34-3.42-8.8-4.21-12.42l-15.47 1.22s-1.35 4.39-3.09 13.13-1.86 19.13-1.72 32.89c0 3.55-.6 6.63-.4 15.47.14 5.94.48 13.22.48 13.22 2.26 1.94 3.83.51 3.83.51s4-17.6 4.55-21.64a12.73 12.73 0 0 0-.54-6.68s1.16-5.53 2-9.88c1-5.31 3.65-16.77 3.65-16.77s-.45 17.61.32 21.08.9 6.49 1.56 11.21c.79 5.7 1.15 14.05 1.15 14.05 2.36 1.5 3.41.35 3.41.35s2.27-11.2 2.27-16.19Z" d="M99.42 463.6a21.09 21.09 0 0 0-1.24-7.15s.82-9.79 1.45-16.22c.92-9.48 2.38-10.42 2-16.18-.38-5.34-3.42-8.8-4.21-12.42l-15.47 1.22s-1.35 4.39-3.09 13.13-1.86 19.13-1.72 32.89c0 3.55-.6 6.63-.4 15.47.14 5.94.48 13.22.48 13.22 2.26 1.94 3.83.51 3.83.51s4-17.6 4.55-21.64a12.73 12.73 0 0 0-.54-6.68s1.16-5.53 2-9.88c1-5.31 3.65-16.77 3.65-16.77s-.45 17.61.32 21.08.9 6.49 1.56 11.21c.79 5.7 1.15 14.05 1.15 14.05 2.36 1.5 3.41.35 3.41.35s2.27-11.2 2.27-16.19Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_34"
/> />
<path <path
className="fill-[#8c50ff]" className="fill-[#8c50ff]"
d="M97.38 411.63c1.62 1.66 4.3 6.71 4.3 14.35 0 8.6-1.25 6.86-1.85 16.33l-.6 9.47s-12.93 6.17-22.15.45c-.82-1.79.31-21.71 1.93-28.79s2.73-10.55 2.73-10.55 11.89 3.21 15.64-1.26Z" d="M97.38 411.63c1.62 1.66 4.3 6.71 4.3 14.35 0 8.6-1.25 6.86-1.85 16.33l-.6 9.47s-12.93 6.17-22.15.45c-.82-1.79.31-21.71 1.93-28.79s2.73-10.55 2.73-10.55 11.89 3.21 15.64-1.26Z"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_35"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M97.57 386.14c-3.3-.2-3.62.35-3.88-.82-.09-.42-.28-3.32-.28-3.32a10.61 10.61 0 0 0 .83-1.22 8.37 8.37 0 0 0 4-6.29 8.47 8.47 0 0 0-7.54-9.3c-4.37-.45-7.59 2.62-8.45 6.84-.46 2.08-.94 6.33.36 9.74.73 1.93 1.41 3.28 3.15 3.21a10.47 10.47 0 0 0 1.72-.3 16.18 16.18 0 0 1 .1 2.41c0 .85.1 1.27-1.56 2.29s2.5 3.1 5.21 2.9 5.75-1.84 6.59-3.59c.92-1.96 1.3-2.46-.25-2.55Z" d="M97.57 386.14c-3.3-.2-3.62.35-3.88-.82-.09-.42-.28-3.32-.28-3.32a10.61 10.61 0 0 0 .83-1.22 8.37 8.37 0 0 0 4-6.29 8.47 8.47 0 0 0-7.54-9.3c-4.37-.45-7.59 2.62-8.45 6.84-.46 2.08-.94 6.33.36 9.74.73 1.93 1.41 3.28 3.15 3.21a10.47 10.47 0 0 0 1.72-.3 16.18 16.18 0 0 1 .1 2.41c0 .85.1 1.27-1.56 2.29s2.5 3.1 5.21 2.9 5.75-1.84 6.59-3.59c.92-1.96 1.3-2.46-.25-2.55Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_35"
/> />
<path <path
className="fill-[#e4e4e7]" className="fill-[#e4e4e7]"
d="M100 400.75s3.73-.84 5.44-2.5c0 0-2.22-7-3.64-8.74S99 399 99 399Z" d="M100 400.75s3.73-.84 5.44-2.5c0 0-2.22-7-3.64-8.74S99 399 99 399Z"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_36"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#e4e4e7]" className="fill-[#e4e4e7]"
d="M94.29 386.07c.61.76.68 1.71-1.67 2.31a7.44 7.44 0 0 1-5.15-.4 34.52 34.52 0 0 0-5.32 2.82c-2 6.6-.39 7.45-1.23 12.18.33 2.38.72 8.52.82 9.91 4.49 3.14 13.6 2.07 15.64-1.26a27.55 27.55 0 0 1 .31-3.31c2.14-7.94 3.81-10 3.22-16-.34-3.42-.61-5.14-2.43-6.06a16.1 16.1 0 0 0-4.19-.19Z" d="M94.29 386.07c.61.76.68 1.71-1.67 2.31a7.44 7.44 0 0 1-5.15-.4 34.52 34.52 0 0 0-5.32 2.82c-2 6.6-.39 7.45-1.23 12.18.33 2.38.72 8.52.82 9.91 4.49 3.14 13.6 2.07 15.64-1.26a27.55 27.55 0 0 1 .31-3.31c2.14-7.94 3.81-10 3.22-16-.34-3.42-.61-5.14-2.43-6.06a16.1 16.1 0 0 0-4.19-.19Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_36"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_37"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M53.07 411.89a23.07 23.07 0 0 1 3 .86 10 10 0 0 0 2.93 1c1.1.17 2.82-.4 5.79-1.8a32 32 0 0 0 8.7-6.54 75.44 75.44 0 0 0 7.6-10.85c1.29-2.35 2.92-2.94 4.34-2.48 1.7.56 1.91 2.25 0 5.41a60.5 60.5 0 0 1-8.7 11.39 32.63 32.63 0 0 1-9.07 6.4 27.87 27.87 0 0 1-7.63 2.65c-.93.2-1.56.59-3.28 1.19a11.36 11.36 0 0 1-5.35.59c-1.49-.29-2-.63-2.06-1s.24-.63.94-.58a9.18 9.18 0 0 0 3.32-.08s-1.54-.05-2.45-.16a8.86 8.86 0 0 1-2.38-.6c-.73-.31-1-1.46-.26-1.42s1.24.26 2.6.4a9 9 0 0 0 2.16 0 16.91 16.91 0 0 1-2.65-.66 3.76 3.76 0 0 1-1.85-1.14c-.25-.29-.18-1 .69-.82a13.89 13.89 0 0 0 3.24.71c1.11 0 1.9-.09 1.76-.26s-.8-.15-1.64-.62-1.34-1.38-1-1.82.34-.11 1.25.23Z" d="M53.07 411.89a23.07 23.07 0 0 1 3 .86 10 10 0 0 0 2.93 1c1.1.17 2.82-.4 5.79-1.8a32 32 0 0 0 8.7-6.54 75.44 75.44 0 0 0 7.6-10.85c1.29-2.35 2.92-2.94 4.34-2.48 1.7.56 1.91 2.25 0 5.41a60.5 60.5 0 0 1-8.7 11.39 32.63 32.63 0 0 1-9.07 6.4 27.87 27.87 0 0 1-7.63 2.65c-.93.2-1.56.59-3.28 1.19a11.36 11.36 0 0 1-5.35.59c-1.49-.29-2-.63-2.06-1s.24-.63.94-.58a9.18 9.18 0 0 0 3.32-.08s-1.54-.05-2.45-.16a8.86 8.86 0 0 1-2.38-.6c-.73-.31-1-1.46-.26-1.42s1.24.26 2.6.4a9 9 0 0 0 2.16 0 16.91 16.91 0 0 1-2.65-.66 3.76 3.76 0 0 1-1.85-1.14c-.25-.29-.18-1 .69-.82a13.89 13.89 0 0 0 3.24.71c1.11 0 1.9-.09 1.76-.26s-.8-.15-1.64-.62-1.34-1.38-1-1.82.34-.11 1.25.23Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_37"
/> />
<path <path
className="fill-[#e4e4e7]" className="fill-[#e4e4e7]"
@ -206,32 +206,32 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
d="M24.23 449.73c.21 1.28 1.24 1.64 2.29 2.29 1.52.94 2.85 2.3 4.42 3.1s5.08 1.46 6.1-.4c-.55 2.51-4.65 1.73-6.21.94s-2.85-2.12-4.34-3.05c-1.07-.66-2.11-1-2.28-2.34a3.6 3.6 0 0 1 .02-.54Z" d="M24.23 449.73c.21 1.28 1.24 1.64 2.29 2.29 1.52.94 2.85 2.3 4.42 3.1s5.08 1.46 6.1-.4c-.55 2.51-4.65 1.73-6.21.94s-2.85-2.12-4.34-3.05c-1.07-.66-2.11-1-2.28-2.34a3.6 3.6 0 0 1 .02-.54Z"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_38"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#e26161]" className="fill-[#e26161]"
d="M11.87 394.54c.08 2.34.8 12.5 1.13 17.87s.4 12 .4 12-.93 3.91-1.53 8.52 0 19.13 0 19.13a3.67 3.67 0 0 0 4 0s2.48-11.63 3.62-16.87 1.35-8.26 1.94-11.5c.72-3.93 2.27-22.26 2.27-22.26h.62l1.66 20.69a23.16 23.16 0 0 0-1.05 4.7c-.45 3.37.48 20.33.48 20.33a4.1 4.1 0 0 0 4.28-.18s4.13-22.46 4.13-25.87c0-2.27-.38-32.07-.38-32.07Z" d="M11.87 394.54c.08 2.34.8 12.5 1.13 17.87s.4 12 .4 12-.93 3.91-1.53 8.52 0 19.13 0 19.13a3.67 3.67 0 0 0 4 0s2.48-11.63 3.62-16.87 1.35-8.26 1.94-11.5c.72-3.93 2.27-22.26 2.27-22.26h.62l1.66 20.69a23.16 23.16 0 0 0-1.05 4.7c-.45 3.37.48 20.33.48 20.33a4.1 4.1 0 0 0 4.28-.18s4.13-22.46 4.13-25.87c0-2.27-.38-32.07-.38-32.07Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_38"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_39"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M17.48 349.35s.26 5.1.15 5.55-2.34 1.94-3 2.48 3.53 4.62 5.56 5.62 8.46-1.52 8.57-4.68-1.1-5.81-2.08-5.95-9.2-3.02-9.2-3.02Z" d="M17.48 349.35s.26 5.1.15 5.55-2.34 1.94-3 2.48 3.53 4.62 5.56 5.62 8.46-1.52 8.57-4.68-1.1-5.81-2.08-5.95-9.2-3.02-9.2-3.02Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_39"
/> />
<path <path
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M31.44 358.81c.3-2.25-1.18-4.39.25-6.18 5 4.94 4.4 18 5.54 26.35 3.74 3.9 11.25 7.75 13.36 8.7.38.18.72.37.64.78a9.26 9.26 0 0 1-1.75 3.82c-5-2.14-8.55-3.37-14.76-9.27a13.59 13.59 0 0 1-1.57-2.07c-1.77-5.32-2.22-18.25-1.71-22.13Z" d="M31.44 358.81c.3-2.25-1.18-4.39.25-6.18 5 4.94 4.4 18 5.54 26.35 3.74 3.9 11.25 7.75 13.36 8.7.38.18.72.37.64.78a9.26 9.26 0 0 1-1.75 3.82c-5-2.14-8.55-3.37-14.76-9.27a13.59 13.59 0 0 1-1.57-2.07c-1.77-5.32-2.22-18.25-1.71-22.13Z"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_40"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#e4e4e7]" className="fill-[#e4e4e7]"
d="M16.91 355.78s3.7 5.51 8.86 6c1.63-1.6 1.77-7.31-.48-9.14 0 0 2.07-1.67 3.49-1.35a7.35 7.35 0 0 1 3.22 4.1 33.52 33.52 0 0 1 1.82 11.8c-.09 5.41 0 21.94 0 21.94s-.72 3.49-5.39 6.09-8.14 2.86-11.31 2.12c-2.61-.61-4.77-1.53-5.67-3.42.24-3.8 1.93-13.69 1.3-18.46s-1.64-8.32-1.9-12.18.5-3.78 2.44-5.15a29.62 29.62 0 0 1 3.62-2.35Z" d="M16.91 355.78s3.7 5.51 8.86 6c1.63-1.6 1.77-7.31-.48-9.14 0 0 2.07-1.67 3.49-1.35a7.35 7.35 0 0 1 3.22 4.1 33.52 33.52 0 0 1 1.82 11.8c-.09 5.41 0 21.94 0 21.94s-.72 3.49-5.39 6.09-8.14 2.86-11.31 2.12c-2.61-.61-4.77-1.53-5.67-3.42.24-3.8 1.93-13.69 1.3-18.46s-1.64-8.32-1.9-12.18.5-3.78 2.44-5.15a29.62 29.62 0 0 1 3.62-2.35Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_40"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_41"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M29 341.75c-.61-4-3.46-7-7.55-6.73a7.85 7.85 0 0 0-4 14.33 7.82 7.82 0 0 0 .63 1.73c1.44 1.69 5.81 2.73 7.06 2.55a3.74 3.74 0 0 0 3.28-2.63c1.36-3.14.93-7.3.58-9.25Z" d="M29 341.75c-.61-4-3.46-7-7.55-6.73a7.85 7.85 0 0 0-4 14.33 7.82 7.82 0 0 0 .63 1.73c1.44 1.69 5.81 2.73 7.06 2.55a3.74 3.74 0 0 0 3.28-2.63c1.36-3.14.93-7.3.58-9.25Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_41"
/> />
<path <path
className="fill-[#2b2a30]" className="fill-[#2b2a30]"
@ -292,10 +292,10 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
cy={438.88} cy={438.88}
rx={3.9} rx={3.9}
ry={6.75} ry={6.75}
transform="rotate(-30 319.598 438.896)"
style={{ style={{
opacity: 0.1, opacity: 0.1,
}} }}
transform="rotate(-30 319.598 438.896)"
/> />
<path <path
className="fill-[#b1b4c4]" className="fill-[#b1b4c4]"
@ -327,16 +327,16 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
ry={13.1} ry={13.1}
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_54"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M439.85 392.73c.24 2.11 2.76 11.21 2.22 14.32s-9.14 8.33-9.14 8.33l-1.19-5.33 4.76-4.67-1.29-8.83Z" d="M439.85 392.73c.24 2.11 2.76 11.21 2.22 14.32s-9.14 8.33-9.14 8.33l-1.19-5.33 4.76-4.67-1.29-8.83Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_54"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_55"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#e4e4e7]" className="fill-[#e4e4e7]"
d="M433.2 383.76a3.77 3.77 0 0 1 4.79 1.45c1.26 2.09 2.57 9.28 2.69 10.5 0 0-1.77 2.46-4.43 2.05Z" d="M433.2 383.76a3.77 3.77 0 0 1 4.79 1.45c1.26 2.09 2.57 9.28 2.69 10.5 0 0-1.77 2.46-4.43 2.05Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_55"
/> />
<path <path
className="fill-[#2b2a30]" className="fill-[#2b2a30]"
@ -355,34 +355,34 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
d="M431.85 486c-1.48-.31-4.2-2.14-7.87-3-1.41-.34-3.84-1.88-4.09-3.72-.32 2.11 2.52 4 4.09 4.36 3.67.89 6.39 2.72 7.87 3 1.1.23 2.52.38 3.4-.48a2.22 2.22 0 0 0 .48-.88 4.38 4.38 0 0 1-3.88.72Z" d="M431.85 486c-1.48-.31-4.2-2.14-7.87-3-1.41-.34-3.84-1.88-4.09-3.72-.32 2.11 2.52 4 4.09 4.36 3.67.89 6.39 2.72 7.87 3 1.1.23 2.52.38 3.4-.48a2.22 2.22 0 0 0 .48-.88 4.38 4.38 0 0 1-3.88.72Z"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_56"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#e26161]" className="fill-[#e26161]"
d="M435.32 460.15a44.45 44.45 0 0 0-1.67-8s.5-6.88.75-13.4c.28-7.4 2.85-13.91.82-19.81l-22.82 5.07s1.38 29.5 1.68 33.07a66.94 66.94 0 0 0 1 8.28c1 5.73 2.73 19.35 2.73 19.35a8.52 8.52 0 0 0 5.58.62s-.17-17.22-.33-21.35c-.19-5-.34-4.65-.34-4.65l.76-12.55.49-6.24s.63 4.58 1 8.67c.36 3.57 1.22 6.8 2.18 13.44.84 5.76 2.19 17.34 2.19 17.34 2.42 1.4 5.69.24 5.69.24s.97-15.08.29-20.08Z" d="M435.32 460.15a44.45 44.45 0 0 0-1.67-8s.5-6.88.75-13.4c.28-7.4 2.85-13.91.82-19.81l-22.82 5.07s1.38 29.5 1.68 33.07a66.94 66.94 0 0 0 1 8.28c1 5.73 2.73 19.35 2.73 19.35a8.52 8.52 0 0 0 5.58.62s-.17-17.22-.33-21.35c-.19-5-.34-4.65-.34-4.65l.76-12.55.49-6.24s.63 4.58 1 8.67c.36 3.57 1.22 6.8 2.18 13.44.84 5.76 2.19 17.34 2.19 17.34 2.42 1.4 5.69.24 5.69.24s.97-15.08.29-20.08Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_56"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_57"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M432 383.88c-1.74.09-3 .54-3.25 0a24.73 24.73 0 0 1-.4-2.71c.2-.51.38-1.05.38-1.05 2.18-1.34 2.87-3.7 3.16-6.45.49-4.7-2.05-8.81-6.75-9.3-4.41-.46-7.66 2.65-8.53 6.91-.46 2.1-1.39 6.07-.14 9.53.71 1.94 1.61 3.47 2.52 3.77a20.09 20.09 0 0 0 2.61-.23s.23 1.3.39 2.14.1 1.28-1.57 2.3 2.52 3.14 5.26 2.93 5.81-1.86 6.66-3.62c.93-1.95.84-4.27-.34-4.22Z" d="M432 383.88c-1.74.09-3 .54-3.25 0a24.73 24.73 0 0 1-.4-2.71c.2-.51.38-1.05.38-1.05 2.18-1.34 2.87-3.7 3.16-6.45.49-4.7-2.05-8.81-6.75-9.3-4.41-.46-7.66 2.65-8.53 6.91-.46 2.1-1.39 6.07-.14 9.53.71 1.94 1.61 3.47 2.52 3.77a20.09 20.09 0 0 0 2.61-.23s.23 1.3.39 2.14.1 1.28-1.57 2.3 2.52 3.14 5.26 2.93 5.81-1.86 6.66-3.62c.93-1.95.84-4.27-.34-4.22Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_57"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_58"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#e4e4e7]" className="fill-[#e4e4e7]"
d="M429.48 384.14c.48 1.2-.75 2.67-4 3.7s-3.56-.3-3.56-.3a66.49 66.49 0 0 0-6.35 3.27c-2 1.37-2.82 5.34-3.09 12-.3 7.74-.33 19.23-.07 21.21 0 0 3.66 3.65 8.35 3.27s12.57-4.53 14.48-7.74c-.05-6.79-.85-7.89.25-11.8 2.52-9 3.84-12.62 2.62-18.57-1-4.91-2.42-5.52-4.36-5.47a41.07 41.07 0 0 0-4.27.43Z" d="M429.48 384.14c.48 1.2-.75 2.67-4 3.7s-3.56-.3-3.56-.3a66.49 66.49 0 0 0-6.35 3.27c-2 1.37-2.82 5.34-3.09 12-.3 7.74-.33 19.23-.07 21.21 0 0 3.66 3.65 8.35 3.27s12.57-4.53 14.48-7.74c-.05-6.79-.85-7.89.25-11.8 2.52-9 3.84-12.62 2.62-18.57-1-4.91-2.42-5.52-4.36-5.47a41.07 41.07 0 0 0-4.27.43Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_58"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_59"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#ffcea9]" className="fill-[#ffcea9]"
d="M405.1 409.24c3.85 1 4.75-1.45 5.26-3.46 1.22-4.86 1.88-9.25 2.79-12 1.08-3.26 2.2-3.78 3.79-4.41 1.85-.75 3.45.82 2.83 5a82.82 82.82 0 0 1-3.52 14.13c-.48 1.52-1.55 4.21-3.16 5.51-2 1.6-5 1.53-9.5.41-1.92-.48-4-1.63-7.69-3.33-1-.46-1.74-.76-3.62-1.64a12.94 12.94 0 0 1-4.82-3.76c-1-1.43-1.11-2.1-.91-2.42s.7-.32 1.22.28a10.72 10.72 0 0 0 2.77 2.56s-1.22-1.26-1.87-2.07a10.55 10.55 0 0 1-1.46-2.38c-.35-.83.37-2 .91-1.37s.81 1.2 1.8 2.39a10.35 10.35 0 0 0 1.78 1.7 19.53 19.53 0 0 1-1.64-2.64 4.45 4.45 0 0 1-.6-2.39c0-.43.65-.95 1.22-.12a16.2 16.2 0 0 0 2.07 3.15c.88.91 1.62 1.44 1.64 1.18s-.53-.75-.84-1.81 0-2.18.59-2.31.47-.09.94.91a28.49 28.49 0 0 1 1.75 3.17 6.56 6.56 0 0 0 1.58 2.73c.95.96 3.04 2.07 6.69 2.99Z" d="M405.1 409.24c3.85 1 4.75-1.45 5.26-3.46 1.22-4.86 1.88-9.25 2.79-12 1.08-3.26 2.2-3.78 3.79-4.41 1.85-.75 3.45.82 2.83 5a82.82 82.82 0 0 1-3.52 14.13c-.48 1.52-1.55 4.21-3.16 5.51-2 1.6-5 1.53-9.5.41-1.92-.48-4-1.63-7.69-3.33-1-.46-1.74-.76-3.62-1.64a12.94 12.94 0 0 1-4.82-3.76c-1-1.43-1.11-2.1-.91-2.42s.7-.32 1.22.28a10.72 10.72 0 0 0 2.77 2.56s-1.22-1.26-1.87-2.07a10.55 10.55 0 0 1-1.46-2.38c-.35-.83.37-2 .91-1.37s.81 1.2 1.8 2.39a10.35 10.35 0 0 0 1.78 1.7 19.53 19.53 0 0 1-1.64-2.64 4.45 4.45 0 0 1-.6-2.39c0-.43.65-.95 1.22-.12a16.2 16.2 0 0 0 2.07 3.15c.88.91 1.62 1.44 1.64 1.18s-.53-.75-.84-1.81 0-2.18.59-2.31.47-.09.94.91a28.49 28.49 0 0 1 1.75 3.17 6.56 6.56 0 0 0 1.58 2.73c.95.96 3.04 2.07 6.69 2.99Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_59"
/> />
<path <path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_60"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
className="fill-[#e4e4e7]" className="fill-[#e4e4e7]"
d="M418.36 388.69c-2.57-.37-4.38.78-5.52 4.67s-1.92 7.09-1.92 7.09a7 7 0 0 0 4.3 2.86c3.07.74 4.18-.73 4.18-.73s.85-4.08 1.38-6.85.76-6.57-2.42-7.04Z" d="M418.36 388.69c-2.57-.37-4.38.78-5.52 4.67s-1.92 7.09-1.92 7.09a7 7 0 0 0 4.3 2.86c3.07.74 4.18-.73 4.18-.73s.85-4.08 1.38-6.85.76-6.57-2.42-7.04Z"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_60"
/> />
<path <path
className="fill-[#2b2a30]" className="fill-[#2b2a30]"
@ -394,10 +394,10 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
export const ASvg = (props: SVGProps<SVGSVGElement>) => ( export const ASvg = (props: SVGProps<SVGSVGElement>) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg"
width={80}
height={100} height={100}
viewBox="20 0 10 12" viewBox="20 0 10 12"
width={80}
xmlns="http://www.w3.org/2000/svg"
{...props} {...props}
> >
<title>{"\u0410"}</title> <title>{"\u0410"}</title>
@ -422,30 +422,30 @@ export const ASvg = (props: SVGProps<SVGSVGElement>) => (
export const LogoSvg = (props: SVGProps<SVGSVGElement>) => ( export const LogoSvg = (props: SVGProps<SVGSVGElement>) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg"
width={258.758}
height={49.796} height={49.796}
viewBox="0 0 68.463 13.175" viewBox="0 0 68.463 13.175"
width={258.758}
xmlns="http://www.w3.org/2000/svg"
{...props} {...props}
> >
<title>{"lloggi.it Info"}</title> <title>{"lloggi.it Info"}</title>
<defs> <defs>
<path id="a" d="M69.953 191.276h677.665v225.16H69.953z" /> <path d="M69.953 191.276h677.665v225.16H69.953z" id="a" />
<path id="b" d="M69.953 191.276h677.665v225.16H69.953z" /> <path d="M69.953 191.276h677.665v225.16H69.953z" id="b" />
</defs> </defs>
<text <text
xmlSpace="preserve"
className="font-sans" className="font-sans"
style={{ style={{
fontSize: 40, fontSize: 40,
lineHeight: 5,
textAlign: "center",
letterSpacing: 1, letterSpacing: 1,
wordSpacing: 0, lineHeight: 5,
whiteSpace: "pre",
strokeWidth: 2, strokeWidth: 2,
textAlign: "center",
whiteSpace: "pre",
wordSpacing: 0,
}} }}
transform="matrix(.26458 0 0 .26458 -98.855 -69.754)" transform="matrix(.26458 0 0 .26458 -98.855 -69.754)"
xmlSpace="preserve"
> >
<tspan x={371.603} y={303.296}> <tspan x={371.603} y={303.296}>
<tspan <tspan
@ -458,18 +458,18 @@ export const LogoSvg = (props: SVGProps<SVGSVGElement>) => (
</tspan> </tspan>
</text> </text>
<text <text
xmlSpace="preserve"
className="font-sans" className="font-sans"
style={{ style={{
fontSize: 40, fontSize: 40,
lineHeight: 5,
textAlign: "center",
letterSpacing: 1, letterSpacing: 1,
wordSpacing: 0, lineHeight: 5,
whiteSpace: "pre",
strokeWidth: 2, strokeWidth: 2,
textAlign: "center",
whiteSpace: "pre",
wordSpacing: 0,
}} }}
transform="matrix(.26458 0 0 .26458 -58.739 -69.754)" transform="matrix(.26458 0 0 .26458 -58.739 -69.754)"
xmlSpace="preserve"
> >
<tspan x={336.717} y={303.296}> <tspan x={336.717} y={303.296}>
<tspan <tspan
@ -502,11 +502,11 @@ export const LogoSvg = (props: SVGProps<SVGSVGElement>) => (
export const EngFlag = (props: SVGProps<SVGSVGElement>) => ( export const EngFlag = (props: SVGProps<SVGSVGElement>) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
width={800}
height={800} height={800}
viewBox="0 0 512 512" viewBox="0 0 512 512"
width={800}
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
{...props} {...props}
> >
<title>EnglishFlag</title> <title>EnglishFlag</title>
@ -539,9 +539,9 @@ export const EngFlag = (props: SVGProps<SVGSVGElement>) => (
export const ItaFlag = (props: SVGProps<SVGSVGElement>) => ( export const ItaFlag = (props: SVGProps<SVGSVGElement>) => (
<svg <svg
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve" xmlSpace="preserve"
viewBox="0 0 512 512"
{...props} {...props}
> >
<title>ItalianFlag</title> <title>ItalianFlag</title>
@ -568,16 +568,16 @@ export const ItaFlag = (props: SVGProps<SVGSVGElement>) => (
export const MissingFile = ({ className }: { className?: string }) => ( export const MissingFile = ({ className }: { className?: string }) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg" className={className}
viewBox="0 0 512 512"
style={{ style={{
clipRule: "evenodd",
fillRule: "evenodd",
imageRendering: "auto",
shapeRendering: "geometricPrecision", shapeRendering: "geometricPrecision",
textRendering: "geometricPrecision", textRendering: "geometricPrecision",
imageRendering: "auto",
fillRule: "evenodd",
clipRule: "evenodd",
}} }}
className={className} viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
> >
<title>MissingFile</title> <title>MissingFile</title>
<path <path
@ -651,11 +651,11 @@ export const MissingFile = ({ className }: { className?: string }) => (
export const WhatsAppIcon = ({ className }: { className?: string }) => ( export const WhatsAppIcon = ({ className }: { className?: string }) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg" className={className}
width={800}
height={800} height={800}
viewBox="0 0 24 24" viewBox="0 0 24 24"
className={className} width={800}
xmlns="http://www.w3.org/2000/svg"
> >
<title>{"WhatsApp icon"}</title> <title>{"WhatsApp icon"}</title>
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51a12.8 12.8 0 0 0-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413Z" /> <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51a12.8 12.8 0 0 0-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413Z" />

View file

@ -15,55 +15,53 @@ export const BanlistTable = (props: {
const tabledata = data.map((row) => { const tabledata = data.map((row) => {
return { return {
actions: "Azioni",
id: row.id, id: row.id,
type: row.type, type: row.type,
value: row.value, value: row.value,
actions: "Azioni",
}; };
}); });
const columns_titles = { const columns_titles = {
actions: "Azioni",
id: "Codice", id: "Codice",
type: "Tipo", type: "Tipo",
value: "Valore", value: "Valore",
actions: "Azioni",
}; };
type Column = (typeof tabledata)[number]; type Column = (typeof tabledata)[number];
const columns: ColumnDef<Column>[] = [ const columns: ColumnDef<Column>[] = [
{ {
accessorKey: "id",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.id} /> <DataTableColumnHeader column={column} title={columns_titles.id} />
), ),
accessorKey: "id",
}, },
{ {
accessorKey: "type",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.type} /> <DataTableColumnHeader column={column} title={columns_titles.type} />
), ),
accessorKey: "type",
}, },
{ {
accessorKey: "value",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.value} /> <DataTableColumnHeader column={column} title={columns_titles.value} />
), ),
accessorKey: "value",
}, },
{ {
header: ({ column }) => ( accessorKey: "actions",
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
cell: ({ row }) => { cell: ({ row }) => {
const data = row.original; const data = row.original;
return ( return (
<Button <Button
aria-label="Edit Banlist Entry" aria-label="Edit Banlist Entry"
variant="outline"
onClick={() => { onClick={() => {
setEditData(data); setEditData(data);
setOpenEdit(true); setOpenEdit(true);
}} }}
variant="outline"
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Cog /> Modifica <Cog /> Modifica
@ -71,9 +69,11 @@ export const BanlistTable = (props: {
</Button> </Button>
); );
}, },
accessorKey: "actions",
enableHiding: false, enableHiding: false,
enableSorting: false, enableSorting: false,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
}, },
]; ];
@ -85,11 +85,11 @@ export const BanlistTable = (props: {
<div className="mx-auto w-full px-2 py-10"> <div className="mx-auto w-full px-2 py-10">
<DataTable <DataTable
columns={columns} columns={columns}
columns_titles={columns_titles}
data={tabledata} data={tabledata}
defaultSort={[{ desc: true, id: "id" }]}
pinnedFiltri={undefined} pinnedFiltri={undefined}
searchColumn={searchFiltro} searchColumn={searchFiltro}
columns_titles={columns_titles}
defaultSort={[{ desc: true, id: "id" }]}
/> />
</div> </div>
); );

View file

@ -14,183 +14,180 @@ export const BannersTable = (props: {
const tabledata = data.map((row) => { const tabledata = data.map((row) => {
return { return {
idbanner: row.idbanner, actions: "Azioni",
titolo: row.titolo, color: row.color,
testo: row.testo,
is_unskippable: row.is_unskippable,
has_cta: row.has_cta,
cta_href: row.cta_href, cta_href: row.cta_href,
cta_icon: row.cta_icon, cta_icon: row.cta_icon,
cta_text: row.cta_text, cta_text: row.cta_text,
color: row.color, has_cta: row.has_cta,
is_active: row.is_active,
show_public: row.show_public,
show_private: row.show_private,
hide_duration: row.hide_duration, hide_duration: row.hide_duration,
idbanner: row.idbanner,
actions: "Azioni", is_active: row.is_active,
is_unskippable: row.is_unskippable,
show_private: row.show_private,
show_public: row.show_public,
testo: row.testo,
titolo: row.titolo,
}; };
}); });
const columns_titles = { const columns_titles = {
idbanner: "Codice", actions: "Azioni",
titolo: "Titolo", color: "Colore",
testo: "Testo",
is_unskippable: "Non saltabile",
has_cta: "Ha link",
hide_duration: "Durata",
cta_href: "Indirizzo", cta_href: "Indirizzo",
cta_icon: "Icona", cta_icon: "Icona",
cta_text: "Testo", cta_text: "Testo",
color: "Colore", has_cta: "Ha link",
hide_duration: "Durata",
idbanner: "Codice",
is_active: "Attivo", is_active: "Attivo",
actions: "Azioni", is_unskippable: "Non saltabile",
show_public: "Pubblico",
show_private: "A. Riservata", show_private: "A. Riservata",
show_public: "Pubblico",
testo: "Testo",
titolo: "Titolo",
}; };
type Column = (typeof tabledata)[number]; type Column = (typeof tabledata)[number];
const columns: ColumnDef<Column>[] = [ const columns: ColumnDef<Column>[] = [
{ {
accessorKey: "idbanner",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.idbanner} title={columns_titles.idbanner}
/> />
), ),
accessorKey: "idbanner",
}, },
{ {
accessorKey: "titolo",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.titolo} /> <DataTableColumnHeader column={column} title={columns_titles.titolo} />
), ),
accessorKey: "titolo",
}, },
{ {
accessorKey: "testo",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.testo} /> <DataTableColumnHeader column={column} title={columns_titles.testo} />
), ),
accessorKey: "testo",
}, },
{ {
accessorKey: "is_unskippable",
cell: ({ row }) => {
const data = row.original;
return data.is_unskippable ? "Vero" : "Falso";
},
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.is_unskippable} title={columns_titles.is_unskippable}
/> />
), ),
accessorKey: "is_unskippable",
cell: ({ row }) => {
const data = row.original;
return data.is_unskippable ? "Vero" : "Falso";
},
}, },
{ {
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.has_cta} />
),
accessorKey: "has_cta", accessorKey: "has_cta",
cell: ({ row }) => { cell: ({ row }) => {
const data = row.original; const data = row.original;
return data.has_cta ? "Vero" : "Falso"; return data.has_cta ? "Vero" : "Falso";
}, },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.has_cta} />
),
}, },
{ {
accessorKey: "hide_duration",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.hide_duration} title={columns_titles.hide_duration}
/> />
), ),
accessorKey: "hide_duration",
}, },
{ {
accessorKey: "cta_href",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.cta_href} title={columns_titles.cta_href}
/> />
), ),
accessorKey: "cta_href",
}, },
{ {
accessorKey: "cta_icon",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.cta_icon} title={columns_titles.cta_icon}
/> />
), ),
accessorKey: "cta_icon",
}, },
{ {
accessorKey: "cta_text",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.cta_text} title={columns_titles.cta_text}
/> />
), ),
accessorKey: "cta_text",
}, },
{ {
accessorKey: "color",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.color} /> <DataTableColumnHeader column={column} title={columns_titles.color} />
), ),
accessorKey: "color",
}, },
{ {
accessorKey: "is_active",
cell: ({ row }) => {
const data = row.original;
return data.is_active ? "Vero" : "Falso";
},
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.is_active} title={columns_titles.is_active}
/> />
), ),
accessorKey: "is_active",
cell: ({ row }) => {
const data = row.original;
return data.is_active ? "Vero" : "Falso";
},
}, },
{ {
accessorKey: "show_public",
cell: ({ row }) => {
const data = row.original;
return data.show_public ? "Vero" : "Falso";
},
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.show_public} title={columns_titles.show_public}
/> />
), ),
accessorKey: "show_public",
cell: ({ row }) => {
const data = row.original;
return data.show_public ? "Vero" : "Falso";
},
}, },
{ {
accessorKey: "show_private",
cell: ({ row }) => {
const data = row.original;
return data.show_private ? "Vero" : "Falso";
},
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.show_private} title={columns_titles.show_private}
/> />
), ),
accessorKey: "show_private",
cell: ({ row }) => {
const data = row.original;
return data.show_private ? "Vero" : "Falso";
},
}, },
{ {
header: ({ column }) => ( accessorKey: "actions",
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
cell: ({ row }) => { cell: ({ row }) => {
const data = row.original; const data = row.original;
return ( return (
<Button <Button
aria-label="Edit Banner" aria-label="Edit Banner"
variant="outline"
onClick={() => { onClick={() => {
setEditData(data); setEditData(data);
setOpenEdit(true); setOpenEdit(true);
}} }}
variant="outline"
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Cog /> Modifica <Cog /> Modifica
@ -198,9 +195,11 @@ export const BannersTable = (props: {
</Button> </Button>
); );
}, },
accessorKey: "actions",
enableHiding: false, enableHiding: false,
enableSorting: false, enableSorting: false,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
}, },
]; ];
@ -212,28 +211,28 @@ export const BannersTable = (props: {
<div className="mx-auto w-full px-2 py-10"> <div className="mx-auto w-full px-2 py-10">
<DataTable <DataTable
columns={columns} columns={columns}
data={tabledata}
pinnedFiltri={undefined}
searchColumn={searchFiltro}
columns_titles={columns_titles} columns_titles={columns_titles}
defaultSort={[{ desc: true, id: "idbanner" }]} data={tabledata}
defaultColumnVisibility={{ defaultColumnVisibility={{
idbanner: true, actions: true,
color: false,
titolo: true,
testo: true,
is_unskippable: true,
has_cta: true,
hide_duration: false,
cta_href: false, cta_href: false,
cta_icon: false, cta_icon: false,
cta_text: false, cta_text: false,
color: false, has_cta: true,
hide_duration: false,
idbanner: true,
is_active: true, is_active: true,
actions: true, is_unskippable: true,
show_public: true,
show_private: true, show_private: true,
show_public: true,
testo: true,
titolo: true,
}} }}
defaultSort={[{ desc: true, id: "idbanner" }]}
pinnedFiltri={undefined}
searchColumn={searchFiltro}
/> />
</div> </div>
); );

View file

@ -15,69 +15,66 @@ export const EtichetteTable = (props: {
const tabledata = data.map((row) => { const tabledata = data.map((row) => {
return { return {
actions: "Azioni",
colore: row.color_hex,
id: row.id_etichetta, id: row.id_etichetta,
title: row.title, title: row.title,
colore: row.color_hex,
actions: "Azioni",
}; };
}); });
const columns_titles = { const columns_titles = {
id: "Id",
etichetta: "Etichetta",
actions: "Azioni", actions: "Azioni",
etichetta: "Etichetta",
id: "Id",
}; };
type Column = (typeof tabledata)[number]; type Column = (typeof tabledata)[number];
const columns: ColumnDef<Column>[] = [ const columns: ColumnDef<Column>[] = [
{ {
accessorKey: "id",
enableHiding: false,
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.id} /> <DataTableColumnHeader column={column} title={columns_titles.id} />
), ),
accessorKey: "id",
enableHiding: false,
}, },
{ {
accessorKey: "title",
cell: ({ row }) => {
const data = row.original;
return (
<Etichetta
className="text-base"
color_hex={data.colore}
title={data.title}
/>
);
},
enableHiding: false,
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.etichetta} title={columns_titles.etichetta}
/> />
), ),
accessorKey: "title",
cell: ({ row }) => {
const data = row.original;
return (
<Etichetta
title={data.title}
color_hex={data.colore}
className="text-base"
/>
);
},
enableHiding: false,
}, },
{ {
header: ({ column }) => ( accessorKey: "actions",
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
cell: ({ row }) => { cell: ({ row }) => {
const data = row.original; const data = row.original;
return ( return (
<Button <Button
aria-label="Modifica Etichetta" aria-label="Modifica Etichetta"
variant="outline"
onClick={() => { onClick={() => {
setEditData({ setEditData({
color_hex: data.colore,
id_etichetta: data.id, id_etichetta: data.id,
title: data.title, title: data.title,
color_hex: data.colore,
}); });
setOpenEdit(true); setOpenEdit(true);
}} }}
variant="outline"
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Cog /> Modifica <Cog /> Modifica
@ -85,9 +82,11 @@ export const EtichetteTable = (props: {
</Button> </Button>
); );
}, },
accessorKey: "actions",
enableHiding: false, enableHiding: false,
enableSorting: false, enableSorting: false,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
}, },
]; ];
@ -99,11 +98,11 @@ export const EtichetteTable = (props: {
<div className="mx-auto w-full px-2 py-10"> <div className="mx-auto w-full px-2 py-10">
<DataTable <DataTable
columns={columns} columns={columns}
columns_titles={columns_titles}
data={tabledata} data={tabledata}
defaultSort={[{ desc: true, id: "id" }]}
pinnedFiltri={undefined} pinnedFiltri={undefined}
searchColumn={searchFiltro} searchColumn={searchFiltro}
columns_titles={columns_titles}
defaultSort={[{ desc: true, id: "id" }]}
/> />
</div> </div>
); );

View file

@ -15,50 +15,46 @@ export const FlagsTable = (props: {
const tabledata = data.map((row) => { const tabledata = data.map((row) => {
return { return {
actions: "Azioni",
id: row.id, id: row.id,
value: row.value, value: row.value,
actions: "Azioni",
}; };
}); });
const columns_titles = { const columns_titles = {
actions: "Azioni",
id: "Codice", id: "Codice",
value: "Valore", value: "Valore",
actions: "Azioni",
}; };
type Column = (typeof tabledata)[number]; type Column = (typeof tabledata)[number];
const columns: ColumnDef<Column>[] = [ const columns: ColumnDef<Column>[] = [
{ {
accessorKey: "id",
enableHiding: false,
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.id} /> <DataTableColumnHeader column={column} title={columns_titles.id} />
), ),
accessorKey: "id",
enableHiding: false,
}, },
{ {
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.value} />
),
accessorKey: "value", accessorKey: "value",
cell: ({ row }) => { cell: ({ row }) => {
const data = row.original; const data = row.original;
return data.value === null ? "null" : data.value; return data.value === null ? "null" : data.value;
}, },
enableHiding: false, enableHiding: false,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.value} />
),
}, },
{ {
header: ({ column }) => ( accessorKey: "actions",
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
cell: ({ row }) => { cell: ({ row }) => {
const data = row.original; const data = row.original;
return ( return (
<Button <Button
aria-label="Modifica Flag" aria-label="Modifica Flag"
variant="outline"
onClick={() => { onClick={() => {
setEditData({ setEditData({
id: data.id, id: data.id,
@ -66,6 +62,7 @@ export const FlagsTable = (props: {
}); });
setOpenEdit(true); setOpenEdit(true);
}} }}
variant="outline"
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Cog /> Modifica <Cog /> Modifica
@ -73,9 +70,11 @@ export const FlagsTable = (props: {
</Button> </Button>
); );
}, },
accessorKey: "actions",
enableHiding: false, enableHiding: false,
enableSorting: false, enableSorting: false,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
}, },
]; ];
@ -87,11 +86,11 @@ export const FlagsTable = (props: {
<div className="mx-auto w-full px-2 py-10"> <div className="mx-auto w-full px-2 py-10">
<DataTable <DataTable
columns={columns} columns={columns}
columns_titles={columns_titles}
data={tabledata} data={tabledata}
defaultSort={[{ desc: true, id: "id" }]}
pinnedFiltri={undefined} pinnedFiltri={undefined}
searchColumn={searchFiltro} searchColumn={searchFiltro}
columns_titles={columns_titles}
defaultSort={[{ desc: true, id: "id" }]}
/> />
</div> </div>
); );

View file

@ -25,38 +25,38 @@ type PaymentsTableProps = {
export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => { export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
const tabledata = data.map((row) => { const tabledata = data.map((row) => {
return { return {
id: row.ordine_id, actions: "Azioni",
username: row.username,
userId: row.userid,
tipo: row.type,
packid: row.packid,
isActive: row.isActive,
created_at: row.created_at, created_at: row.created_at,
id: row.ordine_id,
isActive: row.isActive,
isPaid: row.pagamenti.some( isPaid: row.pagamenti.some(
(p) => p.paymentstatus === PaymentStatusEnum.success, (p) => p.paymentstatus === PaymentStatusEnum.success,
), ),
actions: "Azioni", packid: row.packid,
tipo: row.type,
userId: row.userid,
username: row.username,
}; };
}); });
const columns_titles = { const columns_titles = {
username: "Utente",
created_at: "Creato il",
tipo: "Tipo",
packid: "ID Pacchetto",
isPaid: "Pagato",
actions: "Azioni", actions: "Azioni",
created_at: "Creato il",
isPaid: "Pagato",
packid: "ID Pacchetto",
tipo: "Tipo",
username: "Utente",
}; };
const isPaidOptions = [ const isPaidOptions = [
{ value: "true", label: "Pagato" }, { label: "Pagato", value: "true" },
{ value: "false", label: "Non pagato" }, { label: "Non pagato", value: "false" },
]; ];
const type_options = [ const type_options = [
{ value: OrderTypeEnum.Acconto, label: "Acconto" }, { label: "Acconto", value: OrderTypeEnum.Acconto },
{ value: OrderTypeEnum.Saldo, label: "Saldo" }, { label: "Saldo", value: OrderTypeEnum.Saldo },
{ value: OrderTypeEnum.Consulenza, label: "Consulenza" }, { label: "Consulenza", value: OrderTypeEnum.Consulenza },
{ value: OrderTypeEnum.Altro, label: "Altro" }, { label: "Altro", value: OrderTypeEnum.Altro },
]; ];
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
@ -68,14 +68,7 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
const columns: ColumnDef<Column>[] = [ const columns: ColumnDef<Column>[] = [
{ {
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.username}
/>
),
accessorKey: "username", accessorKey: "username",
enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
if (isAdmin) { if (isAdmin) {
return ( return (
@ -89,26 +82,30 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
} }
return <span>{row.original.username}</span>; return <span>{row.original.username}</span>;
}, },
enableHiding: false,
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.username}
/>
),
}, },
{ {
accessorKey: "created_at",
cell: ({ row }) => {
const date = row.getValue("created_at");
return new Date(date as Date).toLocaleDateString();
},
enableHiding: false,
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.created_at} title={columns_titles.created_at}
/> />
), ),
accessorKey: "created_at",
cell: ({ row }) => {
const date = row.getValue("created_at");
return new Date(date as Date).toLocaleDateString();
},
sortingFn: "datetime", sortingFn: "datetime",
enableHiding: false,
}, },
{ {
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.tipo} />
),
accessorKey: "tipo", accessorKey: "tipo",
cell: ({ row }) => { cell: ({ row }) => {
return row.original.tipo.toString(); return row.original.tipo.toString();
@ -116,23 +113,19 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
filterFn: (row, id, value) => { filterFn: (row, id, value) => {
return value.includes(row.getValue(id)); return value.includes(row.getValue(id));
}, },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.tipo} />
),
}, },
{ {
accessorKey: "packid",
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.packid} /> <DataTableColumnHeader column={column} title={columns_titles.packid} />
), ),
accessorKey: "packid",
}, },
{ {
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.isPaid} />
),
accessorKey: "isPaid", accessorKey: "isPaid",
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
},
cell: ({ row }) => { cell: ({ row }) => {
const isPaid = row.original.isPaid; const isPaid = row.original.isPaid;
const isActive = row.original.isActive; const isActive = row.original.isActive;
@ -160,28 +153,35 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
); );
} }
}, },
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
},
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.isPaid} />
),
}, },
{ {
id: "actions",
enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
return ( return (
<Button <Button
aria-label="Apri Dettagli Ordine" aria-label="Apri Dettagli Ordine"
variant="outline" className="flex items-center gap-2"
size="sm"
onClick={() => { onClick={() => {
setSelectedOrdine(row.original.id); setSelectedOrdine(row.original.id);
setModalOpen(true); setModalOpen(true);
}} }}
className="flex items-center gap-2" size="sm"
variant="outline"
> >
<ExternalLink className="size-6" /> <ExternalLink className="size-6" />
Apri Dettagli Apri Dettagli
</Button> </Button>
); );
}, },
enableHiding: false,
id: "actions",
}, },
]; ];
@ -199,14 +199,14 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
const pinnedFiltri: PinnedFiltro[] = [ const pinnedFiltri: PinnedFiltro[] = [
{ {
columnName: "isPaid", columnName: "isPaid",
title: "Stato Pagamento",
options: isPaidOptions, options: isPaidOptions,
title: "Stato Pagamento",
}, },
{ {
columnName: "tipo", columnName: "tipo",
title: "Tipo Ordine",
options: type_options, options: type_options,
title: "Tipo Ordine",
}, },
]; ];
@ -214,27 +214,27 @@ export const OrdiniTable = ({ data, isAdmin, userId }: PaymentsTableProps) => {
<div className="mx-auto w-full py-10"> <div className="mx-auto w-full py-10">
<DataTable <DataTable
columns={columns} columns={columns}
columns_titles={columns_titles}
data={tabledata} data={tabledata}
defaultColumnVisibility={{
actions: true,
amount: true,
packid: true,
paid_at: true,
status: true,
tipo: true,
username: isAdmin && !userId,
}}
defaultSort={[{ desc: true, id: "created_at" }]}
pinnedFiltri={pinnedFiltri} pinnedFiltri={pinnedFiltri}
searchColumn={searchFiltro} searchColumn={searchFiltro}
columns_titles={columns_titles}
defaultSort={[{ desc: true, id: "created_at" }]}
defaultColumnVisibility={{
username: isAdmin && !userId,
tipo: true,
packid: true,
status: true,
amount: true,
paid_at: true,
actions: true,
}}
/> />
{selectedOrdine && ( {selectedOrdine && (
<OrdiniModal <OrdiniModal
ordineId={selectedOrdine}
isAdmin={isAdmin} isAdmin={isAdmin}
setModalOpen={setModalOpen}
open={modalOpen} open={modalOpen}
ordineId={selectedOrdine}
setModalOpen={setModalOpen}
userId={userId} userId={userId}
/> />
)} )}

View file

@ -64,21 +64,21 @@ export const PaymentsTable = ({
}: PaymentsTableProps) => { }: PaymentsTableProps) => {
const tabledata = data.map((row) => { const tabledata = data.map((row) => {
return { return {
id: row.id, actions: "Azioni",
status: StatusMapping(row.paymentstatus),
amount: row.amount_cent, amount: row.amount_cent,
id: row.id,
paid_at: row.paid_at, paid_at: row.paid_at,
raw_status: row.paymentstatus, raw_status: row.paymentstatus,
actions: "Azioni", status: StatusMapping(row.paymentstatus),
}; };
}); });
const columns_titles = { const columns_titles = {
username: "Utente", actions: "Azioni",
status: "Stato",
amount: "Importo", amount: "Importo",
paid_at: "Pagato il", paid_at: "Pagato il",
actions: "Azioni", status: "Stato",
username: "Utente",
}; };
const status_options = [ const status_options = [
{ label: "Inutilizzato", value: "Inutilizzato" }, { label: "Inutilizzato", value: "Inutilizzato" },
@ -120,45 +120,43 @@ export const PaymentsTable = ({
const columns: ColumnDef<Column>[] = [ const columns: ColumnDef<Column>[] = [
{ {
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.paid_at} />
),
accessorKey: "paid_at", accessorKey: "paid_at",
cell: ({ row }) => { cell: ({ row }) => {
const date = row.getValue("paid_at"); const date = row.getValue("paid_at");
return new Date(date as Date).toLocaleDateString(); return new Date(date as Date).toLocaleDateString();
}, },
sortingFn: "datetime",
enableHiding: false, enableHiding: false,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.paid_at} />
),
sortingFn: "datetime",
}, },
{ {
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.status} />
),
accessorKey: "status", accessorKey: "status",
filterFn: (row, id, value) => { filterFn: (row, id, value) => {
return value.includes(row.getValue(id)); return value.includes(row.getValue(id));
}, },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.status} />
),
}, },
{ {
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.amount} />
),
accessorKey: "amount", accessorKey: "amount",
cell: ({ row }) => { cell: ({ row }) => {
return <span>{formatCurrency(row.original.amount / 100)}</span>; return <span>{formatCurrency(row.original.amount / 100)}</span>;
}, },
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.amount} />
),
}, },
{ {
id: "actions",
enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const data = row.original; const data = row.original;
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="size-8 p-0"> <Button className="size-8 p-0" variant="ghost">
<span className="sr-only">Open menu</span> <span className="sr-only">Open menu</span>
<EllipsisVertical /> <EllipsisVertical />
</Button> </Button>
@ -173,13 +171,13 @@ export const PaymentsTable = ({
<DropdownMenuPortal> <DropdownMenuPortal>
<DropdownMenuSubContent> <DropdownMenuSubContent>
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
value={StatusMapping(data.raw_status)}
onValueChange={(v) => { onValueChange={(v) => {
update({ update({
pagamentoId: data.id,
data: { paymentstatus: EnumFromString(v) }, data: { paymentstatus: EnumFromString(v) },
pagamentoId: data.id,
}); });
}} }}
value={StatusMapping(data.raw_status)}
> >
<DropdownMenuRadioItem value="Inutilizzato"> <DropdownMenuRadioItem value="Inutilizzato">
Inutilizzato Inutilizzato
@ -214,6 +212,8 @@ export const PaymentsTable = ({
</DropdownMenu> </DropdownMenu>
); );
}, },
enableHiding: false,
id: "actions",
}, },
]; ];
@ -227,9 +227,9 @@ export const PaymentsTable = ({
const pinnedFiltri: PinnedFiltro[] = [ const pinnedFiltri: PinnedFiltro[] = [
{ {
title: "Status",
columnName: "status", columnName: "status",
options: status_options, options: status_options,
title: "Status",
}, },
]; ];
@ -237,19 +237,19 @@ export const PaymentsTable = ({
<div className="w-full py-6"> <div className="w-full py-6">
<DataTable <DataTable
columns={columns} columns={columns}
columns_titles={columns_titles}
data={tabledata} data={tabledata}
defaultColumnVisibility={{
actions: true,
amount: true,
packid: true,
paid_at: true,
status: true,
username: isAdmin && !isUserView,
}}
defaultSort={[{ desc: true, id: "paid_at" }]}
pinnedFiltri={pinnedFiltri} pinnedFiltri={pinnedFiltri}
searchColumn={searchFiltro} searchColumn={searchFiltro}
columns_titles={columns_titles}
defaultSort={[{ desc: true, id: "paid_at" }]}
defaultColumnVisibility={{
username: isAdmin && !isUserView,
packid: true,
status: true,
amount: true,
paid_at: true,
actions: true,
}}
/> />
</div> </div>
); );

View file

@ -22,26 +22,26 @@ export const PrezzarioTable = ({
}) => { }) => {
const tabledata = data.map((row) => { const tabledata = data.map((row) => {
return { return {
id: row.idprezziario, actions: "Azioni",
nome_it: row.nome_it,
desc_it: row.desc_it,
nome_en: row.nome_en,
desc_en: row.desc_en,
categoria: [ categoria: [
row.isAcconto && "acconto", row.isAcconto && "acconto",
row.isSaldo && "saldo", row.isSaldo && "saldo",
row.isConsulenza && "consulenza", row.isConsulenza && "consulenza",
].filter(Boolean), ].filter(Boolean),
isTransitorio: row.isTransitorio, desc_en: row.desc_en,
isStabile: row.isStabile, desc_it: row.desc_it,
prezzo_cent: row.prezzo_cent, id: row.idprezziario,
testo_condizioni: row.testo_condizioni,
isAcconto: row.isAcconto, isAcconto: row.isAcconto,
isSaldo: row.isSaldo,
isConsulenza: row.isConsulenza,
isActive: row.isActive, isActive: row.isActive,
isConsulenza: row.isConsulenza,
isSaldo: row.isSaldo,
isStabile: row.isStabile,
isTransitorio: row.isTransitorio,
nome_en: row.nome_en,
nome_it: row.nome_it,
prezzo_cent: row.prezzo_cent,
sconto: row.sconto, sconto: row.sconto,
actions: "Azioni", testo_condizioni: row.testo_condizioni,
}; };
}); });
@ -52,29 +52,27 @@ export const PrezzarioTable = ({
]; ];
const columns_titles = { const columns_titles = {
actions: "Azioni",
categoria: "Categoria",
id: "Codice", id: "Codice",
nome: "Nome", nome: "Nome",
categoria: "Categoria",
tipologia: "Tipo",
prezzo: "Prezzo", prezzo: "Prezzo",
sconto: "Sconto", sconto: "Sconto",
actions: "Azioni", tipologia: "Tipo",
}; };
type Column = (typeof tabledata)[number]; type Column = (typeof tabledata)[number];
const includesAll = (arr: string[], values: string[]) => const includesAll = (arr: string[], values: string[]) =>
values.every((v) => arr.includes(v)); values.every((v) => arr.includes(v));
const columns: ColumnDef<Column>[] = [ const columns: ColumnDef<Column>[] = [
{ {
accessorKey: "id",
enableHiding: false,
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.id} /> <DataTableColumnHeader column={column} title={columns_titles.id} />
), ),
accessorKey: "id",
enableHiding: false,
}, },
{ {
header: ({ column }) => ( accessorKey: "nome",
<DataTableColumnHeader column={column} title={columns_titles.nome} />
),
cell: ({ row }) => { cell: ({ row }) => {
return ( return (
<div className={cn(!row.original.isActive && "line-through")}> <div className={cn(!row.original.isActive && "line-through")}>
@ -82,33 +80,25 @@ export const PrezzarioTable = ({
</div> </div>
); );
}, },
accessorKey: "nome", header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.nome} />
),
}, },
{ {
accessorKey: "categoria",
filterFn: (row, id, value) => {
return includesAll(row.getValue(id), value as string[]);
},
header: ({ column }) => ( header: ({ column }) => (
<DataTableColumnHeader <DataTableColumnHeader
column={column} column={column}
title={columns_titles.categoria} title={columns_titles.categoria}
/> />
), ),
filterFn: (row, id, value) => {
return includesAll(row.getValue(id), value as string[]);
},
accessorKey: "categoria",
}, },
{ {
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.tipologia}
/>
),
accessorKey: "tipologia", accessorKey: "tipologia",
filterFn: (row, id, value) => {
return includesAll(row.getValue(id), value as string[]);
},
cell: ({ row }) => { cell: ({ row }) => {
return ( return (
<div> <div>
@ -122,20 +112,27 @@ export const PrezzarioTable = ({
</div> </div>
); );
}, },
filterFn: (row, id, value) => {
return includesAll(row.getValue(id), value as string[]);
},
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title={columns_titles.tipologia}
/>
),
}, },
{ {
header: ({ column }) => ( accessorKey: "prezzo",
<DataTableColumnHeader column={column} title={columns_titles.prezzo} />
),
cell: ({ row }) => { cell: ({ row }) => {
return <div>{formatCurrency(row.original.prezzo_cent / 100)}</div>; return <div>{formatCurrency(row.original.prezzo_cent / 100)}</div>;
}, },
accessorKey: "prezzo", header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.prezzo} />
),
}, },
{ {
header: ({ column }) => ( accessorKey: "sconto",
<DataTableColumnHeader column={column} title={columns_titles.sconto} />
),
cell: ({ row }) => { cell: ({ row }) => {
if (row.original.sconto === 0) { if (row.original.sconto === 0) {
return ( return (
@ -146,53 +143,54 @@ export const PrezzarioTable = ({
} }
return <Badge>{row.original.sconto}%</Badge>; return <Badge>{row.original.sconto}%</Badge>;
}, },
header: ({ column }) => (
accessorKey: "sconto", <DataTableColumnHeader column={column} title={columns_titles.sconto} />
),
}, },
{ {
header: ({ column }) => ( accessorKey: "actions",
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
cell: ({ row }) => { cell: ({ row }) => {
const rowData = row.original; const rowData = row.original;
return ( return (
<Button <Button
aria-label="Modifica Prezziario" aria-label="Modifica Prezziario"
variant="ghost"
onClick={() => { onClick={() => {
setEditData({ setEditData({
idprezziario: rowData.id,
nome_it: rowData.nome_it,
nome_en: rowData.nome_en,
desc_it: rowData.desc_it,
desc_en: rowData.desc_en, desc_en: rowData.desc_en,
prezzo_cent: rowData.prezzo_cent, desc_it: rowData.desc_it,
testo_condizioni: rowData.testo_condizioni, idprezziario: rowData.id,
isAcconto: rowData.isAcconto, isAcconto: rowData.isAcconto,
isSaldo: rowData.isSaldo,
isConsulenza: rowData.isConsulenza,
isActive: rowData.isActive, isActive: rowData.isActive,
sconto: rowData.sconto, isConsulenza: rowData.isConsulenza,
isTransitorio: rowData.isTransitorio, isSaldo: rowData.isSaldo,
isStabile: rowData.isStabile, isStabile: rowData.isStabile,
isTransitorio: rowData.isTransitorio,
nome_en: rowData.nome_en,
nome_it: rowData.nome_it,
prezzo_cent: rowData.prezzo_cent,
sconto: rowData.sconto,
testo_condizioni: rowData.testo_condizioni,
}); });
setOpenEdit(true); setOpenEdit(true);
}} }}
variant="ghost"
> >
<Cog className="text-neutral-600" /> <Cog className="text-neutral-600" />
</Button> </Button>
); );
}, },
accessorKey: "actions",
enableHiding: false, enableHiding: false,
enableSorting: false, enableSorting: false,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={columns_titles.actions} />
),
}, },
]; ];
const pinnedFiltri: PinnedFiltro[] = [ const pinnedFiltri: PinnedFiltro[] = [
{ {
title: "Categoria",
columnName: "categoria", columnName: "categoria",
options: cat_options, options: cat_options,
title: "Categoria",
}, },
]; ];
const searchFiltro: SearchFiltro = { const searchFiltro: SearchFiltro = {
@ -203,11 +201,11 @@ export const PrezzarioTable = ({
<div className="mx-auto w-full px-2 py-10"> <div className="mx-auto w-full px-2 py-10">
<DataTable <DataTable
columns={columns} columns={columns}
columns_titles={columns_titles}
data={tabledata} data={tabledata}
defaultSort={[{ desc: false, id: "id" }]}
pinnedFiltri={pinnedFiltri} pinnedFiltri={pinnedFiltri}
searchColumn={searchFiltro} searchColumn={searchFiltro}
columns_titles={columns_titles}
defaultSort={[{ desc: false, id: "id" }]}
/> />
</div> </div>
); );

View file

@ -37,11 +37,11 @@ export const TabRicerca = ({
{data.map((servizio, idx) => { {data.map((servizio, idx) => {
return ( return (
<Row <Row
defaultOpen={idx === 0}
isAdmin={isAdmin}
key={idx} key={idx}
servizio={servizio} servizio={servizio}
userId={userId} userId={userId}
isAdmin={isAdmin}
defaultOpen={idx === 0}
/> />
); );
})} })}
@ -64,11 +64,11 @@ const Row = ({
return ( return (
<ServizioProvider <ServizioProvider
isAdmin={isAdmin} isAdmin={isAdmin}
userId={userId}
servizioId={servizio.servizio_id}
servizio={servizio} servizio={servizio}
servizioId={servizio.servizio_id}
userId={userId}
> >
<Collapsible open={isOpen} onOpenChange={setIsOpen}> <Collapsible onOpenChange={setIsOpen} open={isOpen}>
<div className="border-border flex items-center rounded-md border py-2 shadow-sm hover:shadow-md"> <div className="border-border flex items-center rounded-md border py-2 shadow-sm hover:shadow-md">
<CollapsibleTrigger className="grid w-full cursor-pointer grid-cols-10 items-center"> <CollapsibleTrigger className="grid w-full cursor-pointer grid-cols-10 items-center">
<div className="col-span-2 sm:col-span-1"> <div className="col-span-2 sm:col-span-1">
@ -89,8 +89,8 @@ const Row = ({
</div> </div>
<div className="hidden text-sm sm:col-span-2 sm:contents sm:text-base"> <div className="hidden text-sm sm:col-span-2 sm:contents sm:text-base">
{servizio.created_at.toLocaleDateString("it", { {servizio.created_at.toLocaleDateString("it", {
month: "short",
day: "2-digit", day: "2-digit",
month: "short",
})} })}
</div> </div>
</CollapsibleTrigger> </CollapsibleTrigger>
@ -99,9 +99,9 @@ const Row = ({
<CollapsibleContent className="data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up mt-2 overflow-hidden transition-all duration-300 ease-in-out"> <CollapsibleContent className="data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up mt-2 overflow-hidden transition-all duration-300 ease-in-out">
<ServizioProvider <ServizioProvider
isAdmin={isAdmin} isAdmin={isAdmin}
userId={userId}
servizioId={servizio.servizio_id}
servizio={servizio} servizio={servizio}
servizioId={servizio.servizio_id}
userId={userId}
> >
<ServizioContent /> <ServizioContent />
</ServizioProvider> </ServizioProvider>

View file

@ -57,32 +57,32 @@ export const TabServizio = ({
const { data } = api.servizio.getUserServizi.useQuery({ userId }); const { data } = api.servizio.getUserServizi.useQuery({ userId });
const utils = api.useUtils(); const utils = api.useUtils();
const { mutate: remove } = api.servizio.deleteServizio.useMutation({ const { mutate: remove } = api.servizio.deleteServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio rimosso con successo"); toast.success("Servizio rimosso con successo");
await utils.servizio.getUserServizi.invalidate(); await utils.servizio.getUserServizi.invalidate();
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
const { mutate: sendEmail } = api.servizio.sendServizioEmail.useMutation({ const { mutate: sendEmail } = api.servizio.sendServizioEmail.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio rimosso con successo"); toast.success("Servizio rimosso con successo");
await utils.servizio.getUserServizi.invalidate(); await utils.servizio.getUserServizi.invalidate();
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
const { mutate: update } = api.servizio.updateServizio.useMutation({ const { mutate: update } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => { onSuccess: async () => {
toast.success("Servizio modificato con successo"); toast.success("Servizio modificato con successo");
await utils.servizio.getUserServizi.invalidate(); await utils.servizio.getUserServizi.invalidate();
}, },
onError: (error) => {
toast.error(error.message);
},
}); });
if (!data) return <LoadingPage />; if (!data) return <LoadingPage />;
@ -119,9 +119,9 @@ export const TabServizio = ({
<TableCell>{servizio.tipologia}</TableCell> <TableCell>{servizio.tipologia}</TableCell>
<TableCell> <TableCell>
{new Date(servizio.created_at).toLocaleDateString("it-IT", { {new Date(servizio.created_at).toLocaleDateString("it-IT", {
year: "numeric",
month: "2-digit",
day: "2-digit", day: "2-digit",
month: "2-digit",
year: "numeric",
})} })}
</TableCell> </TableCell>
<TableCell> <TableCell>
@ -150,14 +150,13 @@ export const TabServizio = ({
<TableCell> <TableCell>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" aria-label="Servizio Actions"> <Button aria-label="Servizio Actions" variant="outline">
<EllipsisVertical /> <EllipsisVertical />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Confirm <Confirm
title="Invia email"
description="Sei sicuro di voler inviare l'email?" description="Sei sicuro di voler inviare l'email?"
onConfirm={() => { onConfirm={() => {
sendEmail({ sendEmail({
@ -165,13 +164,14 @@ export const TabServizio = ({
userId: userId, userId: userId,
}); });
}} }}
title="Invia email"
> >
<Button <Button
disabled={onboardDone}
aria-label="Invia Email Servizio" aria-label="Invia Email Servizio"
variant="ghost"
className="flex w-full justify-start gap-3 px-2 font-normal" className="flex w-full justify-start gap-3 px-2 font-normal"
disabled={onboardDone}
size="sm" size="sm"
variant="ghost"
> >
<Mail className="stroke-foreground size-5" /> <Mail className="stroke-foreground size-5" />
Invia all&apos;utente Invia all&apos;utente
@ -181,14 +181,14 @@ export const TabServizio = ({
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link <Link
aria-label="Apri Onboarding Servizio" aria-label="Apri Onboarding Servizio"
href={`/area-riservata/admin/user-view/onboard/${servizio.servizio_id}`}
className="!p-0" className="!p-0"
href={`/area-riservata/admin/user-view/onboard/${servizio.servizio_id}`}
> >
<Button <Button
variant="ghost"
size="sm"
className="flex w-full justify-start gap-3 px-2 font-normal" className="flex w-full justify-start gap-3 px-2 font-normal"
disabled={onboardDone} disabled={onboardDone}
size="sm"
variant="ghost"
> >
<ExternalLink className="stroke-foreground size-5" /> <ExternalLink className="stroke-foreground size-5" />
Apri Onboarding Apri Onboarding
@ -198,9 +198,7 @@ export const TabServizio = ({
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Button <Button
aria-label="Invia Email Servizio" aria-label="Invia Email Servizio"
variant="ghost"
className="flex w-full justify-start gap-3 px-2 font-normal" className="flex w-full justify-start gap-3 px-2 font-normal"
size="sm"
disabled={onboardDone} disabled={onboardDone}
onClick={async () => { onClick={async () => {
await navigator.clipboard.writeText( await navigator.clipboard.writeText(
@ -210,6 +208,8 @@ export const TabServizio = ({
icon: "📋", icon: "📋",
}); });
}} }}
size="sm"
variant="ghost"
> >
<Copy className="stroke-foreground size-5" /> <Copy className="stroke-foreground size-5" />
Copia Link Copia Link
@ -224,18 +224,18 @@ export const TabServizio = ({
<DropdownMenuPortal> <DropdownMenuPortal>
<DropdownMenuSubContent> <DropdownMenuSubContent>
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
value={String(servizio.isOkAcconto)}
onValueChange={(v) => { onValueChange={(v) => {
console.log(v); console.log(v);
update({ update({
servizioId: servizio.servizio_id,
data: { data: {
isOkAcconto: v === "true",
decorrenza: v === "true" ? new Date() : null, decorrenza: v === "true" ? new Date() : null,
isOkAcconto: v === "true",
}, },
servizioId: servizio.servizio_id,
}); });
}} }}
value={String(servizio.isOkAcconto)}
> >
<DropdownMenuRadioItem value="true"> <DropdownMenuRadioItem value="true">
Attivo Attivo
@ -255,15 +255,15 @@ export const TabServizio = ({
<DropdownMenuPortal> <DropdownMenuPortal>
<DropdownMenuSubContent> <DropdownMenuSubContent>
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
value={servizio.isInterrotto.toString()}
onValueChange={(v) => { onValueChange={(v) => {
update({ update({
servizioId: servizio.servizio_id,
data: { data: {
isInterrotto: v === "true", isInterrotto: v === "true",
}, },
servizioId: servizio.servizio_id,
}); });
}} }}
value={servizio.isInterrotto.toString()}
> >
<DropdownMenuRadioItem value="true"> <DropdownMenuRadioItem value="true">
Interrotto Interrotto

Some files were not shown because too many files have changed in this diff Show more