diff --git a/apps/db/migrations/30_user_semplification.up.sql b/apps/db/migrations/30_user_semplification.up.sql
new file mode 100644
index 0000000..08e2482
--- /dev/null
+++ b/apps/db/migrations/30_user_semplification.up.sql
@@ -0,0 +1,9 @@
+--- Removes verification columns from users table
+ALTER TABLE public.users
+DROP COLUMN IF EXISTS "isVerified",
+DROP COLUMN IF EXISTS "verificationToken",
+DROP COLUMN IF EXISTS "verificationTokenExpires";
+
+--Add mustChangePassword column to users table
+ALTER TABLE public.users
+ADD COLUMN IF NOT EXISTS "mustChangePassword" BOOLEAN DEFAULT FALSE NOT NULL;
\ No newline at end of file
diff --git a/apps/db/migrations/31_magic_link.up.sql b/apps/db/migrations/31_magic_link.up.sql
new file mode 100644
index 0000000..45c3c33
--- /dev/null
+++ b/apps/db/migrations/31_magic_link.up.sql
@@ -0,0 +1,23 @@
+--- user_invites table
+CREATE TABLE IF NOT EXISTS public.user_invites (
+ id UUID DEFAULT gen_random_uuid () NOT NULL,
+ email TEXT NOT NULL,
+ token TEXT NOT NULL UNIQUE,
+ expires_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+ created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now()
+);
+
+-- Primary Key
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_constraint
+ WHERE conname = 'user_invites_pkey'
+ AND conrelid = 'public.user_invites'::regclass
+ ) THEN
+ ALTER TABLE public.user_invites
+ ADD CONSTRAINT user_invites_pkey PRIMARY KEY (id);
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_user_invites_token ON public.user_invites (token);
\ No newline at end of file
diff --git a/apps/infoalloggi/emails/invito.tsx b/apps/infoalloggi/emails/invito.tsx
new file mode 100644
index 0000000..67dec8c
--- /dev/null
+++ b/apps/infoalloggi/emails/invito.tsx
@@ -0,0 +1,45 @@
+import { Button, Heading, Row, Section, Text } from "@react-email/components";
+import Base from "./base";
+export type Invito_NewMail = {
+ mailType: "invito";
+ props: InvitoProps;
+};
+
+type InvitoProps = {
+ nome: string;
+ inviteUrl: string;
+};
+
+const Invito = ({ nome, inviteUrl }: InvitoProps) => {
+ return (
+
+ <>
+
+ Procedi ora su Infoalloggi.it
+
+
+
+
+ Ciao {nome},
+ Accedi ora al servizio di ricerca Infoalloggi, dove potrai andare
+ a contattare direttamente i proprietari degli immobili
+ disponibili.
+
+
+
+
+
+
+
+
+ >
+
+ );
+};
+
+export default Invito;
diff --git a/apps/infoalloggi/package-lock.json b/apps/infoalloggi/package-lock.json
index 7fe3106..eedc3dc 100644
--- a/apps/infoalloggi/package-lock.json
+++ b/apps/infoalloggi/package-lock.json
@@ -59,7 +59,7 @@
"leaflet": "^1.9.4",
"leaflet-defaulticon-compatibility": "^0.1.2",
"lucide-react": "^0.536.0",
- "next": "15.4.8",
+ "next": "^15.4.10",
"next-themes": "^0.4.6",
"nextjs-progressbar": "^0.0.16",
"nodemailer": "^7.0.5",
@@ -2785,9 +2785,9 @@
}
},
"node_modules/@next/env": {
- "version": "15.4.8",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.8.tgz",
- "integrity": "sha512-LydLa2MDI1NMrOFSkO54mTc8iIHSttj6R6dthITky9ylXV2gCGi0bHQjVCtLGRshdRPjyh2kXbxJukDtBWQZtQ==",
+ "version": "15.4.10",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.10.tgz",
+ "integrity": "sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg==",
"license": "MIT"
},
"node_modules/@next/swc-darwin-arm64": {
@@ -14138,13 +14138,13 @@
"license": "MIT"
},
"node_modules/next": {
- "version": "15.4.8",
- "resolved": "https://registry.npmjs.org/next/-/next-15.4.8.tgz",
- "integrity": "sha512-jwOXTz/bo0Pvlf20FSb6VXVeWRssA2vbvq9SdrOPEg9x8E1B27C2rQtvriAn600o9hH61kjrVRexEffv3JybuA==",
+ "version": "15.4.10",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.4.10.tgz",
+ "integrity": "sha512-itVlc79QjpKMFMRhP+kbGKaSG/gZM6RCvwhEbwmCNF06CdDiNaoHcbeg0PqkEa2GOcn8KJ0nnc7+yL7EjoYLHQ==",
"license": "MIT",
"peer": true,
"dependencies": {
- "@next/env": "15.4.8",
+ "@next/env": "15.4.10",
"@swc/helpers": "0.5.15",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
diff --git a/apps/infoalloggi/package.json b/apps/infoalloggi/package.json
index 91cb825..40c7677 100644
--- a/apps/infoalloggi/package.json
+++ b/apps/infoalloggi/package.json
@@ -71,7 +71,7 @@
"leaflet": "^1.9.4",
"leaflet-defaulticon-compatibility": "^0.1.2",
"lucide-react": "^0.536.0",
- "next": "15.4.8",
+ "next": "15.4.10",
"next-themes": "^0.4.6",
"nextjs-progressbar": "^0.0.16",
"nodemailer": "^7.0.5",
diff --git a/apps/infoalloggi/src/components/area-riservata/userViewHeader.tsx b/apps/infoalloggi/src/components/area-riservata/userViewHeader.tsx
index 3856c62..9a9d42c 100644
--- a/apps/infoalloggi/src/components/area-riservata/userViewHeader.tsx
+++ b/apps/infoalloggi/src/components/area-riservata/userViewHeader.tsx
@@ -16,6 +16,7 @@ import { useRouter } from "next/router";
import toast from "react-hot-toast";
import { useUserViewContext } from "~/lib/userViewContext";
import { api } from "~/utils/api";
+import { Confirm } from "../confirm";
import { WhatsAppIcon2 } from "../svgs";
import { Button } from "../ui/button";
import { Separator } from "../ui/separator";
@@ -31,6 +32,14 @@ export const UserViewHeader = () => {
router.reload();
},
});
+ const { mutateAsync: sendInvite } = api.invite.sendInvite.useMutation({
+ onError: (error) => {
+ toast.error(error.message);
+ },
+ onSuccess: async () => {
+ toast.success("Email inviata con successo");
+ },
+ });
if (!data) return
Errore
;
return (
@@ -50,6 +59,17 @@ export const UserViewHeader = () => {
Intestazione
)}
+ {
+ await sendInvite({ id: data.id });
+ }}
+ title="Vuoi mandare l'email di invito al sito?"
+ >
+
+
diff --git a/apps/infoalloggi/src/components/tables/servizio-table.tsx b/apps/infoalloggi/src/components/tables/servizio-table.tsx
index df1d826..ab2db12 100644
--- a/apps/infoalloggi/src/components/tables/servizio-table.tsx
+++ b/apps/infoalloggi/src/components/tables/servizio-table.tsx
@@ -2,13 +2,11 @@ import {
Copy,
EllipsisVertical,
ExternalLink,
- Mail,
Trash2,
Wrench,
} from "lucide-react";
import Link from "next/link";
import toast from "react-hot-toast";
-import { Confirm } from "~/components/confirm";
import { LoadingPage } from "~/components/loading";
import { AddAnnuncio } from "~/components/servizio/servizio_actions";
import { Button } from "~/components/ui/button";
@@ -65,15 +63,6 @@ export const TabServizio = ({
await utils.servizio.getUserServizi.invalidate();
},
});
- const { mutate: sendEmail } = api.servizio.sendServizioEmail.useMutation({
- onError: (error) => {
- toast.error(error.message);
- },
- onSuccess: async () => {
- toast.success("Email onboarding inviata con successo");
- await utils.servizio.getUserServizi.invalidate();
- },
- });
const { mutate: update } = api.servizio.updateServizio.useMutation({
onError: (error) => {
@@ -154,31 +143,6 @@ export const TabServizio = ({
-
- {
- sendEmail({
- servizioId: servizio.servizio_id,
- userId: userId,
- });
- }}
- title="Invia email"
- >
-
-
-
{
strengthScore,
toggleVisibility,
} = useStrongPassword();
+ const router = useRouter();
const ChangePasswordFormSchema = z
.object({
- newpassword: z.string(),
- oldpassword: z.string(),
+ oldPsw: z.string().min(1, "Inserisci la password corrente"),
+ newPsw: z.string(),
+ confirmPsw: z.string().min(1, "Ripeti la nuova password"),
})
- .superRefine(({ newpassword }, refinementContext) => {
+ .superRefine(({ oldPsw, newPsw, confirmPsw }, refinementContext) => {
passwordRefine({
- password: newpassword,
- path: "newpassword",
+ password: newPsw,
+ path: "newPsw",
refinementContext,
});
+ if (oldPsw === newPsw) {
+ refinementContext.addIssue({
+ code: "custom",
+ message: "La nuova password deve essere diversa da quella corrente",
+ path: ["newPsw"],
+ });
+ }
+ if (newPsw !== confirmPsw) {
+ refinementContext.addIssue({
+ code: "custom",
+ message: "Le password non coincidono",
+ path: ["confirmPsw"],
+ });
+ }
});
type ChangePasswordFormValues = z.infer;
@@ -48,8 +65,9 @@ export const ChangePasswordForm = () => {
const form = useZodForm(ChangePasswordFormSchema, {
defaultValues: {
- newpassword: "",
- oldpassword: "",
+ newPsw: "",
+ oldPsw: "",
+ confirmPsw: "",
},
});
@@ -57,16 +75,17 @@ export const ChangePasswordForm = () => {
onError: (error) => {
toast.error(error.message);
},
- onSuccess: () => {
+ onSuccess: async () => {
toast.success(t.pwReset.pwAggiornata);
form.reset();
+ await router.push("/area-riservata/dashboard");
},
});
function onSubmit(fields: ChangePasswordFormValues) {
mutate({
- newpassword: fields.newpassword,
- oldpassword: fields.oldpassword,
+ newpassword: fields.newPsw,
+ oldpassword: fields.oldPsw,
});
}
@@ -75,7 +94,7 @@ export const ChangePasswordForm = () => {