diff --git a/apps/web/config/app.config.ts b/apps/web/config/app.config.ts index 5ca278a20..8430b0b56 100644 --- a/apps/web/config/app.config.ts +++ b/apps/web/config/app.config.ts @@ -8,11 +8,27 @@ enum Themes { } const AppConfigSchema = z.object({ - name: z.string(), - title: z.string(), - description: z.string(), - url: z.string(), - locale: z.string().default('en'), + name: z + .string({ + description: `This is the name of your SaaS. Ex. "Makerkit"`, + }) + .min(1), + title: z + .string({ + description: `This is the default title tag of your SaaS.`, + }) + .min(1), + description: z.string({ + description: `This is the default description of your SaaS.`, + }), + url: z.string().url({ + message: `Please provide a valid URL. Example: 'https://example.com'`, + }), + locale: z + .string({ + description: `This is the default locale of your SaaS.`, + }) + .default('en'), theme: z.nativeEnum(Themes), production: z.boolean(), themeColor: z.string(), diff --git a/apps/web/config/auth.config.ts b/apps/web/config/auth.config.ts index 2db320ad3..3f007d23d 100644 --- a/apps/web/config/auth.config.ts +++ b/apps/web/config/auth.config.ts @@ -6,8 +6,12 @@ const providers: z.ZodType = getProviders(); const AuthConfigSchema = z.object({ providers: z.object({ - password: z.boolean(), - magicLink: z.boolean(), + password: z.boolean({ + description: 'Enable password authentication.', + }), + magicLink: z.boolean({ + description: 'Enable magic link authentication.', + }), oAuth: providers.array(), }), }); diff --git a/packages/features/auth/package.json b/packages/features/auth/package.json index c8da612b3..1d470bb96 100644 --- a/packages/features/auth/package.json +++ b/packages/features/auth/package.json @@ -18,6 +18,7 @@ "devDependencies": { "@kit/eslint-config": "0.2.0", "@kit/prettier-config": "0.1.0", + "@kit/shared": "0.1.0", "@kit/supabase": "0.1.0", "@kit/tailwind-config": "0.1.0", "@kit/tsconfig": "0.1.0", @@ -25,7 +26,8 @@ "@radix-ui/react-icons": "^1.3.0", "@tanstack/react-query": "5.28.6", "react-i18next": "^14.1.0", - "sonner": "^1.4.41" + "sonner": "^1.4.41", + "zod": "^3.22.4" }, "prettier": "@kit/prettier-config", "eslintConfig": { diff --git a/packages/features/auth/src/components/sign-in-methods-container.tsx b/packages/features/auth/src/components/sign-in-methods-container.tsx index 9419beaad..4062aaa6f 100644 --- a/packages/features/auth/src/components/sign-in-methods-container.tsx +++ b/packages/features/auth/src/components/sign-in-methods-container.tsx @@ -4,8 +4,7 @@ import { useRouter, useSearchParams } from 'next/navigation'; import type { Provider } from '@supabase/supabase-js'; -import { isBrowser } from '@supabase/ssr'; - +import { isBrowser } from '@kit/shared/utils'; import { Divider } from '@kit/ui/divider'; import { If } from '@kit/ui/if'; diff --git a/packages/mailers/src/impl/cloudflare/index.ts b/packages/mailers/src/impl/cloudflare/index.ts new file mode 100644 index 000000000..3fcd9f466 --- /dev/null +++ b/packages/mailers/src/impl/cloudflare/index.ts @@ -0,0 +1,17 @@ +import 'server-only'; +import { z } from 'zod'; + +import { Mailer } from '../../mailer'; +import { MailerSchema } from '../../schema/mailer.schema'; + +type Config = z.infer; + +/** + * A class representing a mailer using Cloudflare's Workers. + * @implements {Mailer} + */ +export class CloudflareMailer implements Mailer { + async sendEmail(config: Config) { + throw new Error('Not implemented'); + } +} diff --git a/packages/mailers/src/impl/nodemailer.ts b/packages/mailers/src/impl/nodemailer.ts deleted file mode 100644 index 20f9d2a7c..000000000 --- a/packages/mailers/src/impl/nodemailer.ts +++ /dev/null @@ -1,59 +0,0 @@ -import 'server-only'; -import { z } from 'zod'; - -import { Mailer } from '../mailer'; -import { MailerSchema } from '../mailer-schema'; - -type Config = z.infer; - -/** - * A class representing a mailer using Nodemailer library. - * @implements {Mailer} - */ -export class Nodemailer implements Mailer { - async sendEmail(config: Config) { - const transporter = await getSMTPTransporter(); - - return transporter.sendMail(config); - } -} - -/** - * @description SMTP Transporter for production use. Add your favorite email - * API details (Mailgun, Sendgrid, etc.) to the appConfig. - */ -async function getSMTPTransporter() { - const { createTransport } = await import('nodemailer'); - - return createTransport(getSMTPConfiguration()); -} - -function getSMTPConfiguration() { - const user = process.env.EMAIL_USER; - const pass = process.env.EMAIL_PASSWORD; - const host = process.env.EMAIL_HOST; - const port = Number(process.env.EMAIL_PORT); - const secure = process.env.EMAIL_TLS !== 'false'; - - // validate that we have all the required appConfig - if (!user || !pass || !host || !port) { - throw new Error( - `Missing email configuration. Please add the following environment variables: - EMAIL_USER - EMAIL_PASSWORD - EMAIL_HOST - EMAIL_PORT - `, - ); - } - - return { - host, - port, - secure, - auth: { - user, - pass, - }, - }; -} diff --git a/packages/mailers/src/impl/nodemailer/index.ts b/packages/mailers/src/impl/nodemailer/index.ts new file mode 100644 index 000000000..c3623001b --- /dev/null +++ b/packages/mailers/src/impl/nodemailer/index.ts @@ -0,0 +1,21 @@ +import 'server-only'; +import { z } from 'zod'; + +import { Mailer } from '../../mailer'; +import { MailerSchema } from '../../schema/mailer.schema'; +import { getSMTPConfiguration } from '../../smtp-configuration'; + +type Config = z.infer; + +/** + * A class representing a mailer using Nodemailer library. + * @implements {Mailer} + */ +export class Nodemailer implements Mailer { + async sendEmail(config: Config) { + const { createTransport } = await import('nodemailer'); + const transporter = createTransport(getSMTPConfiguration()); + + return transporter.sendMail(config); + } +} diff --git a/packages/mailers/src/mailer.ts b/packages/mailers/src/mailer.ts index c07289704..ab4578956 100644 --- a/packages/mailers/src/mailer.ts +++ b/packages/mailers/src/mailer.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { MailerSchema } from './mailer-schema'; +import { MailerSchema } from './schema/mailer.schema'; export abstract class Mailer { abstract sendEmail(data: z.infer): Promise; diff --git a/packages/mailers/src/mailer-schema.ts b/packages/mailers/src/schema/mailer.schema.ts similarity index 100% rename from packages/mailers/src/mailer-schema.ts rename to packages/mailers/src/schema/mailer.schema.ts diff --git a/packages/mailers/src/schema/smtp-config.schema.ts b/packages/mailers/src/schema/smtp-config.schema.ts new file mode 100644 index 000000000..55a97aeb9 --- /dev/null +++ b/packages/mailers/src/schema/smtp-config.schema.ts @@ -0,0 +1,49 @@ +import { z } from 'zod'; + +/* +const user = process.env.EMAIL_USER; + const pass = process.env.EMAIL_PASSWORD; + const host = process.env.EMAIL_HOST; + const port = Number(process.env.EMAIL_PORT); + const secure = process.env.EMAIL_TLS !== 'false'; + + // validate that we have all the required appConfig + if (!user || !pass || !host || !port) { + throw new Error( + `Missing email configuration. Please add the following environment variables: + EMAIL_USER + EMAIL_PASSWORD + EMAIL_HOST + EMAIL_PORT + `, + ); + } + + return { + host, + port, + secure, + auth: { + user, + pass, + }, + }; + */ + +export const SmtpConfigSchema = z.object({ + user: z.string({ + description: + 'This is the email account to send emails from. This is specific to the email provider.', + }), + pass: z.string({ + description: 'This is the password for the email account', + }), + host: z.string({ + description: 'This is the SMTP host for the email provider', + }), + port: z.number({ + description: + 'This is the port for the email provider. Normally 587 or 465.', + }), + secure: z.boolean(), +}); diff --git a/packages/mailers/src/smtp-configuration.ts b/packages/mailers/src/smtp-configuration.ts new file mode 100644 index 000000000..bc863604a --- /dev/null +++ b/packages/mailers/src/smtp-configuration.ts @@ -0,0 +1,21 @@ +import { SmtpConfigSchema } from './schema/smtp-config.schema'; + +export function getSMTPConfiguration() { + const data = SmtpConfigSchema.parse({ + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASSWORD, + host: process.env.EMAIL_HOST, + port: Number(process.env.EMAIL_PORT), + secure: process.env.EMAIL_TLS !== 'false', + }); + + return { + host: data.host, + port: data.port, + secure: data.secure, + auth: { + user: data.user, + pass: data.pass, + }, + }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 586d3a075..47404b492 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -337,6 +337,9 @@ importers: '@kit/prettier-config': specifier: 0.1.0 version: link:../../../tooling/prettier + '@kit/shared': + specifier: 0.1.0 + version: link:../../shared '@kit/supabase': specifier: 0.1.0 version: link:../../supabase @@ -361,6 +364,9 @@ importers: sonner: specifier: ^1.4.41 version: 1.4.41(react-dom@18.2.0)(react@18.2.0) + zod: + specifier: ^3.22.4 + version: 3.22.4 packages/features/team-accounts: devDependencies: