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": [
{
"previewLimit": 50,
"server": "localhost",
"port": 5432,
"database": "postgres",
"driver": "PostgreSQL",
"name": "DB",
"database": "postgres",
"username": "postgres",
"password": "rootpost"
"password": "rootpost",
"port": 5432,
"previewLimit": 50,
"server": "localhost",
"username": "postgres"
}
],
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules\\typescript\\lib"
}

View file

@ -16,15 +16,15 @@
* ```
*/
export type Prettify<T> = {
[K in keyof T]: T[K];
[K in keyof T]: T[K];
} & {};
//
export type PrettifyR<T> = T extends object
? T extends infer O
? { [K in keyof O]: PrettifyR<O[K]> }
: never
: T;
? T extends infer O
? { [K in keyof O]: PrettifyR<O[K]> }
: never
: T;
/**
* @example

View file

@ -1,10 +1,14 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.2/schema.json",
"vcs": {
"enabled": true,
"root": "../../",
"clientKind": "git",
"useIgnoreFile": false
"assist": {
"actions": {
"source": {
"organizeImports": "on",
"useSortedAttributes": "on",
"useSortedKeys": "on"
}
},
"enabled": true
},
"files": {
@ -21,6 +25,7 @@
"headers",
"schemas"
],
"ignoreUnknown": true,
"includes": [
"**",
"!node_modules",
@ -49,60 +54,56 @@
"!src/i18n/nazioni.ts",
"!src/i18n/provincie.ts",
"!src/styles/globals.css"
],
"ignoreUnknown": true
]
},
"formatter": {
"enabled": true,
"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": {
"formatter": {
"quoteStyle": "double"
}
},
"assist": {
"linter": {
"domains": {
"next": "recommended",
"react": "recommended"
},
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
"rules": {
"a11y": {
"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",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/global.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "~/components",
"utils": "~/lib/utils",
"ui": "~/components/ui",
"lib": "~/lib",
"hooks": "~/hooks"
},
"iconLibrary": "lucide"
}
"$schema": "https://ui.shadcn.com/schema.json",
"aliases": {
"components": "~/components",
"hooks": "~/hooks",
"lib": "~/lib",
"ui": "~/components/ui",
"utils": "~/lib/utils"
},
"iconLibrary": "lucide",
"rsc": false,
"style": "new-york",
"tailwind": {
"baseColor": "neutral",
"config": "",
"css": "src/styles/global.css",
"cssVariables": true,
"prefix": ""
},
"tsx": true
}

View file

@ -1,40 +1,40 @@
import { Button, Heading, Row, Section, Text } from "@react-email/components";
import Base from "./base";
export type VerificaEmail_NewMail = {
mailType: "verificaEmail";
props: VerificaEmailProps;
mailType: "verificaEmail";
props: VerificaEmailProps;
};
type VerificaEmailProps = {
token: string;
userId: string;
redirect?: string;
token: string;
userId: string;
redirect?: string;
};
const VerificaEmail = ({ token, userId, redirect }: VerificaEmailProps) => {
return (
<Base preview="Verifica la tua email" noreply>
<>
<Heading className="text-center text-3xl leading-8">
Verifica la tua email
</Heading>
<Section className="px-4 text-center text-base md:px-12">
<Row>
<Text>
Clicca sul pulsante qui sotto per verificare la tua email.
</Text>
</Row>
return (
<Base noreply preview="Verifica la tua email">
<>
<Heading className="text-center text-3xl leading-8">
Verifica la tua email
</Heading>
<Section className="px-4 text-center text-base md:px-12">
<Row>
<Text>
Clicca sul pulsante qui sotto per verificare la tua email.
</Text>
</Row>
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${process.env.NEXT_PUBLIC_BASE_URL}/auth/verifica-email-action?t=${token}&u=${userId}${redirect ? `&r=${redirect}` : ""}`}
>
Clicca qui per verificare
</Button>
</Section>
</>
</Base>
);
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${process.env.NEXT_PUBLIC_BASE_URL}/auth/verifica-email-action?t=${token}&u=${userId}${redirect ? `&r=${redirect}` : ""}`}
>
Clicca qui per verificare
</Button>
</Section>
</>
</Base>
);
};
export default VerificaEmail;

View file

@ -42,13 +42,13 @@ const Base = ({
<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">
<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"
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">

View file

@ -1,48 +1,48 @@
import { Button, Heading, Row, Section, Text } from "@react-email/components";
import Base from "./base";
export type Generic_NewMail = {
mailType: "generic";
props: GenericProps;
mailType: "generic";
props: GenericProps;
};
type GenericProps = {
title: string;
noreply?: boolean;
testo: string;
link?: {
href: string;
label?: string;
};
title: string;
noreply?: boolean;
testo: string;
link?: {
href: string;
label?: string;
};
};
const Generic = ({
title,
noreply = false,
testo,
link = undefined,
title,
noreply = false,
testo,
link = undefined,
}: GenericProps) => {
return (
<Base preview={title} noreply={noreply}>
<>
<Heading className="text-center text-3xl leading-8">{title}</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>{testo}</Text>
</Row>
{link && (
<Section className="mb-5">
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${link.href}`}
>
{link.label}
</Button>
</Section>
)}
</Section>
</>
</Base>
);
return (
<Base noreply={noreply} preview={title}>
<>
<Heading className="text-center text-3xl leading-8">{title}</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>{testo}</Text>
</Row>
{link && (
<Section className="mb-5">
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${link.href}`}
>
{link.label}
</Button>
</Section>
)}
</Section>
</>
</Base>
);
};
export default Generic;

View file

@ -61,11 +61,11 @@ const OnboardingServizio = ({
<Section className="mb-[30px]" key={index}>
<Row className="mb-[12px]">
<Img
src={`${process.env.NEXT_PUBLIC_BASE_URL}/go-api/images/get/${annuncio.immagine}`}
width="100%"
height="140px"
alt={`Annuncio image - ${annuncio.codice}`}
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 className="block w-full sm:hidden">
@ -83,8 +83,8 @@ const OnboardingServizio = ({
{(annuncio.prezzo / 1e2).toFixed(2)}
</Text>
<Link
href={`${process.env.NEXT_PUBLIC_BASE_URL}/annuncio/${annuncio.codice}`}
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
</Link>

View file

@ -1,36 +1,36 @@
import { Button, Heading, Row, Section, Text } from "@react-email/components";
import Base from "./base";
export type PagamentoErrore_NewMail = {
mailType: "pagamentoErrore";
mailType: "pagamentoErrore";
};
const PagamentoErrore = () => {
return (
<Base preview="Errore Pagamento">
<>
<Heading className="text-center text-3xl leading-8">
Errore Pagamento
</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>
Gentile Cliente, la informiamo che il pagamento non è andato a
buon fine. Si prega di riprovare o contattare il nostro servizio
clienti per ulteriori informazioni.
</Text>
</Row>
<Section className="mb-5">
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${process.env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard`}
>
Vai al tuo account
</Button>
</Section>
</Section>
</>
</Base>
);
return (
<Base preview="Errore Pagamento">
<>
<Heading className="text-center text-3xl leading-8">
Errore Pagamento
</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>
Gentile Cliente, la informiamo che il pagamento non è andato a
buon fine. Si prega di riprovare o contattare il nostro servizio
clienti per ulteriori informazioni.
</Text>
</Row>
<Section className="mb-5">
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${process.env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard`}
>
Vai al tuo account
</Button>
</Section>
</Section>
</>
</Base>
);
};
export default PagamentoErrore;

View file

@ -1,36 +1,36 @@
import { Button, Heading, Row, Section, Text } from "@react-email/components";
import Base from "./base";
export type PagamentoRicezione_NewMail = {
mailType: "pagamentoRicezione";
mailType: "pagamentoRicezione";
};
const PagamentoRicezione = () => {
return (
<Base preview="Ricezione Pagamento">
<>
<Heading className="text-center text-3xl leading-8">
Ricezione Pagamento
</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>
Gentile Cliente, le confermiamo la ricezione del pagamento. Le
invieremo una mail di conferma quando il pagamento sarà stato
processato.
</Text>
</Row>
<Section className="mb-5">
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${process.env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard`}
>
Vai al tuo account
</Button>
</Section>
</Section>
</>
</Base>
);
return (
<Base preview="Ricezione Pagamento">
<>
<Heading className="text-center text-3xl leading-8">
Ricezione Pagamento
</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>
Gentile Cliente, le confermiamo la ricezione del pagamento. Le
invieremo una mail di conferma quando il pagamento sarà stato
processato.
</Text>
</Row>
<Section className="mb-5">
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${process.env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard`}
>
Vai al tuo account
</Button>
</Section>
</Section>
</>
</Base>
);
};
export default PagamentoRicezione;

View file

@ -1,39 +1,39 @@
import { Heading, Row, Section, Text } from "@react-email/components";
import Base from "./base";
export type Recesso_NewMail = {
mailType: "recesso";
mailType: "recesso";
};
const Recesso = () => {
return (
<Base preview="Richiesta di recesso">
<>
<Heading className="text-center text-3xl leading-8">
Richiesta di recesso
</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>
Gentile cliente, abbiamo ricevuto la tua richiesta di recesso.
Procederemo ad elaborare la richiesta e termineremo i servizi
attualemte attivi nel tuo account.
</Text>
<Text>
Per ulteriori informazioni e chiarimenti non esiti a contattarci
all&apos;indirizzo{" "}
<a
href="mailto:amministrazione@infoalloggi.it"
rel="noopener noreferrer"
>
amministrazione@infoalloggi.it{" "}
</a>
</Text>
<Text>Cordiali saluti</Text>
<br />
</Row>
</Section>
</>
</Base>
);
return (
<Base preview="Richiesta di recesso">
<>
<Heading className="text-center text-3xl leading-8">
Richiesta di recesso
</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>
Gentile cliente, abbiamo ricevuto la tua richiesta di recesso.
Procederemo ad elaborare la richiesta e termineremo i servizi
attualemte attivi nel tuo account.
</Text>
<Text>
Per ulteriori informazioni e chiarimenti non esiti a contattarci
all&apos;indirizzo{" "}
<a
href="mailto:amministrazione@infoalloggi.it"
rel="noopener noreferrer"
>
amministrazione@infoalloggi.it{" "}
</a>
</Text>
<Text>Cordiali saluti</Text>
<br />
</Row>
</Section>
</>
</Base>
);
};
export default Recesso;

View file

@ -1,35 +1,35 @@
import { Button, Heading, Row, Section, Text } from "@react-email/components";
import Base from "./base";
export type RegistrazioneAvvenuta_NewMail = {
mailType: "registrazioneAvvenuta";
mailType: "registrazioneAvvenuta";
};
const RegistrazioneAvvenuta = () => {
return (
<Base preview="Registrazione Avvenuta">
<>
<Heading className="text-center text-3xl leading-8">
Registrazione avvenuta
</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>
La tua registrazione è avvenuta con successo! Ora puoi accedere al
nostro sito con le tue credenziali.
</Text>
</Row>
<Section className="mb-5">
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${process.env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard`}
>
Vai al tuo account
</Button>
</Section>
</Section>
</>
</Base>
);
return (
<Base preview="Registrazione Avvenuta">
<>
<Heading className="text-center text-3xl leading-8">
Registrazione avvenuta
</Heading>
<Section className="px-5 text-left text-base md:px-12">
<Row>
<Text>
La tua registrazione è avvenuta con successo! Ora puoi accedere al
nostro sito con le tue credenziali.
</Text>
</Row>
<Section className="mb-5">
<Button
className="mx-auto box-border inline-flex h-10 items-center justify-center rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold whitespace-nowrap text-neutral-900"
href={`${process.env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard`}
>
Vai al tuo account
</Button>
</Section>
</Section>
</>
</Base>
);
};
export default RegistrazioneAvvenuta;

View file

@ -1,6 +1,5 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"project": ["src/**", "**", "!src/schemas/public/*.ts", "!emails/**"],
"ignore": [
".kanelrc.js",
"TypeHelpers.type.ts",
@ -36,5 +35,6 @@
"@radix-ui/react-slot",
"@radix-ui/react-tooltip",
"@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} */
const nextConfig = {
output: env.NODE_ENV === "production" ? "standalone" : undefined, // This is for docker builds
reactStrictMode: true,
compiler: {
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,
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 () => {
return [
{
source: "/:path*",
headers: [
{
key: "X-Frame-Options",
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;

View file

@ -1,22 +1,6 @@
{
"name": "infoalloggi",
"version": "0.1.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"
"ct3aMetadata": {
"initVersion": "7.9.0"
},
"dependencies": {
"@fattureincloud/fattureincloud-ts-sdk": "^2.1.1",
@ -122,7 +106,23 @@
"npm-run-all": "^4.1.5",
"typescript": "^5.9.2"
},
"ct3aMetadata": {
"initVersion": "7.9.0"
}
"name": "infoalloggi",
"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,39 +1,39 @@
import type { Metadata } from "next";
import "~/styles/globals.css";
export const metadata: Metadata = {
title: "Home",
description: "Welcome to Next.js",
icons: {
apple: {
url: "/favicon/apple-touch-icon.png",
sizes: "180x180",
type: "image/png",
},
icon: [
{ url: "/favicon/favicon-32x32.png", sizes: "32x32", type: "image/png" },
{ url: "/favicon/favicon-16x16.png", sizes: "16x16", type: "image/png" },
],
other: [
{
rel: "mask-icon",
url: "/favicon/safari-pinned-tab.svg",
color: "#5bbad5",
},
],
},
manifest: "/site.webmanifest",
description: "Welcome to Next.js",
icons: {
apple: {
sizes: "180x180",
type: "image/png",
url: "/favicon/apple-touch-icon.png",
},
icon: [
{ sizes: "32x32", type: "image/png", url: "/favicon/favicon-32x32.png" },
{ sizes: "16x16", type: "image/png", url: "/favicon/favicon-16x16.png" },
],
other: [
{
color: "#5bbad5",
rel: "mask-icon",
url: "/favicon/safari-pinned-tab.svg",
},
],
},
manifest: "/site.webmanifest",
title: "Home",
};
export default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}: {
children: React.ReactNode;
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

View file

@ -2,19 +2,19 @@ import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import { db } from "~/server/db";
export async function getDataFromDB() {
try {
// Example query - adjust to your database schema
const data = await db
.selectFrom("annunci")
.select("codice")
.where("web", "=", true)
.where("tipo", "=", TipologiaPosizioneEnum.Transitorio)
.where("stato", "=", "Attivo")
.execute();
try {
// Example query - adjust to your database schema
const data = await db
.selectFrom("annunci")
.select("codice")
.where("web", "=", true)
.where("tipo", "=", TipologiaPosizioneEnum.Transitorio)
.where("stato", "=", "Attivo")
.execute();
return data;
} catch (error) {
console.error("Database error:", error);
return [];
}
return data;
} catch (error) {
console.error("Database error:", error);
return [];
}
}

View file

@ -216,15 +216,15 @@ export const IconMatrixTooltip = () => {
<div className="grid grid-cols-4 gap-2">
{iconOptions.map((icon) => (
<button
type="button"
className="bg-muted flex flex-col items-center rounded-xl p-2 hover:opacity-75"
key={icon}
onClick={async () => {
await navigator.clipboard.writeText(icon);
toast("Copiato", { icon: "📋" });
}}
key={icon}
className="bg-muted flex flex-col items-center rounded-xl p-2 hover:opacity-75"
type="button"
>
<IconMatrix type={icon} className="size-6" />
<IconMatrix className="size-6" type={icon} />
<span className="text-sm">{icon}</span>
</button>
))}
@ -310,16 +310,16 @@ export const ShieldExclamationIcon = ({
className?: string;
}) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={className}
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Shield</title>
<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"
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>
);
@ -330,49 +330,49 @@ export const QuestionMarkCircleIcon = ({
className?: string;
}) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={className}
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>QuestionMark</title>
<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"
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>
);
export const HomeIcon = ({ className }: { className?: string }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className={className}
fill="none"
stroke="currentColor"
strokeWidth={1.5}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Home</title>
<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"
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>
);
const CheckCircleIcon = ({ className }: { className?: string }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={className}
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>CheckCircle</title>
<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"
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>
);

View file

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

View file

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

View file

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

View file

@ -27,7 +27,7 @@ export const MappaDialogFullscreen = ({
const { t } = useTranslation();
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild>
<Button className="w-auto grow" disabled={!lat || !long}>
<MapIcon className="size-5" /> {t.mappa}
@ -42,18 +42,18 @@ export const MappaDialogFullscreen = ({
{lat && long && (
<>
<MapComp
height="100%"
markerType="home"
posix={{
popupTxt:
"Posizione esatta fornita dopo la richiesta di contatto",
pos: {
lat: Number.parseFloat(lat),
lng: Number.parseFloat(long),
},
popupTxt:
"Posizione esatta fornita dopo la richiesta di contatto",
}}
markerType="home"
scrollWheelZoom
width="100%"
height="100%"
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)}
ref={ref}
>
<Accordion type="single" collapsible className="w-full">
<Accordion className="w-full" collapsible type="single">
{texts.map((text, index) => (
<AccordionItem key={`ac-item-${index}`} value={`item-${index}`}>
<AccordionTrigger className="cursor-pointer gap-4 text-left text-lg">

View file

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

View file

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

View file

@ -45,12 +45,12 @@ export const AllegatoIframe = ({
>
{/** biome-ignore lint/a11y/noNoninteractiveElementInteractions: <need interaction> */}
<iframe
title="Allegato"
ref={if_ref}
src={requestUrl}
allowFullScreen
className="h-full w-full"
onLoad={handleIframeLoad}
allowFullScreen
ref={if_ref}
src={requestUrl}
title="Allegato"
/>
</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">
{pagedata.map((annuncio) => (
<CardAnnuncio
id={annuncio.id}
key={annuncio.codice}
camere={annuncio.numero_camere}
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}
provincia={annuncio.provincia}
stato={annuncio.stato}
tipo={annuncio.tipo}
titolo={getTitoloTranslation({
locale: locale,
titolo_it: annuncio.titolo_it,
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>

View file

@ -30,9 +30,9 @@ export const MapSection = () => {
return (
<div className="relative z-50 flex h-[60vh] w-full items-center justify-center sm:h-[65vh]">
<MapDisplay
selectTipo={tipo}
selectComune={comune}
selectConsegna={consegna}
selectTipo={tipo}
setSelected={setSelected}
/>
<SelectedComp selected={selected} />
@ -53,9 +53,9 @@ const MapDisplay = memo(
setSelected: Dispatch<SetStateAction<AnnuncioRicercaWPosition | null>>;
}) => {
const { data, isLoading } = api.annunci.getMapping.useQuery({
tipologia: selectTipo,
comune: selectComune,
consegna: selectConsegna || undefined,
tipologia: selectTipo,
});
if (isLoading) return <LoadingPage />;
@ -65,12 +65,12 @@ const MapDisplay = memo(
.map((a) => {
if (a.lat_secondario && a.lon_secondario) {
return {
markerTxt: `${a.prezzo / 1e2}`,
onClick: () => setSelected(a),
pos: {
lat: parseFloat(a.lat_secondario),
lng: parseFloat(a.lon_secondario),
},
onClick: () => setSelected(a),
markerTxt: `${a.prezzo / 1e2}`,
};
}
return undefined;
@ -79,15 +79,15 @@ const MapDisplay = memo(
return (
<MapComp
isGroup
groupData={posizioni}
posix={{ pos: { lat: 45.764718, lng: 11.73067 } }}
width="100%"
height="100%"
zoom={13}
scrollWheelZoom
isGroup
markerType="price"
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}>
{selected && (
<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 }}
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 }}
href={`/annuncio/${selected.codice}`}
initial={{ opacity: 0, scale: 0 }}
key={selected.id}
target="_blank"
transition={{
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">
{selected.url_immagini?.[0] ? (
<Image
alt="a"
className="size-24 rounded-md object-cover sm:size-40"
height={500}
src={selected.url_immagini[0]}
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" />
@ -142,11 +142,11 @@ const SelectedComp = memo(
</div>
<p>
{handleConsegna({
aggiornamento: t.card.in_aggiornamento,
consegna: selected.consegna,
consegna_da: t.card.consegna_da,
mesi: t.preferenze.mesi,
subito: t.card.consegna_subito,
aggiornamento: t.card.in_aggiornamento,
})}
</p>
</div>

View file

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

View file

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

View file

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

View file

@ -63,9 +63,9 @@ export const CardAnnuncio = ({
id={`card-annuncio-${id}`}
>
<Link
href={`/annuncio/${codice}`}
className="block p-2"
//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">
{stato === "Trattativa" && (
@ -84,24 +84,24 @@ export const CardAnnuncio = ({
<CarouselItem key={`${img}-${idx}`}>
<ImageFlbk
alt={t.card.alt_immagine}
width={800}
height={400}
src={img}
priority={idx === 0}
className={
"h-56 w-full rounded-xl object-cover outline outline-neutral-200"
}
height={400}
priority={idx === 0}
src={img}
width={800}
/>
</CarouselItem>
))}
</CarouselContent>
<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()}
role="button"
tabIndex={0}
onClick={(e) => e.preventDefault()}
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
>
<CarouselPrevious
className="left-2 cursor-pointer"
@ -187,11 +187,11 @@ export const CardAnnuncio = ({
<div>
<p className="truncate font-medium">
{handleConsegna({
aggiornamento: t.card.in_aggiornamento,
consegna: consegna,
consegna_da: t.card.consegna_da,
mesi: t.preferenze.mesi,
subito: t.card.consegna_subito,
aggiornamento: t.card.in_aggiornamento,
})}
</p>
</div>
@ -220,52 +220,52 @@ export const CarouselAnnuncio = ({
<>
<div className="mx-auto my-4 w-full rounded-md text-clip">
<div className="relative">
<Carousel opts={{ loop: true, align: "start" }}>
<Carousel opts={{ align: "start", loop: true }}>
<CarouselContent>
{immagini?.map((img, idx) => (
<CarouselItem
key={`${img}-${idx}-cF`}
className={cn(
"group relative",
immagini && immagini.length === 1
? ""
: !single && "md:basis-1/2 lg:basis-1/3",
)}
key={`${img}-${idx}-cF`}
>
<ImageFlbk
priority={idx === 0}
alt={img}
width={800}
height={400}
onClick={() => handleOpenModal(idx)}
src={img}
className={cn(
"h-72 w-full rounded-md object-cover",
immagini.length === 1 && "object-contain",
)}
height={400}
onClick={() => handleOpenModal(idx)}
priority={idx === 0}
src={img}
width={800}
/>
<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"
onClick={() => handleOpenModal(idx)}
/>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious
type="button"
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1}
type="button"
/>
<CarouselNext
type="button"
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1}
type="button"
/>
</Carousel>
</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">
<DialogHeader className="absolute top-0 right-0 left-0 z-10">
<DialogTitle className="sr-only">Image Dialog</DialogTitle>
@ -277,24 +277,24 @@ export const CarouselAnnuncio = ({
<CarouselItem key={`${img}-${idx}-cD`}>
<Image
alt={`carousel-img-${idx}`}
width={1920}
className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw]"
height={1080}
src={img}
className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw]"
width={1920}
/>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious
type="button"
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1}
type="button"
/>
<CarouselNext
type="button"
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
disabled={!immagini || immagini.length === 1}
type="button"
/>
</Carousel>
</DialogContent>

View file

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

View file

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

View file

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

View file

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

View file

@ -32,13 +32,14 @@ export const Sidebar = ({
return (
<motion.nav
animate={{ width: minimized ? 65 : 192 }}
aria-label="Sidebar Menu"
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",
className,
minimized && "cursor-pointer",
//defaultOpen && "w-[65px]",
)}
animate={{ width: minimized ? 65 : 192 }}
onClick={
minimized
? (e) => {
@ -48,7 +49,6 @@ export const Sidebar = ({
}
: undefined
}
aria-label="Sidebar Menu"
>
<div
className={cn(
@ -61,9 +61,9 @@ export const Sidebar = ({
)}
<Button
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"
onClick={() => toggleSidebar(!minimized)}
variant="ghost"
>
{minimized ? (
<Expand className="size-6" />
@ -76,9 +76,9 @@ export const Sidebar = ({
<div className={cn("px-4 text-lg", minimized && "px-0")}>
<ARMinimizableLinks
isAdmin={isAdmin}
setOpen={toggleSidebar}
pathname={pathname}
minimized={minimized}
pathname={pathname}
setOpen={toggleSidebar}
/>
</div>
</motion.nav>

View file

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

View file

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

View file

@ -96,14 +96,19 @@ export default function ChatBottombar({
return (
<div className="flex min-h-[74px] w-full items-center justify-between gap-2 p-2">
<div className="flex">
<ChatAttachments userData={userData} chatUserData={chatUserData} />
<ChatAttachments chatUserData={chatUserData} userData={userData} />
</div>
<div className="relative w-full">
<AutosizeTextarea
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}
ref={inputRef}
name="message"
onBlur={() => setIsFocused(false)}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => {
setMessage(e.target.value);
}}
onFocus={() => setIsFocused(true)}
onKeyDown={(e) => {
if (e.key === "Enter") {
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"
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 && (
<div className="absolute top-0 right-2 flex h-full items-center">
<Popover open={openEmoji} onOpenChange={setOpenEmoji}>
<Popover onOpenChange={setOpenEmoji} open={openEmoji}>
<PopoverTrigger asChild>
<Button variant="ghost">
<SmilePlusIcon className="stroke-muted-foreground" />
@ -133,8 +133,8 @@ export default function ChatBottombar({
</PopoverTrigger>
<PopoverContent align="end" className="w-fit p-0">
<EmojiPicker
locale={locale as "it" | "en"}
className="h-84"
locale={locale as "it" | "en"}
onEmojiSelect={({ emoji }) => {
setMessage(message + emoji);
// if (inputRef.current) {
@ -154,16 +154,16 @@ export default function ChatBottombar({
</div>
<Button
aria-label="Send Message"
variant="ghost"
className="shrink-0"
onClick={
message.length !== 0 ? () => handleSend() : () => handleThumbsUp()
}
variant="ghost"
>
{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>
</div>

View file

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

View file

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

View file

@ -4,18 +4,18 @@ import { cn } from "~/lib/utils";
type ChatMessageListProps = HTMLAttributes<HTMLDivElement>;
const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
({ className, children, ...props }, ref) => (
<div
className={cn(
"flex h-full w-full flex-col overflow-y-auto p-4",
className,
)}
ref={ref}
{...props}
>
{children}
</div>
),
({ className, children, ...props }, ref) => (
<div
className={cn(
"flex h-full w-full flex-col overflow-y-auto p-4",
className,
)}
ref={ref}
{...props}
>
{children}
</div>
),
);
ChatMessageList.displayName = "ChatMessageList";

View file

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

View file

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

View file

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

View file

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

View file

@ -26,13 +26,13 @@ export const CodiceBox = ({
return (
<AutocompleteSearchBox
key={locale}
options={codici || []}
loading={loading}
className={className}
optionBoxClassName={optionBoxClassName}
optionsClassName={optionsClassName}
containerClassName={containerClassName}
key={locale}
loading={loading}
optionBoxClassName={optionBoxClassName}
options={codici || []}
optionsClassName={optionsClassName}
/>
);
};
@ -77,7 +77,7 @@ const AutocompleteSearchBox = ({
}, [searchValue, options]);
return (
<div ref={wrapperRef} className="z-[5000] w-full">
<div className="z-[5000] w-full" ref={wrapperRef}>
<div
className={cn(
"relative flex w-full items-center justify-center rounded-md bg-white",
@ -85,42 +85,42 @@ const AutocompleteSearchBox = ({
)}
id="searchContainer"
>
<label htmlFor="search" className="sr-only">
<label className="sr-only" htmlFor="search">
Search Box
</label>
<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"
type="search"
value={searchValue}
onBlur={() => {
setIsFocused(false);
}}
onChange={(e) => setSearchValue(e.target.value)}
onFocus={() => {
setOpen(true);
setIsFocused(true);
}}
onBlur={() => {
setIsFocused(false);
}}
onKeyDown={async (e) => {
if (e.key === "Enter" && results[0] && results.length > 0) {
setNavigationEnter(true);
await router.push(`/annuncio/${results[0].trim()}`);
}
}}
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,
)}
type="search"
value={searchValue}
/>
<MovingText hidden={isFocused || searchValue !== ""} />
</div>
{open && (
<div
ref={listRef}
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",
optionBoxClassName,
)}
id="searchOptionsBox"
ref={listRef}
>
<ul
className={cn(
@ -150,11 +150,11 @@ const AutocompleteSearchBox = ({
{results.map((item) => (
<li key={item}>
<Link
href={`/annuncio/${item}`}
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",
optionsClassName,
)}
href={`/annuncio/${item}`}
>
{item}
</Link>
@ -189,10 +189,10 @@ const MovingText = ({ hidden }: { hidden: boolean }) => {
return (
<StaggeredFade
key={randomFrase}
text={randomFrase}
className="absolute text-2xl font-bold dark:text-neutral-800"
hide={hidden}
key={randomFrase}
text={randomFrase}
/>
);
};

View file

@ -7,34 +7,34 @@ export const ComeFunziona = () => {
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,
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,
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,
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,
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,
icon: <ScrollText className="size-8" />,
image: "/come_funziona_contratto.jpg",
title: t.come_funziona.features.firma.title,
},
];
return (
@ -45,16 +45,16 @@ export const ComeFunziona = () => {
<div className="mx-auto mt-8 w-full space-y-12 md:mt-16">
{features.map((feature, idx) => (
<div
key={idx}
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">
<Image
alt={`img${idx}`}
className="border-border/50 aspect-[6/4] rounded-xl border"
height={200}
src={feature.image}
width={400}
height={200}
alt={`img${idx}`}
/>
</div>
<div className="flex basis-1/2 flex-col">

View file

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

View file

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

View file

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

View file

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

View file

@ -77,18 +77,18 @@ export const AutosizeTextarea = forwardRef<
const [triggerAutoSize, setTriggerAutoSize] = useState("");
useAutosizeTextArea({
textAreaRef: textAreaRef.current,
triggerAutoSize: triggerAutoSize,
maxHeight,
minHeight,
textAreaRef: textAreaRef.current,
triggerAutoSize: triggerAutoSize,
});
useImperativeHandle(ref, () => ({
// biome-ignore lint/style/noNonNullAssertion: <it exists>
textArea: textAreaRef.current!,
focus: () => textAreaRef.current?.focus(),
maxHeight,
minHeight,
// biome-ignore lint/style/noNonNullAssertion: <it exists>
textArea: textAreaRef.current!,
}));
useEffect(() => {
@ -100,8 +100,6 @@ export const AutosizeTextarea = forwardRef<
return (
<textarea
{...props}
value={value}
ref={textAreaRef}
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",
className,
@ -110,6 +108,8 @@ export const AutosizeTextarea = forwardRef<
setTriggerAutoSize(e.target.value);
onChange?.(e);
}}
ref={textAreaRef}
value={value}
/>
);
},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,8 +7,8 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
ref={ref}
type={type}
{...props}
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`,

View file

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

View file

@ -81,15 +81,15 @@ export function MultiSelect({
return (
<MultiSelectContext
value={{
open,
setOpen,
selectedValues: values ? new Set(values) : selectedValues,
toggleValue,
items,
onItemAdded,
open,
selectedValues: values ? new Set(values) : selectedValues,
setOpen,
toggleValue,
}}
>
<Popover open={open} onOpenChange={setOpen}>
<Popover onOpenChange={setOpen} open={open}>
{children}
</Popover>
</MultiSelectContext>
@ -110,13 +110,13 @@ export function MultiSelectTrigger({
<PopoverTrigger asChild>
<Button
{...props}
variant={props.variant ?? "outline"}
role={props.role ?? "combobox"}
aria-expanded={props["aria-expanded"] ?? open}
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",
className,
)}
role={props.role ?? "combobox"}
variant={props.variant ?? "outline"}
>
{children}
<ChevronsUpDownIcon className="size-4 shrink-0 opacity-50" />
@ -197,26 +197,17 @@ export function MultiSelectValue({
return (
<div
{...props}
ref={valueRef}
className={cn(
"flex w-full gap-1.5 overflow-hidden",
shouldWrap && "h-full flex-wrap",
className,
)}
ref={valueRef}
>
{[...selectedValues]
.filter((value) => items.has(value))
.map((value) => (
<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"
key={value}
onClick={
@ -227,6 +218,15 @@ export function MultiSelectValue({
}
: undefined
}
ref={(el) => {
if (el == null) return;
itemsRef.current.add(el);
return () => {
itemsRef.current.delete(el);
};
}}
variant="outline"
>
{items.get(value)}
{clickToRemove && (
@ -235,11 +235,11 @@ export function MultiSelectValue({
</Badge>
))}
<Badge
ref={overflowRef}
style={{
display: overflowAmount > 0 && !shouldWrap ? "block" : "none",
}}
variant="outline"
ref={overflowRef}
>
+{overflowAmount}
</Badge>
@ -275,8 +275,8 @@ export function MultiSelectContent({
) : (
// biome-ignore lint/a11y/noAriaHiddenOnFocusable: <intended>
<button
autoFocus
aria-hidden="true"
autoFocus
className="sr-only"
type="button"
/>
@ -315,11 +315,11 @@ export function MultiSelectItem({
return (
<CommandItem
{...props}
value={value}
onSelect={(v) => {
toggleValue(v);
onSelect?.(v);
}}
value={value}
>
<CheckIcon
className={cn("mr-2 size-4", isSelected ? "opacity-100" : "opacity-0")}

View file

@ -45,45 +45,45 @@ const VirtualizedMenuList = (
const virtualizer = useVirtualizer({
count,
getScrollElement: () => parentRef.current,
estimateSize: () => 45,
getScrollElement: () => parentRef.current,
});
const items = virtualizer.getVirtualItems();
return (
<div
ref={parentRef}
className="List"
ref={parentRef}
style={{
contain: "strict",
height: `${virtualizer.getTotalSize()}px`,
maxHeight: "300px",
width: "auto",
overflowY: "auto",
contain: "strict",
width: "auto",
}}
>
<div
style={{
height: virtualizer.getTotalSize(),
width: "100%",
position: "relative",
width: "100%",
}}
>
<div
style={{
left: 0,
position: "absolute",
top: 0,
left: 0,
width: "100%",
transform: `translateY(${items[0]?.start ?? 0}px)`,
width: "100%",
}}
>
{items.map((virtualRow) => (
<div
key={virtualRow.key}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
className={virtualRow.index % 2 ? "ListItemOdd" : "ListItemEven"}
data-index={virtualRow.index}
key={virtualRow.key}
ref={virtualizer.measureElement}
>
{rows[virtualRow.index]}
</div>
@ -129,14 +129,45 @@ export const MultiSelect = ({
return (
<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 }}
defaultValue={defaultValue}
inputId={elementId}
isMulti={isMulti}
isDisabled={disabled}
instanceId={elementId}
isDisabled={disabled}
isMulti={isMulti}
menuPlacement={menuPlacement}
name={elementName}
options={options}
onChange={(values) => {
if (isMulti) {
const valore = values as ListOption[];
@ -146,40 +177,34 @@ export const MultiSelect = ({
onValueChange(valore);
}
}}
defaultValue={defaultValue}
placeholder={placeholder}
onMenuOpen={() => setMenuOpen(true)}
onMenuClose={() => setMenuOpen(false)}
onMenuOpen={() => setMenuOpen(true)}
options={options}
placeholder={placeholder}
styles={{
container: (base, state) =>
({
...base,
":hover .select__dropdown-indicator": {
color: state.isFocused ? "rgb(161,161,170)" : "rgb(161,161,170)",
},
":hover .select__indicator-separator": {
backgroundColor: state.isFocused
? "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,
control: (base, state) =>
({
...base,
minHeight: "42px",
borderRadius: "0.5rem",
boxShadow: undefined,
border: state.isFocused
? "1px solid rgb(161,161,170)"
: "1px solid rgb(212 ,212 ,216)",
"&:hover": {
borderColor: state.isFocused
"& .select__dropdown-indicator": {
color: state.isFocused
? "rgb(161,161,170)"
: "rgb(161 ,161 ,170)",
: "rgb(212 ,212 ,216)",
transition: "none",
},
"& .select__indicator-separator": {
@ -188,12 +213,23 @@ export const MultiSelect = ({
: "rgb(212 ,212 ,216)",
},
"& .select__dropdown-indicator": {
color: state.isFocused
"&:hover": {
borderColor: state.isFocused
? "rgb(161,161,170)"
: "rgb(212 ,212 ,216)",
transition: "none",
: "rgb(161 ,161 ,170)",
},
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,
multiValue: (base) =>
({
@ -206,21 +242,9 @@ export const MultiSelect = ({
...base,
borderRadius: "0.5rem",
}) as CSSObjectWithLabel,
menu: (base) =>
({
...base,
borderRadius: "0.5rem",
}) as CSSObjectWithLabel,
option: (base, state) =>
({
...base,
backgroundColor: undefined,
color: state.isSelected ? undefined : undefined,
"&:hover": {
backgroundColor: undefined,
color: state.isSelected ? undefined : undefined,
},
"&:active": {
backgroundColor: "rgb(161, 161, 170)",
@ -230,39 +254,15 @@ export const MultiSelect = ({
"&:focus": {
backgroundColor: undefined,
},
"&:hover": {
backgroundColor: undefined,
color: state.isSelected ? undefined : undefined,
},
backgroundColor: undefined,
color: state.isSelected ? undefined : undefined,
}) 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[];
};
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 = {

View file

@ -32,7 +32,7 @@ function Dialog({ children }: { children: ReactNode }) {
return (
<DialogContext.Provider value={{ innerOpen, setInnerOpen }}>
<DialogPrimitive.Root open={outerOpen} onOpenChange={setOuterOpen}>
<DialogPrimitive.Root onOpenChange={setOuterOpen} open={outerOpen}>
{children}
</DialogPrimitive.Root>
</DialogContext.Provider>
@ -48,11 +48,11 @@ const DialogOverlay = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
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",
className,
)}
ref={ref}
{...props}
/>
));
@ -69,12 +69,12 @@ const DialogContent = forwardRef<
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
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",
context.innerOpen && "translate-y-[-55%] scale-[0.97]",
className,
)}
ref={ref}
{...props}
>
{children}
@ -108,8 +108,8 @@ function InnerDialog({ children }: { children: ReactNode }) {
return (
<DialogPrimitive.Root
open={context.innerOpen}
onOpenChange={context.setInnerOpen}
open={context.innerOpen}
>
{children}
</DialogPrimitive.Root>
@ -177,14 +177,6 @@ const InnerDialogContent = forwardRef<
return (
<DialogPortal>
<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(
"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
@ -203,6 +195,14 @@ const InnerDialogContent = forwardRef<
draggable && "",
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}
>
<div ref={contentRef}>{children}</div>
@ -247,11 +247,11 @@ const InnerDialogTitle = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg leading-none font-semibold tracking-tight",
className,
)}
ref={ref}
{...props}
/>
));
@ -262,8 +262,8 @@ const InnerDialogDescription = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-muted-foreground text-sm", className)}
ref={ref}
{...props}
/>
));
@ -299,11 +299,11 @@ const DialogTitle = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg leading-none font-semibold tracking-tight",
className,
)}
ref={ref}
{...props}
/>
));
@ -314,8 +314,8 @@ const DialogDescription = forwardRef<
ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-muted-foreground text-sm", className)}
ref={ref}
{...props}
/>
));

View file

@ -9,6 +9,21 @@ type TextStaggeredFadeProps = {
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> = ({
text,
className = "",
@ -17,30 +32,30 @@ export const StaggeredFade: React.FC<TextStaggeredFadeProps> = ({
const variants = {
hidden: { opacity: 0 },
show: (i: number) => ({
y: 0,
opacity: 1,
transition: { delay: i * 0.07 },
y: 0,
}),
};
const letters = text.split("");
const letters = splitTextWithEmojis(text);
const ref = React.useRef(null);
const isInView = useInView(ref, { once: true });
return (
<motion.h2
ref={ref}
initial="hidden"
animate={isInView ? (!hide ? "show" : "hidden") : ""}
variants={variants}
viewport={{ once: true }}
className={cn(
"text-center text-xl font-bold tracking-tighter md:leading-[4rem]",
className,
)}
initial="hidden"
ref={ref}
variants={variants}
viewport={{ once: true }}
>
{letters.map((word, i) => (
<motion.span key={`${word}-${i}`} variants={variants} custom={i}>
<motion.span custom={i} key={`${word}-${i}`} variants={variants}>
{word}
</motion.span>
))}

View file

@ -43,23 +43,23 @@ export const EtichetteDisplayer = ({
<div className={cn("flex flex-wrap gap-2", className)}>
{data.map((etichetta) => (
<Etichetta
className="text-xs"
color_hex={etichetta.color_hex}
key={etichetta.id_etichetta}
title={etichetta.title}
color_hex={etichetta.color_hex}
className="text-xs"
/>
))}
<button
type="button"
onClick={() => setOpenEdit(true)}
className="flex items-center justify-center rounded-lg border p-1"
onClick={() => setOpenEdit(true)}
type="button"
>
<Plus className="size-4" />
</button>
<EtichetteModal
chatId={chatId}
open={openEdit}
onOpenChange={handleOpen}
open={openEdit}
/>
</div>
);
@ -93,7 +93,7 @@ export const EtichetteModal = ({
});
if (!data) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<Dialog onOpenChange={onOpenChange} open={open}>
<DialogContent>
<DialogHeader>
<DialogTitle>Etichette Attive</DialogTitle>
@ -102,10 +102,10 @@ export const EtichetteModal = ({
<div className="space-y-5">
<div className="flex flex-col gap-2">
{data.map((etichetta, idx) => (
<div key={idx} className="flex gap-2">
<div className="flex gap-2" key={idx}>
<Etichetta
title={etichetta.title}
color_hex={etichetta.color_hex}
title={etichetta.title}
/>
<Button
onClick={() => {
@ -179,9 +179,9 @@ export const Etichetta = ({
function hexToRgb(hex: string): { r: number; g: number; b: number } {
const bigint = parseInt(hex.slice(1), 16);
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 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>
<div className="flex flex-wrap items-center justify-center gap-3">
<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={() => {
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}
</button>
<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"
href="/annunci"
>
{t.annuncio_load_fail.home}
</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="text-center">
<Link
href="/"
aria-label="footerLogo"
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" />
</Link>
@ -22,9 +22,9 @@ export const Footer = () => {
<ul className="flex flex-wrap justify-center gap-6 md:gap-8 lg:gap-12">
<li>
<Link
aria-label="footerGuida"
className="text-sm"
href="/guida"
aria-label="footerGuida"
>
{t.footer.Guida}
</Link>
@ -32,9 +32,9 @@ export const Footer = () => {
<li>
<Link
aria-label="footerChiSiamo"
className="text-sm"
href="/chi-siamo"
aria-label="footerChiSiamo"
>
{t.footer["Chi Siamo"]}
</Link>
@ -42,18 +42,18 @@ export const Footer = () => {
<li>
<Link
aria-label="footerTerminiCondizioni"
className="text-sm"
href="/termini-condizioni"
aria-label="footerTerminiCondizioni"
>
{t.termini_condizioni_title}
</Link>
</li>
<li>
<Link
aria-label="footerPrivacyPolicy"
className="text-sm"
href="/privacy-policy"
aria-label="footerPrivacyPolicy"
>
{t.privacy_policy_title}
</Link>
@ -82,8 +82,8 @@ export const MiniFooter = () => {
<div className="text-center">
<Link
aria-label="footerLogo"
href="/"
className="text-2xl font-bold tracking-wide antialiased"
href="/"
>
Infoalloggi.it
</Link>

View file

@ -52,9 +52,9 @@ type MapProps = {
} & (GroupProps | SingleProps);
const defaults = {
zoom: 15,
width: "500px",
height: "500px",
width: "500px",
zoom: 15,
};
const MedianPoint = (pos: posData[]) => {
@ -92,20 +92,20 @@ const MapComp = ({
case "price":
return (
<CustomMarker
position={pos.pos}
eventHandlers={pos.onClick && { click: pos.onClick }}
title={pos.markerTxt}
icon={
<MapMarker
color="primary"
label={
<span className="inline-block whitespace-nowrap">
{pos.markerTxt}
</span>
}
color="primary"
size="sm"
/>
}
position={pos.pos}
title={pos.markerTxt}
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</CustomMarker>
@ -114,8 +114,8 @@ const MapComp = ({
case "precise":
return (
<Marker
position={pos.pos}
eventHandlers={pos.onClick && { click: pos.onClick }}
position={pos.pos}
title={pos.markerTxt}
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
@ -125,9 +125,9 @@ const MapComp = ({
return (
<Circle
center={pos.pos}
eventHandlers={pos.onClick && { click: pos.onClick }}
pathOptions={{ color: "red" }}
radius={markerRadius || 100}
eventHandlers={pos.onClick && { click: pos.onClick }}
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</Circle>
@ -137,22 +137,22 @@ const MapComp = ({
<>
<Circle
center={pos.pos}
pathOptions={{ color: "#ec4899" }}
fillOpacity={1}
radius={markerRadius || 100}
eventHandlers={pos.onClick && { click: pos.onClick }}
fillOpacity={1}
pathOptions={{ color: "#ec4899" }}
radius={markerRadius || 100}
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</Circle>
<CustomMarker
position={pos.pos}
eventHandlers={pos.onClick && { click: pos.onClick }}
title={pos.markerTxt}
icon={
<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" />
</div>
}
position={pos.pos}
title={pos.markerTxt}
>
{pos.popupTxt && <Popup>{pos.popupTxt}</Popup>}
</CustomMarker>
@ -167,24 +167,24 @@ const MapComp = ({
return (
<MapContainer
key={hashKey}
center={
autocenter ? MedianPoint(isGroup ? groupData : [posix]) : posix.pos
}
zoom={zoom}
className={cn("")}
key={hashKey}
scrollWheelZoom={scrollWheelZoom}
style={{
borderRadius: "8px",
height: height,
width: width,
borderRadius: "8px",
}}
className={cn("")}
zoom={zoom}
>
<APIs />
<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>'
//url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
url="/api/tiles/{s}/{z}/{x}/{y}"
/>
{isGroup ? (

View file

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

View file

@ -16,54 +16,54 @@ export default function MapMarker({
size = "md",
}: MapMarkerProps) {
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: {
rectangle: "bg-blue-500 text-white",
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: {
rectangle: "bg-gray-500 text-white",
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 = {
sm: {
container: "h-8",
rectangle: "px-2 py-1 rounded-md",
triangle: "border-l-[8px] border-r-[8px] border-t-[8px]",
text: "text-xs",
lg: {
container: "h-16",
rectangle: "px-4 py-3 rounded-xl",
text: "text-base",
triangle: "border-l-[12px] border-r-[12px] border-t-[12px]",
},
md: {
container: "h-12",
rectangle: "px-3 py-2 rounded-lg",
triangle: "border-l-[10px] border-r-[10px] border-t-[10px]",
text: "text-sm",
triangle: "border-l-[10px] border-r-[10px] border-t-[10px]",
},
lg: {
container: "h-16",
rectangle: "px-4 py-3 rounded-xl",
triangle: "border-l-[12px] border-r-[12px] border-t-[12px]",
text: "text-base",
sm: {
container: "h-8",
rectangle: "px-2 py-1 rounded-md",
text: "text-xs",
triangle: "border-l-[8px] border-r-[8px] border-t-[8px]",
},
};

View file

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

View file

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

View file

@ -14,8 +14,8 @@ export function MainNav({ pathname }: MainNavProps) {
<div className="mr-2 hidden gap-6 md:flex">
<Link
aria-label="mainNavLink"
href="/"
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" />
</Link>
@ -24,12 +24,12 @@ export function MainNav({ pathname }: MainNavProps) {
{t.nav.mainNav.map((item) => (
<Link
aria-label={item.title}
key={item.href}
href={item.href}
className={cn(
"hover:text-primary/80 transition-colors",
pathname === item.href ? "text-primary" : "text-primary/70",
)}
href={item.href}
key={item.href}
>
{item.title}
</Link>

View file

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

View file

@ -24,9 +24,9 @@ export function RicercaCommand() {
}, []);
return (
<>
<Dialog open={open} onOpenChange={setOpen}>
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild>
<Button variant="ghost" className="p-2" aria-label="ricercaBtn">
<Button aria-label="ricercaBtn" className="p-2" variant="ghost">
<Search className="size-5" />
</Button>
</DialogTrigger>
@ -37,8 +37,8 @@ export function RicercaCommand() {
</DialogHeader>
<div className="relative mx-auto flex w-full gap-2">
<CodiceBox
key={"codiceBox"}
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"
/>
</div>

View file

@ -12,7 +12,7 @@ export const SettingsPopover = () => {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" className="p-2" aria-label="Settings">
<Button aria-label="Settings" className="p-2" variant="ghost">
<Settings className="size-5" />
</Button>
</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">
<MainNav pathname={pathname} />
<MobileNav
pathname={pathname}
open={openSidebar}
pathname={pathname}
setOpen={setOpenSidebar}
userOpen={openUserMenuSidebar}
setUserOpen={setOpenUserMenuSidebar}
userOpen={openUserMenuSidebar}
/>
<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 spring: Transition = {
type: "spring",
stiffness: 700,
damping: 30,
stiffness: 700,
type: "spring",
};
return (
<button
aria-label="Toggle Theme"
type="button"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
className={cn(
`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",
)}
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
type="button"
>
<motion.div
className={cn(

View file

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

View file

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

View file

@ -44,15 +44,15 @@ export const ProgressRedirect = ({ href }: { href: string }) => {
}, [progress]);
return (
<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">
<p className="text-muted-foreground">
Stai venendo reindirizzato... oppure
</p>
<Link
href={href}
className="font-semibold underline underline-offset-1"
href={href}
>
Clicca qui
</Link>

View file

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

View file

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

View file

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

View file

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

View file

@ -46,9 +46,9 @@ export const ServizioContainer = ({
return (
<ServizioProvider
isAdmin={isAdmin}
userId={userId}
servizioId={servizio.servizio_id}
servizio={servizio}
servizioId={servizio.servizio_id}
userId={userId}
>
<ServizioMain />
</ServizioProvider>
@ -91,13 +91,13 @@ export const ServizioContent = () => {
const { servizio, isAdmin, userId } = useServizio();
const utils = api.useUtils();
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => {
toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
},
onError: (error) => {
toast.error(error.message);
},
});
const router = useRouter();
@ -114,6 +114,15 @@ export const ServizioContent = () => {
return (
<ServizioCard
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={
<>
<div className="flex flex-col gap-4">
@ -126,24 +135,15 @@ export const ServizioContent = () => {
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
month: "2-digit",
year: "numeric",
})}
</p>
</CardDescription>
</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) {
return (
<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={
<div className="flex w-full flex-col gap-2">
{/* <div className="flex w-full flex-col gap-4">
@ -168,40 +202,6 @@ export const ServizioContent = () => {
<ServizioActions2 />
</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) {
return (
<ServizioCard
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
header={
<>
{/* <div className="flex w-full flex-wrap items-center gap-2">
@ -235,17 +246,6 @@ export const ServizioContent = () => {
<ServizioActions2 />
</>
}
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
/>
);
}
@ -263,6 +263,68 @@ export const ServizioContent = () => {
return (
<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={
<>
{/* <div className="flex w-full flex-col gap-2">
@ -286,68 +348,6 @@ export const ServizioContent = () => {
<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 utils = api.useUtils();
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => {
toast.success("Servizio modificato con successo");
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
},
onError: (error) => {
toast.error(error.message);
},
});
const router = useRouter();
@ -379,6 +379,15 @@ const ServizioMain = () => {
return (
<ServizioCard
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={
<>
<div className="flex flex-col gap-4">
@ -391,24 +400,15 @@ const ServizioMain = () => {
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
month: "2-digit",
year: "numeric",
})}
</p>
</CardDescription>
</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) {
return (
<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={
<>
<div className="flex w-full flex-col gap-4">
@ -430,40 +464,6 @@ const ServizioMain = () => {
<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) {
return (
<ServizioCard
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
header={
<>
<div className="flex w-full flex-wrap items-center gap-2">
@ -486,9 +497,9 @@ const ServizioMain = () => {
<p>
aggiunto il{" "}
{servizio.created_at.toLocaleDateString("it", {
year: "numeric",
month: "2-digit",
day: "2-digit",
month: "2-digit",
year: "numeric",
})}
</p>
</CardDescription>
@ -497,17 +508,6 @@ const ServizioMain = () => {
<ServizioActions />
</>
}
content={
<>
{annuncioConfermato.map((data) => {
return (
<ServizioAnnuncioProvider data={data} key={data.id}>
<AnnuncioCard />
</ServizioAnnuncioProvider>
);
})}
</>
}
/>
);
}
@ -525,29 +525,6 @@ const ServizioMain = () => {
return (
<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={
<>
<div className="flex flex-col gap-2">
@ -558,7 +535,7 @@ const ServizioMain = () => {
</div>
<span className="text-sm">{openContattiCount} / 10</span>
</div>
<Progress value={openContattiCount * 10} className="h-2" />
<Progress className="h-2" value={openContattiCount * 10} />
</div>
<ServizioDuration decorrenza={servizio.decorrenza} />
@ -573,8 +550,8 @@ const ServizioMain = () => {
.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"
key={a.annunci_id}
>
<h3>
{a.codice} - confermato il:{" "}
@ -582,18 +559,18 @@ const ServizioMain = () => {
{new Date(a.accettato_conferma_at!).toLocaleDateString(
"it",
{
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
month: "2-digit",
year: "numeric",
},
)}
</h3>
<SaldoButton
variant={"outline"}
className="border-none"
annuncioId={a.annunci_id}
className="border-none"
variant={"outline"}
/>
</div>
))}
@ -603,13 +580,36 @@ const ServizioMain = () => {
<div className="space-y-2">
<AnnunciInConferma annunci={annunciInConferma} />
<AnnunciSelezionati
isConfirming={annunciInConferma.length > 0}
annunci={annunciNotInConferma}
isConfirming={annunciInConferma.length > 0}
/>
<AnnunciCompatibili />
</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 (
<Accordion
type="single"
collapsible
defaultValue={isDesktop ? "validity" : undefined}
type="single"
>
<AccordionItem value="validity">
<AccordionTrigger>
@ -35,8 +35,8 @@ export const ServizioDuration = ({
<AccordionContent>
<ServiceDurationDisplay
decorrenza={decorrenza}
scadenza={add(decorrenza, { days: 60 })}
recessoDays={14}
scadenza={add(decorrenza, { days: 60 })}
/>
</AccordionContent>
</AccordionItem>
@ -70,7 +70,7 @@ function ServiceDurationDisplay({
<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 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 className="text-muted-foreground text-sm">Decorrenza</div>
<div className="text-foreground text-lg font-semibold">
@ -84,8 +84,8 @@ function ServiceDurationDisplay({
<div className="flex items-center gap-4">
<CalendarClock
className="text-primary h-6 w-6"
aria-hidden="true"
className="text-primary h-6 w-6"
/>
<div>
<div className="text-muted-foreground text-sm">Scadenza</div>
@ -99,7 +99,7 @@ function ServiceDurationDisplay({
</div>
<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 className="text-muted-foreground text-sm">
Periodo di recesso
@ -114,9 +114,9 @@ function ServiceDurationDisplay({
{/* Progress Bar */}
<div className="pt-2">
<Progress
value={progressPercentage}
className="h-2"
aria-label="Service duration progress"
className="h-2"
value={progressPercentage}
/>
<div className="text-muted-foreground mt-1 text-right text-sm">
{remainingDaysText}

View file

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

View file

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

View file

@ -16,17 +16,17 @@ export const Status500 = () => {
<p className="text-neutral-600">{t[500].sottotitolo}</p>
<div className="flex flex-wrap items-center justify-center gap-3">
<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={() => {
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}
</button>
<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"
href="/"
>
{t[500].home}
</Link>

View file

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

View file

@ -38,48 +38,48 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
ry={13.1}
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_27"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_27"
/>
<path
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"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_28"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_28"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_29"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_29"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_30"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_30"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_31"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_31"
/>
<path
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"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_32"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_32"
/>
<path
className="fill-[#38226d]"
@ -119,10 +119,10 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
ry={13.1}
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_33"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_33"
/>
<path
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"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_34"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_34"
/>
<path
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"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_35"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_35"
/>
<path
className="fill-[#e4e4e7]"
d="M100 400.75s3.73-.84 5.44-2.5c0 0-2.22-7-3.64-8.74S99 399 99 399Z"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_36"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_36"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_37"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_37"
/>
<path
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"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_38"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_38"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_39"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_39"
/>
<path
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"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_40"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_40"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_41"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_41"
/>
<path
className="fill-[#2b2a30]"
@ -292,10 +292,10 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
cy={438.88}
rx={3.9}
ry={6.75}
transform="rotate(-30 319.598 438.896)"
style={{
opacity: 0.1,
}}
transform="rotate(-30 319.598 438.896)"
/>
<path
className="fill-[#b1b4c4]"
@ -327,16 +327,16 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
ry={13.1}
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_54"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_54"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_55"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_55"
/>
<path
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"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_56"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_56"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_57"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_57"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_58"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_58"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_59"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_59"
/>
<path
id="_\u041A\u043E\u043D\u0442\u0443\u0440_60"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
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"
data-name="&lt;\u041A\u043E\u043D\u0442\u0443\u0440&gt;"
id="_\u041A\u043E\u043D\u0442\u0443\u0440_60"
/>
<path
className="fill-[#2b2a30]"
@ -394,10 +394,10 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
export const ASvg = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={80}
height={100}
viewBox="20 0 10 12"
width={80}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<title>{"\u0410"}</title>
@ -422,30 +422,30 @@ export const ASvg = (props: SVGProps<SVGSVGElement>) => (
export const LogoSvg = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={258.758}
height={49.796}
viewBox="0 0 68.463 13.175"
width={258.758}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<title>{"lloggi.it Info"}</title>
<defs>
<path id="a" d="M69.953 191.276h677.665v225.16H69.953z" />
<path id="b" d="M69.953 191.276h677.665v225.16H69.953z" />
<path d="M69.953 191.276h677.665v225.16H69.953z" id="a" />
<path d="M69.953 191.276h677.665v225.16H69.953z" id="b" />
</defs>
<text
xmlSpace="preserve"
className="font-sans"
style={{
fontSize: 40,
lineHeight: 5,
textAlign: "center",
letterSpacing: 1,
wordSpacing: 0,
whiteSpace: "pre",
lineHeight: 5,
strokeWidth: 2,
textAlign: "center",
whiteSpace: "pre",
wordSpacing: 0,
}}
transform="matrix(.26458 0 0 .26458 -98.855 -69.754)"
xmlSpace="preserve"
>
<tspan x={371.603} y={303.296}>
<tspan
@ -458,18 +458,18 @@ export const LogoSvg = (props: SVGProps<SVGSVGElement>) => (
</tspan>
</text>
<text
xmlSpace="preserve"
className="font-sans"
style={{
fontSize: 40,
lineHeight: 5,
textAlign: "center",
letterSpacing: 1,
wordSpacing: 0,
whiteSpace: "pre",
lineHeight: 5,
strokeWidth: 2,
textAlign: "center",
whiteSpace: "pre",
wordSpacing: 0,
}}
transform="matrix(.26458 0 0 .26458 -58.739 -69.754)"
xmlSpace="preserve"
>
<tspan x={336.717} y={303.296}>
<tspan
@ -502,11 +502,11 @@ export const LogoSvg = (props: SVGProps<SVGSVGElement>) => (
export const EngFlag = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
width={800}
height={800}
viewBox="0 0 512 512"
width={800}
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
{...props}
>
<title>EnglishFlag</title>
@ -539,9 +539,9 @@ export const EngFlag = (props: SVGProps<SVGSVGElement>) => (
export const ItaFlag = (props: SVGProps<SVGSVGElement>) => (
<svg
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
viewBox="0 0 512 512"
{...props}
>
<title>ItalianFlag</title>
@ -568,16 +568,16 @@ export const ItaFlag = (props: SVGProps<SVGSVGElement>) => (
export const MissingFile = ({ className }: { className?: string }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className={className}
style={{
clipRule: "evenodd",
fillRule: "evenodd",
imageRendering: "auto",
shapeRendering: "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>
<path
@ -651,11 +651,11 @@ export const MissingFile = ({ className }: { className?: string }) => (
export const WhatsAppIcon = ({ className }: { className?: string }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={800}
className={className}
height={800}
viewBox="0 0 24 24"
className={className}
width={800}
xmlns="http://www.w3.org/2000/svg"
>
<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" />

View file

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

View file

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

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