Skip to content

Commit

Permalink
Merge pull request #522 from sinamics/mail-verification
Browse files Browse the repository at this point in the history
Added user email verification
  • Loading branch information
sinamics authored Aug 27, 2024
2 parents 0173189 + 112585e commit 039e4e1
Show file tree
Hide file tree
Showing 17 changed files with 597 additions and 75 deletions.
204 changes: 204 additions & 0 deletions src/components/adminPage/mail/mailVerifyEmail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useState, useEffect } from "react";
import { api } from "~/utils/api";
import { toast } from "react-hot-toast";
import cn from "classnames";
import { useTranslations } from "next-intl";
import {
useTrpcApiErrorHandler,
useTrpcApiSuccessHandler,
} from "~/hooks/useTrpcApiHandler";

type InviteUserTemplate = {
subject: string;
body: string;
};

const VerifyEmailTemplate = () => {
const t = useTranslations("admin");

const handleApiError = useTrpcApiErrorHandler();
const handleApiSuccess = useTrpcApiSuccessHandler();

const [changes, setChanges] = useState({
subject: false,
body: false,
});

const [emailTemplate, setEmailTemplate] = useState({
subject: "",
body: "",
});
// get default mail template
const {
data: mailTemplates,
refetch: refetchMailTemplates,
isLoading: loadingTemplates,
} = api.admin.getMailTemplates.useQuery({
template: "verifyEmailTemplate",
});

const changeTemplateHandler = (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
) => {
const modifiedValue = e.target.value.replace(/\n/g, "<br />");
setEmailTemplate({
...emailTemplate,
[e.target.name]: modifiedValue,
});
};

const { mutate: sendTestMail, isLoading: sendingMailLoading } =
api.admin.sendTestMail.useMutation({
onError: handleApiError,
onSuccess: handleApiSuccess({
toastMessage: t("mail.templates.successToastMailSent"),
}),
});

const { mutate: setMailTemplates } = api.admin.setMailTemplates.useMutation();

const { mutate: getDefaultMailTemplate, data: defaultTemplates } =
api.admin.getDefaultMailTemplate.useMutation();

useEffect(() => {
if (!defaultTemplates) return;

setEmailTemplate(defaultTemplates);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultTemplates]);

useEffect(() => {
const verifyEmailTemplate = mailTemplates as InviteUserTemplate;
setEmailTemplate(verifyEmailTemplate);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mailTemplates]);

useEffect(() => {
const keysToCompare = ["subject", "body"]; // Add more keys as needed

const inviteUserTemplate = mailTemplates as InviteUserTemplate;
if (!inviteUserTemplate || !emailTemplate) return;

const newChanges = keysToCompare.reduce(
(acc, key) => {
const val1 = inviteUserTemplate?.[key] as string;
const val2 = emailTemplate[key] as string;

// Here we just compare strings directly, you could add more complex comparison logic if needed
acc[key] = val1 !== val2;

return acc;
},
{ subject: false, body: false },
);

setChanges(newChanges);
}, [mailTemplates, emailTemplate]);

const submitTemplateHandler = () => {
if (!emailTemplate.subject || !emailTemplate.body) {
return toast.error(t("mail.templates.errorFields"));
}

setMailTemplates(
{
template: JSON.stringify(emailTemplate),
type: "verifyEmailTemplate",
},
{
onSuccess: () => {
toast.success(t("mail.templates.successToastTemplateSaved"));
void refetchMailTemplates();
},
},
);
};

if (loadingTemplates) {
return (
<div className="flex flex-col items-center justify-center">
<h1 className="text-center text-2xl font-semibold">
<progress className="progress progress-primary w-56"></progress>
</h1>
</div>
);
}
return (
<div>
<div className="space-y-3">
<p className="font-medium">
{t("mail.templates.availableTags")}
<span className="text-primary flex gap-1">
<kbd className="kbd kbd-sm">toName</kbd>
<kbd className="kbd kbd-sm">verifyLink</kbd>
</span>
</p>
<div className="form-control w-full">
<label className="label">
<span className="label-text">{t("mail.templates.subject")}</span>
</label>
<input
type="text"
placeholder={t("mail.templates.inputPlaceholderSubject")}
value={emailTemplate?.subject || ""}
name="subject"
className={cn("input input-bordered w-full focus:outline-none", {
"border-2 border-red-500": changes?.subject,
})}
onChange={changeTemplateHandler}
/>
</div>
<div className="form-control w-full">
<label className="label">
<span className="label-text">{t("mail.templates.htmlBody")}</span>
</label>
<textarea
value={emailTemplate?.body?.replace(/<br \/>/g, "\n") || ""}
className={cn(
"custom-scrollbar textarea textarea-bordered w-full border-2 font-medium leading-snug focus:outline-none",
{ "border-2 border-red-500": changes.body },
)}
placeholder={t("mail.templates.textareaPlaceholderBody")}
rows={10}
name="body"
onChange={changeTemplateHandler}
></textarea>
</div>
</div>
<div className="flex justify-between p-5">
<div className="space-x-2">
<button
className="btn btn-primary btn-sm"
onClick={() => submitTemplateHandler()}
>
{t("mail.templates.saveTemplateButton")}
</button>
<button
className="btn btn-sm"
onClick={() =>
getDefaultMailTemplate({
template: "verifyEmailTemplate",
})
}
>
{t("mail.templates.resetButton")}
</button>
</div>
<div className="flex justify-end">
<button
className="btn btn-sm"
disabled={changes.subject || changes.body || sendingMailLoading}
onClick={() => sendTestMail({ type: "verifyEmailTemplate" })}
>
{sendingMailLoading
? t("mail.templates.sendTestMailButtonLoading")
: t("mail.templates.sendTestMailButton")}
</button>
</div>
</div>
</div>
);
};

export default VerifyEmailTemplate;
48 changes: 30 additions & 18 deletions src/components/elements/inputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ interface FormProps {
badge?: {
text: string;
color: string;
onClick?: () => void;
};
headerBadge?: {
text: string;
color: string;
onClick?: () => void;
};
}

Expand Down Expand Up @@ -125,7 +127,7 @@ const InputField = ({
const handleChange = (
e:
| ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>
| { target: { name: string; value: string[] } },
| { target: { name: string; value: string | string[] } },
) => {
const { name, value } = "target" in e ? e.target : e;
setFormValues((prevValues) => ({
Expand Down Expand Up @@ -156,32 +158,42 @@ const InputField = ({
</div>
);

const renderBadge = (badgeProps: FormProps["badge"] | FormProps["headerBadge"]) => {
if (!badgeProps) return null;

return (
<span
className={`badge badge-outline badge-${badgeProps.color} ml-2 ${
badgeProps.onClick ? "cursor-pointer" : ""
}`}
onClick={(e) => {
e.stopPropagation(); // Prevent event from bubbling up
if (badgeProps.onClick) {
badgeProps.onClick();
}
}}
>
{badgeProps.text}
</span>
);
};
return (
<>
{!showInputs ? (
<div className="flex w-full justify-between">
<div onClick={handleEditClick} className={`cursor-pointer ${labelStyle}`}>
<div className="flex font-medium">
<div onClick={handleEditClick} className={`cursor-pointer ${labelStyle}`}>
<div className="flex font-medium items-center">
<span>{label}</span>

{headerBadge && (
<span className={`badge badge-outline badge-${headerBadge.color} ml-2`}>
{headerBadge.text}
</span>
)}
{renderBadge(headerBadge)}
</div>
<div>
{description ? (
<p className="m-0 p-0 text-xs text-gray-500">{description}</p>
) : null}
</div>
<div className="text-gray-500">
{placeholder ?? fields[0].placeholder}
{badge && (
<span className={`badge badge-outline badge-${badge.color} ml-2`}>
{badge.text}
</span>
)}
<div className="flex items-center text-gray-500">
<span>{placeholder ?? fields[0].placeholder}</span>
{renderBadge(badge)}
</div>
</div>
<div>
Expand Down Expand Up @@ -263,9 +275,9 @@ const InputField = ({
formFieldName={field.name}
options={field.selectOptions as string[]}
value={(formValues[field.name] as string[]) || []}
onChange={(e) =>
onChange={(selectedValues: string[]) =>
handleChange({
target: { name: field.name, type: "select", value: e },
target: { name: field.name, value: selectedValues },
})
}
prompt={field.placeholder}
Expand Down
8 changes: 8 additions & 0 deletions src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@
"mfaRecoveryResetTitle": "2FA Recovery",
"mfaRecoveryResetMessage": "Please enter your credentials and Recovery Code",
"backToLogin": "Back to Login"
},
"emailVerification": {
"emailVerified": "Email Verified",
"errorMessage": "An error occurred while verifying your email.",
"successMessage": "Your email has been successfully verified.",
"redirectMessage": "You will be redirected automatically in {seconds} seconds.",
"goNowButton": "Go Now"
}
},
"networks": {
Expand Down Expand Up @@ -448,6 +455,7 @@
"password": "Password",
"useSSL": "Use SSL",
"inviteUserTemplate": "Invite user template",
"emailVerificationTemplate": "Email verification template",
"forgotPasswordTemplate": "Forgot Password template",
"notificationTemplate": "Notification template",
"organizationInviteTemplate": "Organization Invite template",
Expand Down
8 changes: 8 additions & 0 deletions src/locales/es/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@
"mfaRecoveryResetTitle": "Recuperación de 2FA",
"mfaRecoveryResetMessage": "Por favor, introduce tus credenciales y el código de recuperación",
"backToLogin": "Volver al inicio de sesión"
},
"emailVerification": {
"emailVerified": "Correo electrónico verificado",
"errorMessage": "Ocurrió un error al verificar tu correo electrónico.",
"successMessage": "Tu correo electrónico ha sido verificado exitosamente.",
"redirectMessage": "Serás redirigido automáticamente en {seconds} segundos.",
"goNowButton": "Ir ahora"
}
},
"networks": {
Expand Down Expand Up @@ -448,6 +455,7 @@
"password": "Contraseña",
"useSSL": "Usar SSL",
"inviteUserTemplate": "Plantilla de invitación de usuario",
"emailVerificationTemplate": "Plantilla de verificación de correo electrónico",
"forgotPasswordTemplate": "Plantilla de contraseña olvidada",
"notificationTemplate": "Plantilla de notificación",
"organizationInviteTemplate": "Plantilla de invitación de organización",
Expand Down
8 changes: 8 additions & 0 deletions src/locales/fr/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@
"mfaRecoveryResetTitle": "Récupération 2FA",
"mfaRecoveryResetMessage": "Veuillez entrer vos identifiants et le code de récupération",
"backToLogin": "Retour à la connexion"
},
"emailVerification": {
"emailVerified": "E-mail vérifié",
"errorMessage": "Une erreur s'est produite lors de la vérification de votre e-mail.",
"successMessage": "Votre e-mail a été vérifié avec succès.",
"redirectMessage": "Vous serez redirigé automatiquement dans {seconds} secondes.",
"goNowButton": "Aller maintenant"
}
},
"networks": {
Expand Down Expand Up @@ -448,6 +455,7 @@
"password": "Mot de passe",
"useSSL": "Utiliser SSL",
"inviteUserTemplate": "Modèle d'invitation d'utilisateur",
"emailVerificationTemplate": "Modèle de vérification d'e-mail",
"forgotPasswordTemplate": "Modèle de mot de passe oublié",
"notificationTemplate": "Modèle de notification",
"organizationInviteTemplate": "Modèle d'invitation d'organisation",
Expand Down
8 changes: 8 additions & 0 deletions src/locales/no/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@
"mfaRecoveryResetTitle": "Gjenoppretting av 2FA",
"mfaRecoveryResetMessage": "Vennligst skriv inn dine legitimasjonsbeskrivelser og gjenopprettingskode",
"backToLogin": "Tilbake til innlogging"
},
"emailVerification": {
"emailVerified": "E-post bekreftet",
"errorMessage": "Det oppstod en feil under verifisering av e-posten din.",
"successMessage": "E-posten din har blitt bekreftet.",
"redirectMessage": "Du vil bli videresendt automatisk om {seconds} sekunder.",
"goNowButton": "Gå nå"
}
},
"networks": {
Expand Down Expand Up @@ -448,6 +455,7 @@
"password": "Passord",
"useSSL": "Bruk SSL",
"inviteUserTemplate": "Inviter bruker mal",
"emailVerificationTemplate": "E-post verifikasjons mal",
"forgotPasswordTemplate": "Glemt passord mal",
"notificationTemplate": "Varslings mal",
"organizationInviteTemplate": "Organisasjons invitasjons mal",
Expand Down
Loading

0 comments on commit 039e4e1

Please sign in to comment.