mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-22 20:16:57 +00:00
Account deletion
This commit is contained in:
Vendored
+1
@@ -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
@@ -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({
|
||||
|
||||
@@ -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
@@ -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
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -4,3 +4,8 @@ export type UserModel = {
|
||||
email: string;
|
||||
emailVerified?: Date;
|
||||
};
|
||||
|
||||
export type VerificationModel = {
|
||||
identifier: string;
|
||||
expires: Date;
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 } =
|
||||
|
||||
Reference in New Issue
Block a user