Skip to content

Commit

Permalink
Merge pull request #419 from rharkor/dev
Browse files Browse the repository at this point in the history
Preferred lang
  • Loading branch information
rharkor authored Dec 10, 2023
2 parents b527305 + 7959f7c commit 7017ab8
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ CREATE TABLE "User" (
"role" TEXT NOT NULL DEFAULT 'user',
"password" TEXT,
"hasPassword" BOOLEAN NOT NULL DEFAULT false,
"lastLocale" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

Expand Down
1 change: 1 addition & 0 deletions packages/app/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ model User {
hasPassword Boolean @default(false)
resetPasswordToken ResetPassordToken?
userEmailVerificationToken UserEmailVerificationToken?
lastLocale String?
// Timestamps
createdAt DateTime @default(now())
Expand Down
7 changes: 5 additions & 2 deletions packages/app/src/api/auth/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { hash } from "@/lib/bcrypt"
import { logger } from "@/lib/logger"
import { sendMail } from "@/lib/mailer"
import { prisma } from "@/lib/prisma"
import { redis } from "@/lib/redis"
import { signUpSchema } from "@/lib/schemas/auth"
import { html, plainText, subject } from "@/lib/templates/mail/verify-email"
import { ApiError, handleApiError, throwableErrorsMessages } from "@/lib/utils/server-utils"
Expand All @@ -25,8 +26,10 @@ export const register = async ({ input }: apiInputFromSchema<typeof signUpSchema
email: email.toLowerCase(),
username,
password: hashedPassword,
lastLocale: input.locale,
},
})
await redis.set(`lastLocale:${user.id}`, input.locale)

//* Send verification email
if (env.ENABLE_MAILING_SERVICE === true) {
Expand All @@ -43,8 +46,8 @@ export const register = async ({ input }: apiInputFromSchema<typeof signUpSchema
from: `"${env.SMTP_FROM_NAME}" <${env.SMTP_FROM_EMAIL}>`,
to: email.toLowerCase(),
subject: subject,
text: plainText(url),
html: html(url),
text: plainText(url, input.locale),
html: html(url, input.locale),
})
} else {
logger.debug("Email verification disabled, skipping email sending on registration")
Expand Down
6 changes: 4 additions & 2 deletions packages/app/src/api/me/email/mutation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { randomUUID } from "crypto"
import { env } from "env.mjs"
import { i18n } from "i18n-config"

import { logger } from "@/lib/logger"
import { sendMail } from "@/lib/mailer"
Expand All @@ -20,6 +21,7 @@ export const sendVerificationEmail = async ({ input }: apiInputFromSchema<typeof
email: email.toLowerCase(),
},
})

if (!user) {
logger.debug("User not found")
return { email }
Expand Down Expand Up @@ -70,8 +72,8 @@ export const sendVerificationEmail = async ({ input }: apiInputFromSchema<typeof
from: `"${env.SMTP_FROM_NAME}" <${env.SMTP_FROM_EMAIL}>`,
to: email.toLowerCase(),
subject: subject,
text: plainText(url),
html: html(url),
text: plainText(url, user.lastLocale ?? i18n.defaultLocale),
html: html(url, user.lastLocale ?? i18n.defaultLocale),
})
} else {
logger.debug("Email verification disabled")
Expand Down
13 changes: 11 additions & 2 deletions packages/app/src/api/me/password/mutation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { randomUUID } from "crypto"
import { env } from "env.mjs"
import { i18n } from "i18n-config"

import { hash } from "@/lib/bcrypt"
import { logger } from "@/lib/logger"
Expand Down Expand Up @@ -63,8 +64,16 @@ export const forgotPassword = async ({ input }: apiInputFromSchema<typeof forgot
from: `"${env.SMTP_FROM_NAME}" <${env.SMTP_FROM_EMAIL}>`,
to: email,
subject: subject,
text: plainText(user.username ?? email, `${env.VERCEL_URL ?? env.BASE_URL}/reset-password/${resetPasswordToken}`),
html: html(user.username ?? email, `${env.VERCEL_URL ?? env.BASE_URL}/reset-password/${resetPasswordToken}`),
text: plainText(
user.username ?? email,
`${env.VERCEL_URL ?? env.BASE_URL}/reset-password/${resetPasswordToken}`,
user.lastLocale ?? i18n.defaultLocale
),
html: html(
user.username ?? email,
`${env.VERCEL_URL ?? env.BASE_URL}/reset-password/${resetPasswordToken}`,
user.lastLocale ?? i18n.defaultLocale
),
})

return { email }
Expand Down
35 changes: 34 additions & 1 deletion packages/app/src/app/[lang]/(protected)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Locale } from "i18n-config"
import requireAuth from "@/components/auth/require-auth"
import LocaleSwitcher from "@/components/locale-switcher"
import { ThemeSwitch } from "@/components/theme/theme-switch"
import { prisma } from "@/lib/prisma"
import { redis } from "@/lib/redis"

export default async function ProtectedLayout({
children,
Expand All @@ -13,7 +15,38 @@ export default async function ProtectedLayout({
lang: Locale
}
}) {
await requireAuth()
const session = await requireAuth()

//* Set last locale
// Get last locale from redis or db
const getLastLocale = async () => {
const lastLocale = await redis.get(`lastLocale:${session.user.id}`)
if (lastLocale) {
return lastLocale
}
const lastLocaleFromDb = await prisma.user.findUnique({
where: { id: session.user.id },
select: { lastLocale: true },
})
if (lastLocaleFromDb && lastLocaleFromDb.lastLocale) {
await redis.set(`lastLocale:${session.user.id}`, lastLocaleFromDb.lastLocale)
return lastLocaleFromDb.lastLocale
}
return null
}
// Set last locale in redis and db
const setLastLocale = async (locale: Locale) => {
await redis.set(`lastLocale:${session.user.id}`, locale)
await prisma.user.update({
where: { id: session.user.id },
data: { lastLocale: locale },
})
}

const lastLocale = await getLastLocale()
if (lastLocale !== lang) {
await setLastLocale(lang)
}

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default async function SignupByCredentials({
<CardTitle>{dictionary.signUpPage.createAnAccount}</CardTitle>
</CardHeader>
<CardBody>
<RegisterUserAuthForm className="gap-3" searchParams={searchParams} dictionary={dictionary} />
<RegisterUserAuthForm className="gap-3" searchParams={searchParams} dictionary={dictionary} locale={lang} />
</CardBody>
</Card>
</main>
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/app/[lang]/(sys-auth)/sign-up/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default async function SignUpPage({
<p className="text-muted-foreground text-sm">{dictionary.signUpPage.enterEmail}</p>
</div>
<div className="grid gap-6">
<RegisterUserAuthForm isMinimized searchParams={searchParams} dictionary={dictionary} />
<RegisterUserAuthForm isMinimized searchParams={searchParams} dictionary={dictionary} locale={lang} />
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
Expand Down
4 changes: 3 additions & 1 deletion packages/app/src/components/auth/register-user-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type UserAuthFormProps = React.HTMLAttributes<HTMLFormElement> & {
dictionary: TDictionary
isMinimized?: boolean
searchParams?: { [key: string]: string | string[] | undefined }
locale: string
}

export const formSchema = (dictionary: TDictionary) =>
Expand Down Expand Up @@ -52,7 +53,7 @@ export const getFormSchema = ({ dictionary, isMinimized }: { dictionary: TDictio
export type IForm = z.infer<ReturnType<typeof formSchema>>
export type IFormMinimized = z.infer<ReturnType<typeof formMinizedSchema>>

export function RegisterUserAuthForm({ dictionary, isMinimized, searchParams, ...props }: UserAuthFormProps) {
export function RegisterUserAuthForm({ dictionary, isMinimized, searchParams, locale, ...props }: UserAuthFormProps) {
const router = useRouter()

const registerMutation = trpc.auth.register.useMutation({
Expand Down Expand Up @@ -102,6 +103,7 @@ export function RegisterUserAuthForm({ dictionary, isMinimized, searchParams, ..
username: "",
password: "",
confirmPassword: "",
locale,
},
})

Expand Down
1 change: 1 addition & 0 deletions packages/app/src/lib/schemas/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const signUpSchema = (dictionary?: TDictionary) =>
signInSchema(dictionary).extend({
username: usernameSchema(dictionary),
password: passwordSchemaWithRegex(dictionary),
locale: z.string(),
})

export const signUpResponseSchema = (dictionary?: TDictionary) =>
Expand Down
52 changes: 43 additions & 9 deletions packages/app/src/lib/templates/mail/reset-password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { env } from "env.mjs"

export const subject = "Reset your password"

export const plainText = (username: string, resetLink: string) => `Password Reset
export const plainText = (username: string, resetLink: string, locale: string) => {
const en = `Password Reset
Hello ${username},
Expand All @@ -13,12 +14,29 @@ ${resetLink}
If you did not request this password reset, you can safely ignore this email.
This email was sent to you as part of our account services.${
env.SUPPORT_EMAIL ? ` If you have any questions, please contact us at ${env.SUPPORT_EMAIL}.` : ""
}
env.SUPPORT_EMAIL ? ` If you have any questions, please contact us at ${env.SUPPORT_EMAIL}.` : ""
}
`
const fr = `Réinitialiser votre mot de passe
Bonjour ${username},
Nous avons reçu une demande de réinitialisation de votre mot de passe. Vous pouvez réinitialiser votre mot de passe en cliquant sur le lien suivant :
${resetLink}
Si vous n'avez pas demandé cette réinitialisation de mot de passe, vous pouvez ignorer cet e-mail en toute sécurité.
Ce courriel vous a été envoyé dans le cadre de nos services de compte.${
env.SUPPORT_EMAIL ? ` Si vous avez des questions, veuillez nous contacter à l'adresse ${env.SUPPORT_EMAIL}.` : ""
}
`
if (locale === "fr") return fr
return en
}

export const html = (username: string, resetLink: string) => `<!DOCTYPE html>
<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" lang="en">
export const html = (username: string, resetLink: string, locale: string) => `<!DOCTYPE html>
<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" lang="${locale}">
<head>
<title></title>
Expand Down Expand Up @@ -134,7 +152,13 @@ export const html = (username: string, resetLink: string) => `<!DOCTYPE html>
<td class="pad" style="text-align:center;width:100%;">
<h1
style="margin: 0; color: #101010; direction: ltr; font-family: Arial, Helvetica Neue, Helvetica, sans-serif; font-size: 27px; font-weight: normal; letter-spacing: normal; line-height: 120%; text-align: center; margin-top: 0; margin-bottom: 0;">
<strong>Forgot Your Password?</strong>
<strong>
${
locale === "fr"
? "Mot de passe oublié ?"
: "Forgot Your Password?"
}
</strong>
</h1>
</td>
</tr>
Expand Down Expand Up @@ -175,7 +199,12 @@ export const html = (username: string, resetLink: string) => `<!DOCTYPE html>
style="color:#848484;font-family:Arial, Helvetica Neue, Helvetica, sans-serif;font-size:14px;line-height:180%;text-align:center;mso-line-height-alt:25.2px;">
<p style="margin: 0; word-break: break-word;">
<span>
Hi ${username}, you recently requested to reset your password for your account. Click the button below to reset it.
${
locale === "fr"
? `Bonjour ${username}, vous avez récemment demandé à réinitialiser votre mot de passe pour votre compte. Cliquez sur le bouton ci-dessous pour le réinitialiser.`
: `
Hi ${username}, you recently requested to reset your password for your account. Click the button below to reset it.`
}
</span>
</p>
</div>
Expand All @@ -195,8 +224,13 @@ export const html = (username: string, resetLink: string) => `<!DOCTYPE html>
href="${resetLink}" target="_blank"
style="text-decoration:none;display:inline-block;color:#ffffff;background-color:#101;border-radius:4px;width:auto;border-top:1px solid #101;font-weight:undefined;border-right:1px solid #101;border-bottom:1px solid #101;border-left:1px solid #101;padding-top:5px;padding-bottom:5px;font-family:Arial, Helvetica Neue, Helvetica, sans-serif;font-size:16px;text-align:center;mso-border-alt:none;word-break:keep-all;"><span
style="padding-left:20px;padding-right:20px;font-size:16px;display:inline-block;letter-spacing:normal;"><span
style="word-break: break-word; line-height: 32px;">Reset
Password</span></span></a><!--[if mso]></center></v:textbox></v:roundrect><![endif]-->
style="word-break: break-word; line-height: 32px;">
${
locale === "fr"
? "Réinitialiser le mot de passe"
: "Reset Password"
}
</span></span></a><!--[if mso]></center></v:textbox></v:roundrect><![endif]-->
</div>
</td>
</tr>
Expand Down
Loading

0 comments on commit 7017ab8

Please sign in to comment.