Account deletion

This commit is contained in:
Timothy Armes
2024-04-12 16:23:44 +02:00
parent cfbba733c2
commit 45ea8960e0
10 changed files with 198 additions and 42 deletions
+1
View File
@@ -31,6 +31,7 @@
"Spiderfy",
"spiderified",
"superjson",
"supprimer",
"tailwindcss",
"tanstack",
"timothyarmes",
@@ -0,0 +1,89 @@
"use client";
import { useState } from "react";
import { signOut } from "next-auth/react";
import { useTranslations } from "next-intl";
import InfoMessage from "@/components/InfoMessage";
import { deleteAccount } from "@/server/deleteAccount";
import { useRouter } from "@/utils/navigation";
export default function Contact() {
const t = useTranslations("DeleteAccount");
const at = useTranslations("App");
const router = useRouter();
const [deleting, setDeleting] = useState(false);
const [deleted, setDeleted] = useState(false);
const [error, setError] = useState(false);
const onDeleteAccount = async () => {
try {
setDeleting(true);
const result = await deleteAccount();
if (result.serverError) {
throw new Error(result.serverError);
}
signOut({ redirect: false });
setDeleted(true);
} catch {
setDeleting(false);
setError(true);
}
};
return (
<section className="bg-white pb-20 dark:bg-gray-800">
<div className="mx-auto max-w-screen-md px-4 py-8 lg:py-16">
<h2
className="mb-4 text-center text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white"
data-test="header2"
>
{t("title")}
</h2>
{deleted ? (
<InfoMessage
className="text-center"
responseMessage={{ isSuccessful: true, message: t("success") }}
/>
) : (
<>
<p className="mb-8 text-center font-light text-gray-500 dark:text-gray-400 sm:text-xl lg:mb-16">
{t("info")}
</p>
{error && (
<InfoMessage
className="mb-8 text-center"
responseMessage={{ isSuccessful: false, message: t("failure") }}
/>
)}
<div className="flex items-center justify-center space-x-4 text-sm font-bold">
<button
type="button"
onClick={() => router.back()}
className="rounded-lg border border-primary px-5 py-3 text-center text-xs text-primary hover:bg-primary hover:text-white focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:focus:ring-primary-800 sm:w-fit"
>
{at("cancelButton")}
</button>
<button
onClick={() => onDeleteAccount()}
disabled={deleting}
className="rounded-lg bg-primary-600 px-5 py-3 text-center text-sm font-medium text-white hover:bg-primary-800 focus:outline-none focus:ring-4 focus:ring-primary-300 disabled:opacity-25 dark:text-white dark:hover:bg-primary-700 dark:focus:ring-primary-800 sm:w-fit"
>
{t("deleteButton")}
</button>
</div>
</>
)}
</div>
</section>
);
}
+5 -1
View File
@@ -4,11 +4,15 @@ import NextAuth from "next-auth";
import { clientPromise } from "@/server/mongodb";
import Email from "@/utils/nodemailerProvider";
export const adapter = MongoDBAdapter(clientPromise, {
databaseName: "userData",
});
export const {
handlers: { GET, POST },
auth,
} = NextAuth({
adapter: MongoDBAdapter(clientPromise, { databaseName: "userData" }),
adapter,
providers: [
Email({
+5 -5
View File
@@ -43,11 +43,11 @@ export const useAuthMenuOptions = () => {
onClick: () => router.push("/zones"),
disabled: signingOut,
},
// {
// title: t("deleteAccount"),
// onClick: handleSignOut,
// disabled: signingOut,
// },
{
title: t("deleteAccount"),
onClick: () => router.push("/delete-account"),
disabled: signingOut,
},
{
title: t("signOut"),
onClick: handleSignOut,
+26 -18
View File
@@ -220,24 +220,6 @@
"ERR_NO_TOURNAMENT_ID": "The link you provided is incorrect"
},
"SignIn": {
"title": "Sign In",
"info": "Sign in to access additional features, such as receiving email notifications for tournaments in your area. There's no need to create an account, just sign in with your email address and we'll send you a link.",
"emailLabel": "Email",
"emailPlaceholder": "Your email",
"signInButton": "Sign In",
"failure": "Oops, something went wrong. Please try again.",
"checkEmail": "Please check your email for a sign-in link.",
"email": {
"subject": "Sign in to {host}",
"button": "Sign in",
"ignore": "If you didn't request this, you can safely ignore this email.",
"textMessage": "Click the following link to sign in to {host}: {url}\n\n"
}
},
"Zones": {
"title": "Your Zones",
"info": "You can set up zones to receive email notifications for tournaments in these areas. These zones can also be used to filter tournaments on the main map.",
@@ -259,5 +241,31 @@
"deleteZoneTitle": "Delete Zone",
"deleteZoneInfo": "Are you sure you want to delete this zone? This action cannot be undone."
},
"SignIn": {
"title": "Sign In",
"info": "Sign in to access additional features, such as receiving email notifications for tournaments in your area. There's no need to create an account, just sign in with your email address and we'll send you a link.",
"emailLabel": "Email",
"emailPlaceholder": "Your email",
"signInButton": "Sign In",
"failure": "Oops, something went wrong. Please try again.",
"checkEmail": "Please check your email for a sign-in link.",
"email": {
"subject": "Sign in to {host}",
"button": "Sign in",
"ignore": "If you didn't request this, you can safely ignore this email.",
"textMessage": "Click the following link to sign in to {host}: {url}\n\n"
}
},
"DeleteAccount": {
"title": "Delete Account",
"info": "Are you sure you want to delete your account? This action cannot be undone.",
"deleteButton": "Delete Account",
"success": "Your account has been deleted.",
"failure": "Oops, something went wrong. Please try again later."
}
}
+25 -17
View File
@@ -221,23 +221,6 @@
"ERR_NO_TOURNAMENT_ID": "L'identifiant du tournoi fourni est incorrect"
},
"SignIn": {
"title": "Se connecter",
"info": "Connectez-vous pour accéder à des fonctionnalités supplémentaires, telles que la réception de notifications par e-mail pour les tournois dans votre région. Pas besoin de créer un compte, connectez-vous simplement avec votre adresse e-mail et nous vous enverrons un lien.",
"emailLabel": "E-mail",
"emailPlaceholder": "Votre e-mail",
"signInButton": "Se connecter",
"failure": "Oups, quelque chose s'est mal passé. Veuillez réessayer.",
"checkEmail": "Veuillez vérifier votre e-mail pour un lien de connexion.",
"email": {
"subject": "Connexion à {host}",
"button": "Se connecter",
"ignore": "Si vous n'avez pas demandé cela, vous pouvez ignorer en toute sécurité cet e-mail.",
"textMessage": "Cliquez sur le lien suivant pour vous connecter à {host}: {url}\n\n"
}
},
"Zones": {
"title": "Vos régions",
"info": "Vous pouvez définir des zones personnalisées pour recevoir des notifications par e-mail pour les tournois dans ces zones. Ces zones peuvent aussi être utiliser pour filtrer les tournois sur la carte.",
@@ -259,5 +242,30 @@
"deleteZoneTitle": "Supprimer la région",
"deleteZoneInfo": "Êtes-vous sûr de vouloir supprimer cette région\u00a0?"
},
"SignIn": {
"title": "Se connecter",
"info": "Connectez-vous pour accéder à des fonctionnalités supplémentaires, telles que la réception de notifications par e-mail pour les tournois dans votre région. Pas besoin de créer un compte, connectez-vous simplement avec votre adresse e-mail et nous vous enverrons un lien.",
"emailLabel": "E-mail",
"emailPlaceholder": "Votre e-mail",
"signInButton": "Se connecter",
"failure": "Oups, quelque chose s'est mal passé. Veuillez réessayer.",
"checkEmail": "Veuillez vérifier votre e-mail pour un lien de connexion.",
"email": {
"subject": "Connexion à {host}",
"button": "Se connecter",
"ignore": "Si vous n'avez pas demandé cela, vous pouvez ignorer en toute sécurité cet e-mail.",
"textMessage": "Cliquez sur le lien suivant pour vous connecter à {host}: {url}\n\n"
}
},
"DeleteAccount": {
"title": "Supprimer le compte",
"info": "Êtes-vous sûr de vouloir supprimer votre compte\u00a0? Cette action est irréversible.",
"deleteButton": "Supprimer le compte",
"success": "Votre compte a été supprimé avec succès.",
"failure": "Oups, quelque chose s'est mal passé. Veuillez réessayer."
}
}
+32
View File
@@ -0,0 +1,32 @@
"use server";
import { ObjectId } from "mongodb";
import { z } from "zod";
import { adapter, auth } from "@/auth";
import { collections, dbConnect } from "@/server/mongodb";
import { errorLog } from "@/utils/logger";
import { action } from "./safeAction";
export const deleteAccount = action(z.void(), async () => {
try {
await dbConnect();
const user = await auth();
if (!user?.user) {
throw new Error("You must be logged in to delete your account");
}
await collections.zones!.deleteMany({
userId: new ObjectId(user.user!.id!),
});
await adapter.deleteUser!(user.user!.id!);
return true;
} catch (error) {
errorLog(error);
throw error;
}
});
+5
View File
@@ -4,3 +4,8 @@ export type UserModel = {
email: string;
emailVerified?: Date;
};
export type VerificationModel = {
identifier: string;
expires: Date;
};
+5 -1
View File
@@ -1,7 +1,7 @@
import mongoDB, { MongoClient } from "mongodb";
import { TournamentModel } from "@/server/models/tournamentModel";
import { UserModel } from "@/server/models/userModel";
import { UserModel, VerificationModel } from "@/server/models/userModel";
import { ZoneModel } from "@/server/models/zoneModel";
import { ClubModel } from "./models/clubModel";
@@ -11,6 +11,7 @@ export const collections: {
clubs?: mongoDB.Collection<ClubModel>;
users?: mongoDB.Collection<UserModel>;
zones?: mongoDB.Collection<ZoneModel>;
userVerificationTokens?: mongoDB.Collection<VerificationModel>;
} = {};
if (!process.env.MONGODB_URI) {
@@ -52,6 +53,9 @@ export async function dbConnect() {
const userData: mongoDB.Db = p.db("userData");
collections.users = userData.collection("userData");
collections.userVerificationTokens = userData.collection(
"userVerificationTokens",
);
collections.zones = userData.collection("zones");
const tournamentData: mongoDB.Db = p.db("tournamentsFranceDB");
+5
View File
@@ -41,6 +41,11 @@ export const pathnames = {
fr: "/contactez-nous",
en: "/contact-us",
},
"/delete-account": {
fr: "/supprimer-compte",
en: "/delete-account",
},
} satisfies Pathnames<typeof locales>;
export const { Link, redirect, usePathname, useRouter, getPathname } =