formatt
This commit is contained in:
parent
f46702275b
commit
5755b00d7b
319 changed files with 9017 additions and 8869 deletions
20
apps/infoalloggi/.vscode/settings.json
vendored
20
apps/infoalloggi/.vscode/settings.json
vendored
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'annuncio →
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'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'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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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/**"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 “{email.title}” 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%"
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ export function AnimatedNumber({
|
|||
onAnimationComplete,
|
||||
}: AnimatedNumberProps) {
|
||||
const spring = useSpring(value, {
|
||||
damping,
|
||||
mass,
|
||||
stiffness,
|
||||
damping,
|
||||
});
|
||||
|
||||
const display: MotionValue<string> = useTransform(spring, (current) =>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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]}
|
||||
|
|
|
|||
|
|
@ -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" ? (
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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")}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
));
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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='© <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 ? (
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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]",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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" ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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'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'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'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'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 />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -38,48 +38,48 @@ export const HeroSvg = (props: SVGProps<SVGSVGElement>) => (
|
|||
ry={13.1}
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_27"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_28"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_29"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_29"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_30"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_30"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_31"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_36"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_37"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_38"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_39"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_40"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_41"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_54"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_55"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_56"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_57"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_57"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_58"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_58"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_59"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_59"
|
||||
/>
|
||||
<path
|
||||
id="_\u041A\u043E\u043D\u0442\u0443\u0440_60"
|
||||
data-name="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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="<\u041A\u043E\u043D\u0442\u0443\u0440>"
|
||||
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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue