diff --git a/apps/backend/src/assets/logotipo-y-organica.png b/apps/backend/src/assets/logotipo-y-organica.png new file mode 100644 index 0000000..4179ead Binary files /dev/null and b/apps/backend/src/assets/logotipo-y-organica.png differ diff --git a/apps/backend/src/lib/email.ts b/apps/backend/src/lib/email.ts index e2269e9..83abbcf 100644 --- a/apps/backend/src/lib/email.ts +++ b/apps/backend/src/lib/email.ts @@ -1,9 +1,31 @@ +import { readFileSync } from 'node:fs' import nodemailer, { type Transporter } from 'nodemailer' type VerificationEmailData = { email: string; url: string; name?: string } type PasswordResetEmailData = { email: string; url: string; name?: string } type InvitationEmailData = { email: string; url: string; organizationName: string } +// Colores de marca (mismo sistema que la web: index.css de apps/web). +const BRAND_600 = '#2544ea' +const TEXT_STRONG = '#0f172a' +const TEXT_MUTED = '#475569' +const ON_ACCENT = '#ffffff' +const LINK_BG = BRAND_600 + +// Logotipo embebido como data URI para que los mails no dependan de hosting. +function loadLogoTypoDataUri(): string { + try { + const base64 = readFileSync( + new URL('../assets/logotipo-y-organica.png', import.meta.url), + ).toString('base64') + return `data:image/png;base64,${base64}` + } catch { + return '' + } +} + +const LOGO_DATA_URI = loadLogoTypoDataUri() + interface EmailProvider { sendVerificationEmail(data: VerificationEmailData): Promise sendPasswordResetEmail(data: PasswordResetEmailData): Promise @@ -30,11 +52,15 @@ class SMTPEmailProvider implements EmailProvider { } private layout(title: string, body: string) { + const header = LOGO_DATA_URI + ? `Gruperly` + : '

Gruperly

' + return ` -
-

Gruperly

-

${title}

-

${body}

+
+
${header}
+

${title}

+

${body}

` } @@ -49,7 +75,7 @@ class SMTPEmailProvider implements EmailProvider { } async sendVerificationEmail({ email, url, name }: VerificationEmailData) { - const link = `Verificar email` + const link = `Verificar email` await this.send( email, 'Verifica tu email en Gruperly', @@ -61,7 +87,7 @@ class SMTPEmailProvider implements EmailProvider { } async sendPasswordResetEmail({ email, url, name }: PasswordResetEmailData) { - const link = `Restablecer contraseña` + const link = `Restablecer contraseña` await this.send( email, 'Restablece tu contraseña en Gruperly', @@ -73,7 +99,7 @@ class SMTPEmailProvider implements EmailProvider { } async sendOrganizationInvitation({ email, url, organizationName }: InvitationEmailData) { - const link = `Unirme a ${organizationName}` + const link = `Unirme a ${organizationName}` await this.send( email, `Te invitaron a ${organizationName} en Gruperly`, diff --git a/apps/web/index.html b/apps/web/index.html index 55e9213..1b6cde3 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -4,9 +4,28 @@ Gruperly + + + + + +
- + \ No newline at end of file diff --git a/apps/web/public/apple-touch-icon.png b/apps/web/public/apple-touch-icon.png new file mode 100644 index 0000000..9817109 Binary files /dev/null and b/apps/web/public/apple-touch-icon.png differ diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg new file mode 100644 index 0000000..56d29ed --- /dev/null +++ b/apps/web/public/favicon.svg @@ -0,0 +1,12 @@ + + Gruperly + + + + \ No newline at end of file diff --git a/apps/web/src/assets/brand/isotipo-y-organica-oscuro.svg b/apps/web/src/assets/brand/isotipo-y-organica-oscuro.svg new file mode 100644 index 0000000..1ac5bc2 --- /dev/null +++ b/apps/web/src/assets/brand/isotipo-y-organica-oscuro.svg @@ -0,0 +1,6 @@ + + Gruperly — isotipo y orgánica + Y orgánica con rama corta blanco azulado #EEF4FF y diagonal larga celeste #93B4FD, separadas, sobre fondo transparente. + + + \ No newline at end of file diff --git a/apps/web/src/assets/brand/isotipo-y-organica.svg b/apps/web/src/assets/brand/isotipo-y-organica.svg new file mode 100644 index 0000000..a7f9dcd --- /dev/null +++ b/apps/web/src/assets/brand/isotipo-y-organica.svg @@ -0,0 +1,6 @@ + + Gruperly — isotipo y orgánica + Y orgánica con rama corta azul noche #171D54 y diagonal larga celeste #93B4FD, separadas, sobre fondo transparente. + + + \ No newline at end of file diff --git a/apps/web/src/assets/brand/logotipo-y-organica-oscuro.svg b/apps/web/src/assets/brand/logotipo-y-organica-oscuro.svg new file mode 100644 index 0000000..2335bda --- /dev/null +++ b/apps/web/src/assets/brand/logotipo-y-organica-oscuro.svg @@ -0,0 +1,7 @@ + +Gruperly — y orgánica bicolor +Rama corta azul noche y diagonal larga celeste, separadas, con curvas suaves y terminales redondeados. + + + + \ No newline at end of file diff --git a/apps/web/src/assets/brand/logotipo-y-organica.svg b/apps/web/src/assets/brand/logotipo-y-organica.svg new file mode 100644 index 0000000..5a4f989 --- /dev/null +++ b/apps/web/src/assets/brand/logotipo-y-organica.svg @@ -0,0 +1,7 @@ + +Gruperly — y orgánica bicolor +Rama corta azul noche y diagonal larga celeste, separadas, con curvas suaves y terminales redondeados. + + + + \ No newline at end of file diff --git a/apps/web/src/components/auth/AuthShell.tsx b/apps/web/src/components/auth/AuthShell.tsx index a0c0158..f080c85 100644 --- a/apps/web/src/components/auth/AuthShell.tsx +++ b/apps/web/src/components/auth/AuthShell.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from 'react' -import { Logo, LogoIcon } from '../brand' +import { Logo } from '../brand' export type AuthShellProps = { title: string @@ -11,11 +11,10 @@ export function AuthShell({ title, subtitle, children }: AuthShellProps) { return (
-
- - +
+
-
+

{title}

{subtitle ?

{subtitle}

: null}
{children}
diff --git a/apps/web/src/components/brand/Logo.tsx b/apps/web/src/components/brand/Logo.tsx index 514b481..22e5512 100644 --- a/apps/web/src/components/brand/Logo.tsx +++ b/apps/web/src/components/brand/Logo.tsx @@ -1,23 +1,35 @@ -import { forwardRef, type HTMLAttributes } from 'react' +import { forwardRef, type SVGProps } from 'react' import { cn } from '../../lib/utils' -export type LogoProps = Omit, 'children'> & { - className?: string -} +export type LogoProps = SVGProps -export const Logo = forwardRef( +export const Logo = forwardRef( ({ className, ...props }, ref) => { return ( - - gruperl - y - + Gruperly + + + + ) }, ) -Logo.displayName = 'Logo' +Logo.displayName = 'Logo' \ No newline at end of file diff --git a/apps/web/src/components/brand/LogoIcon.tsx b/apps/web/src/components/brand/LogoIcon.tsx index 2250e30..e3993aa 100644 --- a/apps/web/src/components/brand/LogoIcon.tsx +++ b/apps/web/src/components/brand/LogoIcon.tsx @@ -10,30 +10,24 @@ export const LogoIcon = forwardRef( return ( - Gruperly + - ) }, ) -LogoIcon.displayName = 'LogoIcon' +LogoIcon.displayName = 'LogoIcon' \ No newline at end of file diff --git a/apps/web/src/components/layout/BottomNav.tsx b/apps/web/src/components/layout/BottomNav.tsx index 5cd17a0..05adc0f 100644 --- a/apps/web/src/components/layout/BottomNav.tsx +++ b/apps/web/src/components/layout/BottomNav.tsx @@ -6,7 +6,7 @@ export function BottomNav() { const location = useLocation() return ( -