feat: update payment recap links and add new receipt page
- Changed payment recap link in OrdiniModal to point to the new receipt page. - Added new PaymentRecapPage for displaying receipt details. - Updated PaymentsTable to set paid_at date based on payment status. - Introduced manual payment type in PaymentType and updated related strings. - Refactored getOrdineRicevutaData to use overridableProcedure for access control. - Enhanced mail sending functionality to include PDF attachments for receipts and conditions. - Updated PDF generation service to handle new receipt PDF generation. - Cleaned up context checking utilities by removing unnecessary session checks.
This commit is contained in:
parent
6c9524e54b
commit
280e0ab5c2
21 changed files with 384 additions and 279 deletions
|
|
@ -64,8 +64,8 @@ const Base = ({
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="mt-3">
|
<Section className="mt-3 text-center">
|
||||||
<Text className="text-left text-sm">
|
<Text className="text-sm">
|
||||||
Arcenia S.r.l. Via Beata Giovanna 1, Bassano del Grappa (VI) -
|
Arcenia S.r.l. Via Beata Giovanna 1, Bassano del Grappa (VI) -
|
||||||
tel: 0424529869
|
tel: 0424529869
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
import { Button, Heading, Row, Section, Text } from "@react-email/components";
|
import { Heading, Row, Section, Text } from "@react-email/components";
|
||||||
import Base from "./base";
|
import Base from "./base";
|
||||||
export type PagamentoConferma_NewMail = {
|
export type PagamentoConferma_NewMail = {
|
||||||
mailType: "pagamentoConferma";
|
mailType: "pagamentoConferma";
|
||||||
props: PagamentoConfermaProps;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type PagamentoConfermaProps = {
|
const PagamentoConferma = () => {
|
||||||
receiptHref: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PagamentoConferma = ({ receiptHref }: PagamentoConfermaProps) => {
|
|
||||||
return (
|
return (
|
||||||
<Base preview="Conferma Pagamento">
|
<Base preview="Conferma Pagamento">
|
||||||
<>
|
<>
|
||||||
|
|
@ -19,20 +14,18 @@ const PagamentoConferma = ({ receiptHref }: PagamentoConfermaProps) => {
|
||||||
<Section className="px-5 text-left text-base md:px-12">
|
<Section className="px-5 text-left text-base md:px-12">
|
||||||
<Row>
|
<Row>
|
||||||
<Text>
|
<Text>
|
||||||
|
{/**TODO correggere */}
|
||||||
Gentile Cliente, la informiamo che il pagamento è stato confermato
|
Gentile Cliente, la informiamo che il pagamento è stato confermato
|
||||||
con successo. Procederemo ad attivare i servizi acquistati se non
|
con successo.
|
||||||
lo fossero già. Può controllare lo stato del suo account nella
|
</Text>
|
||||||
sezione dedicata.
|
<Text>
|
||||||
|
Puoi controllare lo stato del tuo account nella sezione dedicata.
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
In allegato troverai una ricevuta di cortesia, la fattura
|
||||||
|
eletnica arriverà separatamente.
|
||||||
</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
<Section className="mb-5">
|
|
||||||
<Button
|
|
||||||
className="mx-auto box-border inline-flex h-10 items-center justify-center whitespace-nowrap rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold text-neutral-900"
|
|
||||||
href={receiptHref}
|
|
||||||
>
|
|
||||||
Vai al tuo account
|
|
||||||
</Button>
|
|
||||||
</Section>
|
|
||||||
</Section>
|
</Section>
|
||||||
</>
|
</>
|
||||||
</Base>
|
</Base>
|
||||||
|
|
|
||||||
34
apps/infoalloggi/emails/servizio-attivato.tsx
Normal file
34
apps/infoalloggi/emails/servizio-attivato.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Heading, Row, Section, Text } from "@react-email/components";
|
||||||
|
import Base from "./base";
|
||||||
|
export type ServizioAttivato_NewMail = {
|
||||||
|
mailType: "servizioAttivato";
|
||||||
|
};
|
||||||
|
|
||||||
|
const ServizioAttivato = () => {
|
||||||
|
return (
|
||||||
|
<Base noreply preview="Conferma Servizio Attivato">
|
||||||
|
<>
|
||||||
|
<Heading className="text-center text-3xl leading-8">
|
||||||
|
Conferma Attivazione Servizio
|
||||||
|
</Heading>
|
||||||
|
<Section className="px-5 text-left text-base md:px-12">
|
||||||
|
<Row>
|
||||||
|
<Text>
|
||||||
|
Gentile Cliente, ti informiamo che il pagamento è stato confermato
|
||||||
|
con successo e il servizio è stato attivato.
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
Puoi controllare lo stato del tuo account nella sezione dedicata.
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
In allegato troverai una ricevuta di cortesia e le condizioni
|
||||||
|
contrattuali accettate relative al servizio attivato.
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
</Section>
|
||||||
|
</>
|
||||||
|
</Base>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServizioAttivato;
|
||||||
|
|
@ -10,12 +10,21 @@ import { Card } from "~/components/ui/card";
|
||||||
import { PaymentMethodToString, type PaymentType } from "~/i18n/stripe";
|
import { PaymentMethodToString, type PaymentType } from "~/i18n/stripe";
|
||||||
import { formatCurrency } from "~/lib/utils";
|
import { formatCurrency } from "~/lib/utils";
|
||||||
|
|
||||||
export function ReceiptGenerator({ data }: { data: PurchaseData }) {
|
export function ReceiptGenerator({
|
||||||
|
data,
|
||||||
|
raw,
|
||||||
|
}: {
|
||||||
|
data: PurchaseData;
|
||||||
|
raw: boolean;
|
||||||
|
}) {
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
const reactToPrintFn = useReactToPrint({
|
const reactToPrintFn = useReactToPrint({
|
||||||
contentRef,
|
contentRef,
|
||||||
//print: async (target) => {console.log("Printing...", target.contentDocument);},
|
//print: async (target) => {console.log("Printing...", target.contentDocument);},
|
||||||
});
|
});
|
||||||
|
if (raw) {
|
||||||
|
return <Receipt data={data} />;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto max-w-4xl">
|
<div className="mx-auto max-w-4xl">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|
@ -27,7 +36,9 @@ export function ReceiptGenerator({ data }: { data: PurchaseData }) {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div ref={contentRef}>
|
<div ref={contentRef}>
|
||||||
<Receipt data={data} />
|
<Card className="p-4 print:border-none print:shadow-none">
|
||||||
|
<Receipt data={data} />
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -65,123 +76,119 @@ function Receipt({ data }: ReceiptProps) {
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
const note =
|
const note =
|
||||||
"La presente è non costituisce ricevuta o fattura fiscale.\nRiceverai la fattura elettronica direttamente al tuo indirizzo di posta.";
|
"La presente è non costituisce ricevuta o fattura fiscale ai sensi dell'Art.21, DPR 633/72.\nSeguirà fattura elettronica direttamente al tuo indirizzo di posta.";
|
||||||
const footer =
|
const footer =
|
||||||
"Arcenia Srl. | Tel. +39 0424529869 | Email: arca@infoalloggi.it";
|
"Arcenia Srl. | Tel. +39 0424529869 | Email: arca@infoalloggi.it";
|
||||||
return (
|
return (
|
||||||
<Card className="p-4 print:border-none print:shadow-none">
|
<div className="space-y-6">
|
||||||
<div className="space-y-6">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex items-start justify-between">
|
<div>
|
||||||
<div>
|
<h1 className="font-bold text-2xl">{companyName}</h1>
|
||||||
<h1 className="font-bold text-2xl">{companyName}</h1>
|
<p className="whitespace-pre-line text-sm">{companyAddress}</p>
|
||||||
<p className="whitespace-pre-line text-sm">{companyAddress}</p>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative size-24">
|
<div className="relative size-24">
|
||||||
<Image
|
<Image
|
||||||
alt={`${companyName} logo`}
|
alt={`${companyName} logo`}
|
||||||
className="object-contain"
|
className="object-contain"
|
||||||
fill
|
fill
|
||||||
src={"/favicon/favicon.png"}
|
src={"/favicon/favicon.png"}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="border-b pb-4">
|
</div>
|
||||||
<h2 className="text-center font-semibold text-xl">
|
<div className="border-b pb-4">
|
||||||
Ricevuta di cortesia
|
<h2 className="text-center font-semibold text-xl">
|
||||||
</h2>
|
Ricevuta di cortesia
|
||||||
<div className="mt-2 flex justify-between">
|
</h2>
|
||||||
<div>
|
<div className="mt-2 flex justify-between">
|
||||||
<p className="text-muted-foreground text-sm">ID Pagamento</p>
|
|
||||||
<p className="text-sm">{data.paymentId}</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<p className="text-muted-foreground text-sm">Data</p>
|
|
||||||
<p className="font-medium">
|
|
||||||
{format(data.date, "PPP", {
|
|
||||||
locale: it,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm">
|
|
||||||
{format(data.date, "p", {
|
|
||||||
locale: it,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-6">
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-1 font-semibold">Intestatario:</h3>
|
<p className="text-muted-foreground text-sm">ID Pagamento</p>
|
||||||
<p className="font-medium">{data.clienteNome}</p>
|
<p className="text-sm">{data.paymentId}</p>
|
||||||
<p className="whitespace-pre-line text-sm">
|
</div>
|
||||||
{data.clienteIndirizzo}
|
<div className="text-right">
|
||||||
|
<p className="text-muted-foreground text-sm">Data</p>
|
||||||
|
<p className="font-medium">
|
||||||
|
{format(data.date, "PPP", {
|
||||||
|
locale: it,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
{format(data.date, "p", {
|
||||||
|
locale: it,
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<table className="w-full">
|
<h3 className="mb-1 font-semibold">Intestatario:</h3>
|
||||||
<thead>
|
<p className="font-medium">{data.clienteNome}</p>
|
||||||
<tr className="border-b">
|
<p className="whitespace-pre-line text-sm">{data.clienteIndirizzo}</p>
|
||||||
<th className="py-2 text-left">Descrizione</th>
|
|
||||||
<th className="py-2 text-right">Prezzo</th>
|
|
||||||
<th className="py-2 text-right">Sconto</th>
|
|
||||||
<th className="py-2 text-right">Importo</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data.items.map((item) => (
|
|
||||||
<tr className="border-b" key={item.description}>
|
|
||||||
<td className="py-2">{item.description}</td>
|
|
||||||
<td className="py-2 text-right">
|
|
||||||
{formatCurrency(item.price)}
|
|
||||||
</td>
|
|
||||||
<td className="py-2 text-right">
|
|
||||||
{item.discount > 0
|
|
||||||
? `-${(item.discount * 100).toFixed(0)}%`
|
|
||||||
: ""}
|
|
||||||
</td>
|
|
||||||
<td className="py-2 text-right">
|
|
||||||
{formatCurrency(item.price * (1 - item.discount))}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<div className="w-64">
|
|
||||||
<div className="mt-1 flex justify-between pt-2 font-bold">
|
|
||||||
<span>Totale:</span>
|
|
||||||
<span>{formatCurrency(total)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between py-1">
|
|
||||||
<span>di cui IVA (22%):</span>
|
|
||||||
<span>{formatCurrency(total * 0.22)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="border-t pt-4">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 className="mb-1 font-semibold">Metodo di pagamento</h3>
|
|
||||||
<p>{PaymentMethodToString(data.paymentMethod as PaymentType)}</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<h3 className="mb-1 font-semibold">Status pagamento</h3>
|
|
||||||
<p className="font-medium text-green-600">Pagato</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="border-t pt-4">
|
|
||||||
<h3 className="mb-1 font-semibold">Note</h3>
|
|
||||||
<p className="whitespace-pre-line text-sm">{note}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-6 text-center text-muted-foreground text-sm">
|
|
||||||
<p>{footer}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
<div>
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b">
|
||||||
|
<th className="py-2 text-left">Descrizione</th>
|
||||||
|
<th className="py-2 text-right">Prezzo</th>
|
||||||
|
<th className="py-2 text-right">Sconto</th>
|
||||||
|
<th className="py-2 text-right">Importo</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.items.map((item) => (
|
||||||
|
<tr className="border-b" key={item.description}>
|
||||||
|
<td className="py-2">{item.description}</td>
|
||||||
|
<td className="py-2 text-right">
|
||||||
|
{formatCurrency(item.price)}
|
||||||
|
</td>
|
||||||
|
<td className="py-2 text-right">
|
||||||
|
{item.discount > 0
|
||||||
|
? `-${(item.discount * 100).toFixed(0)}%`
|
||||||
|
: ""}
|
||||||
|
</td>
|
||||||
|
<td className="py-2 text-right">
|
||||||
|
{formatCurrency(item.price * (1 - item.discount))}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<div className="w-64">
|
||||||
|
<div className="mt-1 flex justify-between pt-2 font-bold">
|
||||||
|
<span>Totale:</span>
|
||||||
|
<span>{formatCurrency(total)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between py-1">
|
||||||
|
<span>di cui IVA (22%):</span>
|
||||||
|
<span>{formatCurrency(total * 0.22)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border-t pt-4">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-1 font-semibold">Metodo di pagamento</h3>
|
||||||
|
<p>{PaymentMethodToString(data.paymentMethod as PaymentType)}</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<h3 className="mb-1 font-semibold">Status pagamento</h3>
|
||||||
|
<p className="font-medium text-green-600">Pagato</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border-t pt-4">
|
||||||
|
<h3 className="mb-1 font-semibold">Note</h3>
|
||||||
|
<p className="whitespace-pre-line text-sm">{note}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-6 text-center text-muted-foreground text-sm">
|
||||||
|
<p>{footer}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ export const OrdiniModal = ({
|
||||||
) && (
|
) && (
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
href={`/area-riservata/payment-recap/${data.ordine_id}`}
|
href={`/servizio/ricevuta-cortesia/${data.ordine_id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Button variant="outline">
|
<Button variant="outline">
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,10 @@ export const PaymentsTable = ({
|
||||||
<DropdownMenuRadioGroup
|
<DropdownMenuRadioGroup
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
update({
|
update({
|
||||||
data: { paymentstatus: EnumFromString(v) },
|
data: {
|
||||||
|
paymentstatus: EnumFromString(v),
|
||||||
|
paid_at: v === "Successo" ? new Date() : null,
|
||||||
|
},
|
||||||
pagamentoId: data.id,
|
pagamentoId: data.id,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export type PaymentType =
|
export type PaymentType =
|
||||||
|
| "manual"
|
||||||
| "acss_debit"
|
| "acss_debit"
|
||||||
| "affirm"
|
| "affirm"
|
||||||
| "afterpay_clearpay"
|
| "afterpay_clearpay"
|
||||||
|
|
@ -50,6 +51,8 @@ export type PaymentType =
|
||||||
| "zip";
|
| "zip";
|
||||||
export const PaymentMethodToString = (type: PaymentType) => {
|
export const PaymentMethodToString = (type: PaymentType) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case "manual":
|
||||||
|
return "Pagamento in sede";
|
||||||
case "card":
|
case "card":
|
||||||
return "Carta di credito";
|
return "Carta di credito";
|
||||||
case "sepa_debit":
|
case "sepa_debit":
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { LoadingPage } from "~/components/loading";
|
||||||
import { Status500 } from "~/components/status-page";
|
import { Status500 } from "~/components/status-page";
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { generateSSGHelper } from "~/server/utils/ssgHelper";
|
||||||
import { zTestiEStringheStingaId } from "~/server/utils/zod_types";
|
import { zTestiEStringheStingaId } from "~/server/utils/zod_types";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
|
|
@ -38,6 +38,9 @@ const CondizioniPage: NextPageWithLayout<CondizioniProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
CondizioniPage.getLayout = function getLayout(page) {
|
CondizioniPage.getLayout = function getLayout(page) {
|
||||||
|
if (page.props.raw) {
|
||||||
|
return <>{page}</>;
|
||||||
|
}
|
||||||
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -45,7 +48,7 @@ export default CondizioniPage;
|
||||||
|
|
||||||
export const getServerSideProps = (async (context) => {
|
export const getServerSideProps = (async (context) => {
|
||||||
const id = context.params?.stringaId;
|
const id = context.params?.stringaId;
|
||||||
|
const raw = context.query?.raw;
|
||||||
if (typeof id !== "string") {
|
if (typeof id !== "string") {
|
||||||
console.error("Error: stringaId is not a string");
|
console.error("Error: stringaId is not a string");
|
||||||
return {
|
return {
|
||||||
|
|
@ -67,18 +70,17 @@ export const getServerSideProps = (async (context) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const access_token = context.req.cookies.access_token;
|
const helper = generateSSGHelper();
|
||||||
|
|
||||||
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
|
||||||
if (helper) {
|
if (helper) {
|
||||||
await helper.trpc.strings.getStringa.prefetch({
|
await helper.strings.getStringa.prefetch({
|
||||||
stringaId: parsed.data,
|
stringaId: parsed.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
raw: raw === "true",
|
||||||
stringaId: parsed.data,
|
stringaId: parsed.data,
|
||||||
trpcState: helper.trpc.dehydrate(),
|
trpcState: helper.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,17 @@ import { LoadingPage } from "~/components/loading";
|
||||||
import { Status500 } from "~/components/status-page";
|
import { Status500 } from "~/components/status-page";
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
import { TrpcAuthedFetchingIstance } from "~/server/utils/ssgHelper";
|
import { generateSSGHelper } from "~/server/utils/ssgHelper";
|
||||||
import { zOrdineId } from "~/server/utils/zod_types";
|
import { zOrdineId } from "~/server/utils/zod_types";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
type PagamentoRecapProps = {
|
type PagamentoRecapProps = {
|
||||||
|
raw: boolean;
|
||||||
ordineId: OrdiniOrdineId;
|
ordineId: OrdiniOrdineId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PaymentRecapPage: NextPageWithLayout<PagamentoRecapProps> = ({
|
const PaymentRecapPage: NextPageWithLayout<PagamentoRecapProps> = ({
|
||||||
|
raw,
|
||||||
ordineId,
|
ordineId,
|
||||||
}: PagamentoRecapProps) => {
|
}: PagamentoRecapProps) => {
|
||||||
const { data, isLoading } = api.servizio.getOrdineRicevutaData.useQuery({
|
const { data, isLoading } = api.servizio.getOrdineRicevutaData.useQuery({
|
||||||
|
|
@ -28,12 +30,15 @@ const PaymentRecapPage: NextPageWithLayout<PagamentoRecapProps> = ({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full p-4">
|
<div className="mx-auto w-full p-4">
|
||||||
<ReceiptGenerator data={data} />
|
<ReceiptGenerator data={data} raw={raw} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PaymentRecapPage.getLayout = function getLayout(page) {
|
PaymentRecapPage.getLayout = function getLayout(page) {
|
||||||
|
if (page.props.raw) {
|
||||||
|
return <>{page}</>;
|
||||||
|
}
|
||||||
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
return <AreaRiservataLayout noSidebar>{page}</AreaRiservataLayout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -41,7 +46,7 @@ export default PaymentRecapPage;
|
||||||
|
|
||||||
export const getServerSideProps = (async (context) => {
|
export const getServerSideProps = (async (context) => {
|
||||||
const id = context.params?.ordineId;
|
const id = context.params?.ordineId;
|
||||||
|
const raw = context.query?.raw;
|
||||||
if (typeof id !== "string") {
|
if (typeof id !== "string") {
|
||||||
console.error("Error: ordineId is not a string");
|
console.error("Error: ordineId is not a string");
|
||||||
return {
|
return {
|
||||||
|
|
@ -63,18 +68,17 @@ export const getServerSideProps = (async (context) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const access_token = context.req.cookies.access_token;
|
const helper = generateSSGHelper();
|
||||||
|
|
||||||
const helper = await TrpcAuthedFetchingIstance({ access_token });
|
|
||||||
if (helper) {
|
if (helper) {
|
||||||
await helper.trpc.servizio.getOrdineRicevutaData.prefetch({
|
await helper.servizio.getOrdineRicevutaData.prefetch({
|
||||||
ordineId: parsed.data,
|
ordineId: parsed.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
raw: raw === "true",
|
||||||
ordineId: parsed.data,
|
ordineId: parsed.data,
|
||||||
trpcState: helper.trpc.dehydrate(),
|
trpcState: helper.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -9,8 +9,12 @@ import { signToken, verifyToken } from "~/server/auth/jwt";
|
||||||
export const authProxy: ProxyFn = async (req: NextRequest) => {
|
export const authProxy: ProxyFn = async (req: NextRequest) => {
|
||||||
const path = req.nextUrl.pathname;
|
const path = req.nextUrl.pathname;
|
||||||
|
|
||||||
|
const isOverridableRoute =
|
||||||
|
path.startsWith("/area-riservata/admin/scheda-annuncio-stampa/") ||
|
||||||
|
path.startsWith("/servizio/condizioni/") ||
|
||||||
|
path.startsWith("/servizio/ricevuta-cortesia/");
|
||||||
// permette a puppeteer di accedere alla pagina di generazione PDF bypassando l'autenticazione normale
|
// permette a puppeteer di accedere alla pagina di generazione PDF bypassando l'autenticazione normale
|
||||||
if (path.startsWith("/area-riservata/admin/scheda-annuncio-stampa/")) {
|
if (isOverridableRoute) {
|
||||||
const overrideToken = req.cookies.get(
|
const overrideToken = req.cookies.get(
|
||||||
TOKEN_CONFIG.OVERRIDE_TOKEN_COOKIE_NAME,
|
TOKEN_CONFIG.OVERRIDE_TOKEN_COOKIE_NAME,
|
||||||
)?.value;
|
)?.value;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
||||||
import {
|
import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
|
overridableProcedure,
|
||||||
protectedProcedure,
|
protectedProcedure,
|
||||||
publicProcedure,
|
publicProcedure,
|
||||||
} from "~/server/api/trpc";
|
} from "~/server/api/trpc";
|
||||||
|
|
@ -169,14 +170,14 @@ export const servizioRouter = createTRPCRouter({
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getOrdineById(input.ordineId);
|
return await getOrdineById(input.ordineId);
|
||||||
}),
|
}),
|
||||||
getOrdineRicevutaData: protectedProcedure
|
getOrdineRicevutaData: overridableProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
ordineId: zOrdineId,
|
ordineId: zOrdineId,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input }) => {
|
||||||
return await getOrdineRicevutaData({ ctx, ordineId: input.ordineId });
|
return await getOrdineRicevutaData({ ordineId: input.ordineId });
|
||||||
}),
|
}),
|
||||||
getServizioOrdini: protectedProcedure
|
getServizioOrdini: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,10 @@ export const testRouter = createTRPCRouter({
|
||||||
console.error("test console.error");
|
console.error("test console.error");
|
||||||
throw new Error("This is a test error");
|
throw new Error("This is a test error");
|
||||||
}),
|
}),
|
||||||
|
});
|
||||||
/*
|
/*
|
||||||
PROTECTED API CALL EXAMPLE
|
PROTECTED API CALL EXAMPLE
|
||||||
curl --location 'localhost:3000/api/trpc/test.testApi' \
|
curl --location 'localhost:3000/api/trpc/test.testApi' \
|
||||||
--header 'Authorization: Basic aW5mb2FsbG9nZ2ktYWRtaW46Y3lZazI0dTlmUm41RHNxakZXcFRWOEdLQk53aFF2RXRiTW1IeEplVTczWlNhWHJkQ0xBUGc2TnREa0M0M1pRYTlkVktIYnhNdjdmV3B5NjVnRm1Bc0VSZVh3dVBZVEw4MmpKblNVaHJ6Y3FHa3F2c0hQeXhtRUFGellWU250R2VRVHIyNWRKYjNVTkNNdVJwZ3dhRFc2OVpoYzdLajhMZkI0WkNIUjlrWWZXVnNHcng3dHlncWR2YzgyU0ZYajU0TmhucG02UEVCTGJLVXVhTVRRSmVBM3dEZ1BTQ1h3RFZtaG5lZDZKOFpRM3F6TnNyNWJCdUx0SHh2RlRBS0d5NzRXMmZZOXBja2pNUlVFSllSa1ZQWndidkJ0VEt5MkY4VUxBc2V1bnpXajdHNWQ2UTNFeGZtTkNYcWc0TURhaEg5Y1NydVh5V3hMTkFzR1FUUFNGaEQ5SFlWekIzTWVnMnJrNUN2YWNmcThtZFo2dFVFNEtScHdibjdqSkFldjdXUmJWZ1BkbmNNTEt4d204eVQ5cFFaNEROVWthWWo2enRYZmhzQkhxRjVFckN1MzJHY2piM0g2S3h2V3N1OXA1ZmtQcmg3WkFSWE1MRGRTODJlbkZVNENxd3p0VGF5RW1HTlZnWUpC'
|
--header 'Authorization: Basic aW5mb2FsbG9nZ2ktYWRtaW46Y3lZazI0dTlmUm41RHNxakZXcFRWOEdLQk53aFF2RXRiTW1IeEplVTczWlNhWHJkQ0xBUGc2TnREa0M0M1pRYTlkVktIYnhNdjdmV3B5NjVnRm1Bc0VSZVh3dVBZVEw4MmpKblNVaHJ6Y3FHa3F2c0hQeXhtRUFGellWU250R2VRVHIyNWRKYjNVTkNNdVJwZ3dhRFc2OVpoYzdLajhMZkI0WkNIUjlrWWZXVnNHcng3dHlncWR2YzgyU0ZYajU0TmhucG02UEVCTGJLVXVhTVRRSmVBM3dEZ1BTQ1h3RFZtaG5lZDZKOFpRM3F6TnNyNWJCdUx0SHh2RlRBS0d5NzRXMmZZOXBja2pNUlVFSllSa1ZQWndidkJ0VEt5MkY4VUxBc2V1bnpXajdHNWQ2UTNFeGZtTkNYcWc0TURhaEg5Y1NydVh5V3hMTkFzR1FUUFNGaEQ5SFlWekIzTWVnMnJrNUN2YWNmcThtZFo2dFVFNEtScHdibjdqSkFldjdXUmJWZ1BkbmNNTEt4d204eVQ5cFFaNEROVWthWWo2enRYZmhzQkhxRjVFckN1MzJHY2piM0g2S3h2V3N1OXA1ZmtQcmg3WkFSWE1MRGRTODJlbkZVNENxd3p0VGF5RW1HTlZnWUpC'
|
||||||
|
|
||||||
*/
|
*/
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import {
|
||||||
} from "~/server/controllers/user.controller";
|
} from "~/server/controllers/user.controller";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import { getClientProfilo } from "~/server/services/user.service";
|
import { getClientProfilo } from "~/server/services/user.service";
|
||||||
import { checkCtxSession } from "~/server/utils/ctxChecker";
|
|
||||||
import { zUserId } from "~/server/utils/zod_types";
|
import { zUserId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
export const usersRouter = createTRPCRouter({
|
export const usersRouter = createTRPCRouter({
|
||||||
|
|
@ -27,8 +26,8 @@ export const usersRouter = createTRPCRouter({
|
||||||
id: zUserId,
|
id: zUserId,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await blockUserHandler({ ctx, id: input.id });
|
return await blockUserHandler(input.id);
|
||||||
}),
|
}),
|
||||||
deleteUser: adminProcedure
|
deleteUser: adminProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|
@ -36,8 +35,8 @@ export const usersRouter = createTRPCRouter({
|
||||||
id: zUserId,
|
id: zUserId,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return await deleteUserHandler({ ctx, id: input.id });
|
return await deleteUserHandler(input.id);
|
||||||
}),
|
}),
|
||||||
editUser: protectedProcedure
|
editUser: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|
@ -46,8 +45,7 @@ export const usersRouter = createTRPCRouter({
|
||||||
data: z.custom<UsersUpdate>(),
|
data: z.custom<UsersUpdate>(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ input }) => {
|
||||||
checkCtxSession({ ctx });
|
|
||||||
return await editAccountHandler({ ...input });
|
return await editAccountHandler({ ...input });
|
||||||
}),
|
}),
|
||||||
editUserAnagrafica: protectedProcedure
|
editUserAnagrafica: protectedProcedure
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,33 @@ const enforceUserIsAuthed = t.middleware(({ ctx, next, path }) => {
|
||||||
message: `User is not logged, error in path: ${path}`,
|
message: `User is not logged, error in path: ${path}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return next({
|
||||||
|
ctx: {
|
||||||
|
// infers the `session` as non-nullable
|
||||||
|
session: { ...ctx.session },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const enforceUserIsAuthedOrOverride = t.middleware(({ ctx, next, path }) => {
|
||||||
|
const overrideToken =
|
||||||
|
ctx.req?.cookies[TOKEN_CONFIG.OVERRIDE_TOKEN_COOKIE_NAME];
|
||||||
|
if (overrideToken && overrideToken === env.EXP_API_PASS) {
|
||||||
|
return next({
|
||||||
|
ctx: {
|
||||||
|
// infers the `session` as non-nullable
|
||||||
|
session: { ...ctx.session },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!ctx.session) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: `User is not logged, error in path: ${path}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return next({
|
return next({
|
||||||
ctx: {
|
ctx: {
|
||||||
// infers the `session` as non-nullable
|
// infers the `session` as non-nullable
|
||||||
|
|
@ -158,6 +185,7 @@ const enforceUserIsAuthed = t.middleware(({ ctx, next, path }) => {
|
||||||
* @see https://trpc.io/docs/procedures
|
* @see https://trpc.io/docs/procedures
|
||||||
*/
|
*/
|
||||||
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
|
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
|
||||||
|
export const overridableProcedure = t.procedure.use(enforceUserIsAuthedOrOverride);
|
||||||
|
|
||||||
/** Reusable middleware that enforces users are logged in before running the procedure. */
|
/** Reusable middleware that enforces users are logged in before running the procedure. */
|
||||||
const enforceUserIsAdmin = t.middleware(({ ctx, next, path }) => {
|
const enforceUserIsAdmin = t.middleware(({ ctx, next, path }) => {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import type {
|
||||||
OrdiniOrdineId,
|
OrdiniOrdineId,
|
||||||
OrdiniUpdate,
|
OrdiniUpdate,
|
||||||
} from "~/schemas/public/Ordini";
|
} from "~/schemas/public/Ordini";
|
||||||
|
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import { db, type Querier } from "~/server/db";
|
import { db, type Querier } from "~/server/db";
|
||||||
|
|
||||||
|
|
@ -67,11 +68,36 @@ export const getOrdineById = async (ordineId: OrdiniOrdineId) => {
|
||||||
|
|
||||||
export const createOrdine = async (data: NewOrdini) => {
|
export const createOrdine = async (data: NewOrdini) => {
|
||||||
try {
|
try {
|
||||||
return await db
|
return await db.transaction().execute(async (tx) => {
|
||||||
.insertInto("ordini")
|
const prezzo = await tx
|
||||||
.values(data)
|
.selectFrom("prezziario")
|
||||||
.returningAll()
|
.select(["prezzo_cent", "nome_it"])
|
||||||
.executeTakeFirstOrThrow();
|
.where("prezziario.idprezziario", "=", data.packid)
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
|
const order = await tx
|
||||||
|
.insertInto("ordini")
|
||||||
|
.values(data)
|
||||||
|
.returningAll()
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
await tx
|
||||||
|
.insertInto("payments")
|
||||||
|
.values({
|
||||||
|
ordine_id: order.ordine_id,
|
||||||
|
servizio_id: order.servizio_id,
|
||||||
|
userid: order.userid,
|
||||||
|
amount_cent: prezzo.prezzo_cent,
|
||||||
|
paymentname: prezzo.nome_it,
|
||||||
|
paymentmethod: "manual",
|
||||||
|
paymentstatus: data.isActive
|
||||||
|
? PaymentStatusEnum.success
|
||||||
|
: PaymentStatusEnum.processing,
|
||||||
|
paid_at: data.isActive ? new Date() : null,
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return order;
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
import type { Users, UsersId } from "~/schemas/public/Users";
|
import type { Users, UsersId } from "~/schemas/public/Users";
|
||||||
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
||||||
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||||
import type { Context } from "~/server/api/trpc";
|
|
||||||
import {
|
import {
|
||||||
addEventToQueue,
|
addEventToQueue,
|
||||||
canSendNotification,
|
canSendNotification,
|
||||||
|
|
@ -882,8 +881,8 @@ export const getAllServizioAnnunci = async (
|
||||||
withVideos(),
|
withVideos(),
|
||||||
])
|
])
|
||||||
//ignora annunci che non sono disponibili
|
//ignora annunci che non sono disponibili
|
||||||
.where("annunci.web", "=", true)
|
//.where("annunci.web", "=", true)
|
||||||
.where("annunci.stato", "!=", "Sospeso")
|
//.where("annunci.stato", "!=", "Sospeso")
|
||||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
||||||
ob.asc().nullsLast(),
|
ob.asc().nullsLast(),
|
||||||
)
|
)
|
||||||
|
|
@ -1035,8 +1034,8 @@ export const getServizioDataById = async (
|
||||||
withVideos(),
|
withVideos(),
|
||||||
])
|
])
|
||||||
//ignora annunci che non sono disponibili
|
//ignora annunci che non sono disponibili
|
||||||
.where("annunci.web", "=", true)
|
//.where("annunci.web", "=", true)
|
||||||
.where("annunci.stato", "!=", "Sospeso")
|
//.where("annunci.stato", "!=", "Sospeso")
|
||||||
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
.orderBy("servizio_annunci.user_confirmed_at", (ob) =>
|
||||||
ob.asc().nullsLast(),
|
ob.asc().nullsLast(),
|
||||||
)
|
)
|
||||||
|
|
@ -2025,10 +2024,8 @@ export const getCompatibileAnnunci = async ({
|
||||||
|
|
||||||
export const getOrdineRicevutaData = async ({
|
export const getOrdineRicevutaData = async ({
|
||||||
ordineId,
|
ordineId,
|
||||||
ctx,
|
|
||||||
}: {
|
}: {
|
||||||
ordineId: OrdiniOrdineId;
|
ordineId: OrdiniOrdineId;
|
||||||
ctx: Context;
|
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const data = await db
|
const data = await db
|
||||||
|
|
@ -2070,19 +2067,6 @@ export const getOrdineRicevutaData = async ({
|
||||||
|
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
if (!ctx.session) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "User not authenticated",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!ctx.session.isAdmin && data.userid !== ctx.session.id) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You do not have permission to access this ordine",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data || !data.user || !data.anagrafica) {
|
if (!data || !data.user || !data.anagrafica) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,15 @@ import stripe from "~/lib/stripe";
|
||||||
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
|
||||||
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
|
||||||
import type { Payments, PaymentsId } from "~/schemas/public/Payments";
|
import type { Payments, PaymentsId } from "~/schemas/public/Payments";
|
||||||
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import { NewMail } from "~/server/services/mailer";
|
import { NewMail } from "~/server/services/mailer";
|
||||||
import { getUser } from "~/server/services/user.service";
|
import { getUser } from "~/server/services/user.service";
|
||||||
|
import {
|
||||||
|
genPdfCondizioniBase64,
|
||||||
|
genPdfRicevutaCortesiaBase64,
|
||||||
|
} from "../services/puppeteer.service";
|
||||||
|
|
||||||
export const stripeHealthCheck = async () => {
|
export const stripeHealthCheck = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -162,23 +167,31 @@ export const whIntentSucceededHandler = async ({
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
|
//SERVIZIO ATTIVATO
|
||||||
await NewMail({
|
await NewMail({
|
||||||
template: {
|
template: {
|
||||||
mailType: "generic",
|
mailType: "servizioAttivato",
|
||||||
|
|
||||||
props: {
|
|
||||||
link: {
|
|
||||||
href: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`,
|
|
||||||
label: "Vai alla ricevuta",
|
|
||||||
},
|
|
||||||
noreply: true,
|
|
||||||
testo: `Il pagamento dell'acconto per il servizio ${ordine.packid} è stato confermato.`,
|
|
||||||
title: "Pagamento Confermato ",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mail: {
|
mail: {
|
||||||
subject: "Pagamento Confermato - Acconto Infoalloggi.it",
|
subject: "Pagamento Confermato - Acconto Infoalloggi.it",
|
||||||
to: utente.email,
|
to: utente.email,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
filename: `condizioni_contrattuali_${ordine.created_at.toISOString()}.pdf`,
|
||||||
|
//ATTENTION: hardcoded condizioni key
|
||||||
|
content: await genPdfCondizioniBase64(
|
||||||
|
"CONDIZIONI_CERCHI" as TestiEStringheStingaId,
|
||||||
|
),
|
||||||
|
encoding: "base64",
|
||||||
|
contentType: "application/pdf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
|
||||||
|
content: await genPdfRicevutaCortesiaBase64(ordine.ordine_id),
|
||||||
|
encoding: "base64",
|
||||||
|
contentType: "application/pdf",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
userId: utente.id,
|
userId: utente.id,
|
||||||
});
|
});
|
||||||
|
|
@ -196,20 +209,19 @@ export const whIntentSucceededHandler = async ({
|
||||||
.execute();
|
.execute();
|
||||||
await NewMail({
|
await NewMail({
|
||||||
template: {
|
template: {
|
||||||
mailType: "generic",
|
mailType: "pagamentoConferma",
|
||||||
props: {
|
|
||||||
link: {
|
|
||||||
href: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`,
|
|
||||||
label: "Vai alla ricevuta",
|
|
||||||
},
|
|
||||||
noreply: true,
|
|
||||||
testo: `Il pagamento del saldo per il servizio ${ordine.packid} è stato confermato.`,
|
|
||||||
title: "Pagamento Confermato ",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mail: {
|
mail: {
|
||||||
subject: "Pagamento Confermato - Saldo Infoalloggi.it",
|
subject: "Pagamento Confermato - Saldo Infoalloggi.it",
|
||||||
to: utente.email,
|
to: utente.email,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
|
||||||
|
content: await genPdfRicevutaCortesiaBase64(ordine.ordine_id),
|
||||||
|
encoding: "base64",
|
||||||
|
contentType: "application/pdf",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
userId: utente.id,
|
userId: utente.id,
|
||||||
});
|
});
|
||||||
|
|
@ -224,20 +236,19 @@ export const whIntentSucceededHandler = async ({
|
||||||
.execute();
|
.execute();
|
||||||
await NewMail({
|
await NewMail({
|
||||||
template: {
|
template: {
|
||||||
mailType: "generic",
|
mailType: "pagamentoConferma",
|
||||||
props: {
|
|
||||||
link: {
|
|
||||||
href: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`,
|
|
||||||
label: "Vai alla ricevuta",
|
|
||||||
},
|
|
||||||
noreply: true,
|
|
||||||
testo: `Il pagamento per la consulenza è stato confermato.`,
|
|
||||||
title: "Pagamento Confermato ",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mail: {
|
mail: {
|
||||||
subject: "Pagamento Confermato - Consulenza Infoalloggi.it",
|
subject: "Pagamento Confermato - Consulenza Infoalloggi.it",
|
||||||
to: utente.email,
|
to: utente.email,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
|
||||||
|
content: await genPdfRicevutaCortesiaBase64(ordine.ordine_id),
|
||||||
|
encoding: "base64",
|
||||||
|
contentType: "application/pdf",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
userId: utente.id,
|
userId: utente.id,
|
||||||
});
|
});
|
||||||
|
|
@ -247,13 +258,18 @@ export const whIntentSucceededHandler = async ({
|
||||||
await NewMail({
|
await NewMail({
|
||||||
template: {
|
template: {
|
||||||
mailType: "pagamentoConferma",
|
mailType: "pagamentoConferma",
|
||||||
props: {
|
|
||||||
receiptHref: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mail: {
|
mail: {
|
||||||
subject: "Pagamento Confermato",
|
subject: "Pagamento Confermato - Infoalloggi.it",
|
||||||
to: utente.email,
|
to: utente.email,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
filename: `ricevuta_cortesia_${ordine.ordine_id}.pdf`,
|
||||||
|
content: await genPdfRicevutaCortesiaBase64(ordine.ordine_id),
|
||||||
|
encoding: "base64",
|
||||||
|
contentType: "application/pdf",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
userId: utente.id,
|
userId: utente.id,
|
||||||
});
|
});
|
||||||
|
|
@ -294,7 +310,7 @@ export const whIntentFailedHandler = async ({
|
||||||
mailType: "pagamentoErrore",
|
mailType: "pagamentoErrore",
|
||||||
},
|
},
|
||||||
mail: {
|
mail: {
|
||||||
subject: "Pagamento Fallito",
|
subject: "Pagamento Fallito - Infoalloggi.it",
|
||||||
to: utente.email,
|
to: utente.email,
|
||||||
},
|
},
|
||||||
userId: utente.id,
|
userId: utente.id,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import type { UsersId, UsersUpdate } from "~/schemas/public/Users";
|
import type { UsersId, UsersUpdate } from "~/schemas/public/Users";
|
||||||
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
||||||
import type { Context } from "~/server/api/trpc";
|
|
||||||
import { db, type Querier } from "~/server/db";
|
import { db, type Querier } from "~/server/db";
|
||||||
import { checkCtxAdmin } from "~/server/utils/ctxChecker";
|
|
||||||
|
|
||||||
export const editAccountHandler = async ({
|
export const editAccountHandler = async ({
|
||||||
id,
|
id,
|
||||||
|
|
@ -92,15 +90,7 @@ export const editAnagraficaHandler = async ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteUserHandler = async ({
|
export const deleteUserHandler = async (id: UsersId) => {
|
||||||
ctx,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
ctx: Context;
|
|
||||||
id: UsersId;
|
|
||||||
}) => {
|
|
||||||
checkCtxAdmin({ ctx });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.deleteFrom("users").where("id", "=", id).execute();
|
await db.deleteFrom("users").where("id", "=", id).execute();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
|
@ -112,14 +102,7 @@ export const deleteUserHandler = async ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const blockUserHandler = async ({
|
export const blockUserHandler = async (id: UsersId) => {
|
||||||
ctx,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
ctx: Context;
|
|
||||||
id: UsersId;
|
|
||||||
}) => {
|
|
||||||
checkCtxAdmin({ ctx });
|
|
||||||
return await db.transaction().execute(async (trx) => {
|
return await db.transaction().execute(async (trx) => {
|
||||||
return await trx
|
return await trx
|
||||||
.updateTable("users")
|
.updateTable("users")
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ import EmailSchedaContatto, {
|
||||||
} from "emails/scheda-annuncio";
|
} from "emails/scheda-annuncio";
|
||||||
import type { ContattiConScheda_NewMail } from "emails/scheda-contatti";
|
import type { ContattiConScheda_NewMail } from "emails/scheda-contatti";
|
||||||
import EmailContattiConScheda from "emails/scheda-contatti";
|
import EmailContattiConScheda from "emails/scheda-contatti";
|
||||||
|
import type { ServizioAttivato_NewMail } from "emails/servizio-attivato";
|
||||||
|
import ServizioAttivato from "emails/servizio-attivato";
|
||||||
import ShareAnnunci, { type ShareAnnunci_NewMail } from "emails/share-annunci";
|
import ShareAnnunci, { type ShareAnnunci_NewMail } from "emails/share-annunci";
|
||||||
import VerificaEmail, {
|
import VerificaEmail, {
|
||||||
type VerificaEmail_NewMail,
|
type VerificaEmail_NewMail,
|
||||||
|
|
@ -93,7 +95,8 @@ export type MailsTemplates =
|
||||||
| Generic_NewMail
|
| Generic_NewMail
|
||||||
| SchedaContatto_NewMail
|
| SchedaContatto_NewMail
|
||||||
| Invito_NewMail
|
| Invito_NewMail
|
||||||
| ContattiConScheda_NewMail;
|
| ContattiConScheda_NewMail
|
||||||
|
| ServizioAttivato_NewMail;
|
||||||
|
|
||||||
export const genMail = async ({ ...mail }: MailsTemplates) => {
|
export const genMail = async ({ ...mail }: MailsTemplates) => {
|
||||||
let mailComponent: JSX.Element | null = null;
|
let mailComponent: JSX.Element | null = null;
|
||||||
|
|
@ -121,9 +124,7 @@ export const genMail = async ({ ...mail }: MailsTemplates) => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "pagamentoConferma":
|
case "pagamentoConferma":
|
||||||
mailComponent = PagamentoConferma({
|
mailComponent = PagamentoConferma();
|
||||||
...mail.props,
|
|
||||||
});
|
|
||||||
title = "Conferma pagamento";
|
title = "Conferma pagamento";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -191,6 +192,10 @@ export const genMail = async ({ ...mail }: MailsTemplates) => {
|
||||||
});
|
});
|
||||||
title = "Contatti Con Scheda";
|
title = "Contatti Con Scheda";
|
||||||
break;
|
break;
|
||||||
|
case "servizioAttivato":
|
||||||
|
mailComponent = ServizioAttivato();
|
||||||
|
title = "Servizio Attivato";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import type Redis from "ioredis";
|
import type Redis from "ioredis";
|
||||||
import puppeteer from "puppeteer-core";
|
import puppeteer, { type PDFOptions } from "puppeteer-core";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
|
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
|
||||||
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||||
import { getKeydbClient } from "~/utils/keydb";
|
import { getKeydbClient } from "~/utils/keydb";
|
||||||
import { TOKEN_CONFIG } from "../auth/configs";
|
import { TOKEN_CONFIG } from "../auth/configs";
|
||||||
|
|
||||||
|
|
@ -82,13 +84,17 @@ export const genPdfSchedaAnnuncioBase64 = async (annuncioId: AnnunciId) => {
|
||||||
export const genPdfSchedaAnnuncio = async (annuncioId: AnnunciId) => {
|
export const genPdfSchedaAnnuncio = async (annuncioId: AnnunciId) => {
|
||||||
const keybd = getKeydbClient();
|
const keybd = getKeydbClient();
|
||||||
const url = `/area-riservata/admin/scheda-annuncio-stampa/${annuncioId}?raw=true`;
|
const url = `/area-riservata/admin/scheda-annuncio-stampa/${annuncioId}?raw=true`;
|
||||||
|
const options: PDFOptions = { printBackground: true };
|
||||||
if (keybd) {
|
if (keybd) {
|
||||||
const cacheKey = `${SCHEDA_ANNUNCIO_CACHE_PREFIX}${annuncioId}`;
|
const cacheKey = `${SCHEDA_ANNUNCIO_CACHE_PREFIX}${annuncioId}`;
|
||||||
const cached = await keybd.get(cacheKey);
|
const cached = await keybd.get(cacheKey);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return new Uint8Array(Buffer.from(cached, "base64"));
|
return new Uint8Array(Buffer.from(cached, "base64"));
|
||||||
}
|
}
|
||||||
const scheda = await puppeteerGenPdf(url);
|
const scheda = await puppeteerGenPdf({
|
||||||
|
url,
|
||||||
|
options,
|
||||||
|
});
|
||||||
await keybd.set(
|
await keybd.set(
|
||||||
cacheKey,
|
cacheKey,
|
||||||
Buffer.from(scheda).toString("base64"),
|
Buffer.from(scheda).toString("base64"),
|
||||||
|
|
@ -97,10 +103,26 @@ export const genPdfSchedaAnnuncio = async (annuncioId: AnnunciId) => {
|
||||||
);
|
);
|
||||||
return scheda;
|
return scheda;
|
||||||
}
|
}
|
||||||
return await puppeteerGenPdf(url);
|
return await puppeteerGenPdf({ url, options });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const genPdfCondizioni = async (condizioniId: AnnunciId) => {
|
export const genPdfCondizioniBase64 = async (
|
||||||
|
condizioniId: TestiEStringheStingaId,
|
||||||
|
) => {
|
||||||
|
const pdf = await genPdfCondizioni(condizioniId);
|
||||||
|
const pdfBase64 = Buffer.from(pdf).toString("base64");
|
||||||
|
const base64Content = pdfBase64.includes("base64,")
|
||||||
|
? pdfBase64.split("base64,")[1]
|
||||||
|
: pdfBase64;
|
||||||
|
if (!base64Content) {
|
||||||
|
throw new Error("Failed to convert PDF to base64");
|
||||||
|
}
|
||||||
|
return base64Content;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const genPdfCondizioni = async (
|
||||||
|
condizioniId: TestiEStringheStingaId,
|
||||||
|
) => {
|
||||||
const keybd = getKeydbClient();
|
const keybd = getKeydbClient();
|
||||||
const url = `/servizio/condizioni/${condizioniId}?raw=true`;
|
const url = `/servizio/condizioni/${condizioniId}?raw=true`;
|
||||||
if (keybd) {
|
if (keybd) {
|
||||||
|
|
@ -109,7 +131,7 @@ export const genPdfCondizioni = async (condizioniId: AnnunciId) => {
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return new Uint8Array(Buffer.from(cached, "base64"));
|
return new Uint8Array(Buffer.from(cached, "base64"));
|
||||||
}
|
}
|
||||||
const scheda = await puppeteerGenPdf(url);
|
const scheda = await puppeteerGenPdf({ url });
|
||||||
await keybd.set(
|
await keybd.set(
|
||||||
cacheKey,
|
cacheKey,
|
||||||
Buffer.from(scheda).toString("base64"),
|
Buffer.from(scheda).toString("base64"),
|
||||||
|
|
@ -118,10 +140,31 @@ export const genPdfCondizioni = async (condizioniId: AnnunciId) => {
|
||||||
);
|
);
|
||||||
return scheda;
|
return scheda;
|
||||||
}
|
}
|
||||||
return await puppeteerGenPdf(url);
|
return await puppeteerGenPdf({ url });
|
||||||
};
|
};
|
||||||
|
|
||||||
const puppeteerGenPdf = async (url: string) => {
|
export const genPdfRicevutaCortesiaBase64 = async (
|
||||||
|
ordineId: OrdiniOrdineId,
|
||||||
|
) => {
|
||||||
|
const url = `/servizio/ricevuta-cortesia/${ordineId}?raw=true`;
|
||||||
|
const pdf = await puppeteerGenPdf({ url });
|
||||||
|
const pdfBase64 = Buffer.from(pdf).toString("base64");
|
||||||
|
const base64Content = pdfBase64.includes("base64,")
|
||||||
|
? pdfBase64.split("base64,")[1]
|
||||||
|
: pdfBase64;
|
||||||
|
if (!base64Content) {
|
||||||
|
throw new Error("Failed to convert PDF to base64");
|
||||||
|
}
|
||||||
|
return base64Content;
|
||||||
|
};
|
||||||
|
|
||||||
|
const puppeteerGenPdf = async ({
|
||||||
|
url,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
url: string;
|
||||||
|
options?: PDFOptions;
|
||||||
|
}) => {
|
||||||
let browser = null;
|
let browser = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -155,7 +198,7 @@ const puppeteerGenPdf = async (url: string) => {
|
||||||
|
|
||||||
const pdf = await page.pdf({
|
const pdf = await page.pdf({
|
||||||
format: "A4",
|
format: "A4",
|
||||||
printBackground: true,
|
...options,
|
||||||
});
|
});
|
||||||
return pdf;
|
return pdf;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import type { Prettify } from "TypeHelpers.type";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import type { Context, Session } from "~/server/api/trpc";
|
|
||||||
|
|
||||||
export const checkCtx = (props: {
|
export const checkCtx = (props: {
|
||||||
res: NextApiResponse | undefined;
|
res: NextApiResponse | undefined;
|
||||||
|
|
@ -11,29 +9,3 @@ export const checkCtx = (props: {
|
||||||
if (!req || !res)
|
if (!req || !res)
|
||||||
throw new TRPCError({ code: "BAD_REQUEST", message: "res o req mancanti" });
|
throw new TRPCError({ code: "BAD_REQUEST", message: "res o req mancanti" });
|
||||||
};
|
};
|
||||||
type CtxWSession = Prettify<Context & { session: Session }>;
|
|
||||||
|
|
||||||
export const checkCtxSession = ({ ctx }: { ctx: Context }) => {
|
|
||||||
if (!ctx.session) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Utente non autorizzato",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return ctx as CtxWSession;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const checkCtxAdmin = ({ ctx }: { ctx: Context }) => {
|
|
||||||
const ckd_ctx = checkCtxSession({ ctx });
|
|
||||||
if (!ckd_ctx.session.isAdmin) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Utente non autorizzato",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return ckd_ctx as Context & {
|
|
||||||
session: Session & {
|
|
||||||
isAdmin: true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue