Skip to content

Commit

Permalink
Add connection to Mailgun to send emails
Browse files Browse the repository at this point in the history
- User registration email
- Template mjml for the email template
- Send verification email
- check the required enviroment variables
-
  • Loading branch information
leamsigc committed Nov 4, 2024
1 parent e2ef38d commit 2aef12f
Show file tree
Hide file tree
Showing 13 changed files with 1,836 additions and 1,140 deletions.
7 changes: 6 additions & 1 deletion .env-example
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ NUXT_TURSO_AUTH_TOKEN="12312312"
# Better Auth
BETTER_AUTH_SECRET=lJLBAEJke0WjRpehRB1yUNBsArQxCxws
BETTER_AUTH_URL=http://localhost:3000
NUXT_BETTER_AUTH_URL=http://localhost:3000
NUXT_BETTER_AUTH_URL=http://localhost:3000

# Email
MAILGUN_API_KEY=<TOKEN>
MAILGUN_DOMAIN=<DOMAIN>
MAIL_FROM_EMAIL=<Email from example no-reply@domain.com>
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,35 @@ export type UserRegisterType = Omit<typeof UserInsert, "createdAt" | "updatedAt"

### Dashboard

![Dashboard](./public/Dashboard.png)
![Dashboard](./public/Dashboard.png)





## TODO

- [x] When a new user registered, send a verification email to the user
- [ ] If the user is not verified show a verification screen
```ts
await authClient.signIn.emailAndPassword({
email: "email@example.com",
password: "password"
}, {
onError: (ctx) => {
// Handle the error
if(ctx.error.status === 403) {
// Display a verification overlay
}
}
});
```

- [ ] Enforce email verification
```ts
export const auth = betterAuth({
emailAndPassword: {
requireEmailVerification: true
}
})
```
14 changes: 8 additions & 6 deletions app/components/content/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,14 @@ const { data } = await useAsyncData("nav_en", () =>
<div class="hidden lg:flex">
<ToggleTheme />

<UiButton as-child size="sm" variant="ghost" :aria-label="action.label" v-for="action in data.actions">
<NuxtLink :aria-label="action.label" :href="action.href" :target="_blank">
<Icon :name="action.icon" />
{{ action.name }}
</NuxtLink>
</UiButton>
<template v-if="data">
<UiButton as-child size="sm" variant="ghost" :aria-label="action.label" v-for="action in data.actions">
<NuxtLink :aria-label="action.label" :href="action.href" :target="action.target || ''">
<Icon :name="action.icon" />
{{ action.name }}
</NuxtLink>
</UiButton>
</template>
</div>
</header>
</section>
Expand Down
12 changes: 2 additions & 10 deletions app/pages/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ const loginFormSchema = [
$formkit: "text",
name: "email",
label: "Email",
help: "This will be used for your account.",
validation: "required|email",
},
{
$formkit: "password",
name: "password",
label: "Password",
help: "Enter your new password.",
validation: "required|length:5,16",
},
];
Expand All @@ -56,14 +54,8 @@ const HandleLoginUser = async () => {
</UiCardDescription>
</UiCardHeader>
<UiCardContent class="grid gap-4">
<FormKit
id="login-form"
v-slot="{ state: { valid } }"
v-model="loginForm"
type="form"
:actions="false"
@submit="HandleLoginUser"
>
<FormKit id="login-form" v-slot="{ state: { valid } }" v-model="loginForm" type="form" :actions="false"
@submit="HandleLoginUser">
<FormKitSchema :schema="loginFormSchema" />
<UiButton class="w-full" type="submit" :disabled="!valid"> Sign in </UiButton>
</FormKit>
Expand Down
13 changes: 2 additions & 11 deletions app/pages/register.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,18 @@ const registerForm = computed(() => [
$formkit: "text",
name: "email",
label: "Email",
help: "This will be used for your account.",
validation: "required|email",
},
{
$formkit: "password",
name: "password",
label: "Password",
help: "Enter your new password.",
validation: "required|length:5,16",
},
{
$formkit: "password",
name: "password_confirm",
label: "Confirm password",
help: "Enter your new password again to confirm it.",
validation: "required|confirm",
validationLabel: "password confirmation",
},
Expand Down Expand Up @@ -87,14 +84,8 @@ const HandleRegisterUser = async () => {
</UiCardDescription>
</UiCardHeader>
<UiCardContent class="grid gap-4">
<FormKit
id="register-form"
v-slot="{ state: { valid } }"
v-model="userInformation"
type="form"
:actions="false"
@submit="HandleRegisterUser"
>
<FormKit id="register-form" v-slot="{ state: { valid } }" v-model="userInformation" type="form" :actions="false"
@submit="HandleRegisterUser">
<FormKitSchema :schema="registerForm" />

<UiButton class="w-full" type="submit" :disabled="!valid"> Register </UiButton>
Expand Down
21 changes: 21 additions & 0 deletions config/env.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export function checkEnv(env: NodeJS.ProcessEnv) {
const required = [
"MAIL_FROM_EMAIL",
"NUXT_TURSO_DATABASE_URL",
"NUXT_TURSO_AUTH_TOKEN",
"BETTER_AUTH_SECRET",
"NUXT_BETTER_AUTH_URL",
"BETTER_AUTH_URL",
"MAILGUN_API_KEY",
"MAILGUN_DOMAIN",
"MAIL_FROM_EMAIL",

];

const missing = required.filter((key) => !env[key]);

if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(", ")}`);
}
}

2 changes: 1 addition & 1 deletion formkit/formkit.theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ const classes: Record<string, Record<string, boolean>> = {
"group-data-[invalid]:ring-red-500": true,
"group-data-[disabled]:bg-neutral-100": true,
"group-data-[disabled]:!cursor-not-allowed": true,
"shadow": true,
"shadow": false,
"group-[]/repeater:shadow-none": true,
"group-[]/multistep:shadow-none": true,
"dark:bg-transparent": true,
Expand Down
5 changes: 2 additions & 3 deletions lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
import * as schema from "../db/schema";
import { useDrizzle } from "../server/utils/drizzle";

import { sendUserVerificationEmail } from "~~/server/utils/email";

export const auth = betterAuth({
database: drizzleAdapter(useDrizzle(), {
Expand Down Expand Up @@ -31,9 +32,7 @@ export const auth = betterAuth({
},
emailVerification: {
async sendVerificationEmail(user, url) {
console.log("Sending verification email to", user.email);
console.log(url);

await sendUserVerificationEmail(user, url);
},
sendOnSignUp: true,
},
Expand Down
6 changes: 6 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
// https://nuxt.com/docs/api/configuration/nuxt-config

import { checkEnv } from "./config/env.config"
import { env } from "node:process";

checkEnv(env);

export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
future: { compatibilityVersion: 4 },
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"drizzle-orm": "^0.36.0",
"embla-carousel-vue": "^8.3.1",
"lucide-vue-next": "^0.454.0",
"nuxt": "^3.13.2",
"mjml": "^4.15.3",
"nuxt": "^3.14.0",
"shadcn-nuxt": "^0.10.4",
"vaul-vue": "^0.2.0",
"vee-validate": "^4.14.6",
Expand Down
Loading

0 comments on commit 2aef12f

Please sign in to comment.