feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
43
packages/email/src/templates/_components/button.tsx
Normal file
43
packages/email/src/templates/_components/button.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Button as ReactEmailButton } from "@react-email/components";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@turbostarter/ui";
|
||||
|
||||
import type { ButtonProps as ReactEmailButtonProps } from "@react-email/components";
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
|
||||
export const buttonVariants = cva(
|
||||
"block rounded-md text-center text-sm font-medium whitespace-nowrap",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground",
|
||||
destructive: "bg-destructive text-destructive-foreground",
|
||||
outline: "border-input bg-background border",
|
||||
secondary: "bg-secondary text-secondary-foreground",
|
||||
link: "text-primary underline-offset-4",
|
||||
},
|
||||
size: {
|
||||
default: "px-4 py-2.5",
|
||||
sm: "rounded-md px-3",
|
||||
lg: "rounded-md px-8",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export type ButtonProps = ReactEmailButtonProps &
|
||||
VariantProps<typeof buttonVariants>;
|
||||
|
||||
export const Button = ({ variant, size, className, ...props }: ButtonProps) => {
|
||||
return (
|
||||
<ReactEmailButton
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
29
packages/email/src/templates/_components/layout/footer.tsx
Normal file
29
packages/email/src/templates/_components/layout/footer.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Img, Link, Text } from "@react-email/components";
|
||||
|
||||
import { env } from "../../../env";
|
||||
|
||||
interface FooterProps {
|
||||
readonly origin: string;
|
||||
}
|
||||
|
||||
export const Footer = ({ origin }: FooterProps) => {
|
||||
return (
|
||||
<>
|
||||
<Img
|
||||
src={`${origin}/images/logo.png`}
|
||||
alt="Turbostarter Logo"
|
||||
height={45}
|
||||
className="mt-12"
|
||||
/>
|
||||
<Text className="text-muted-foreground max-w-[250px] leading-normal">
|
||||
<Link
|
||||
href="https://turbostarter.dev"
|
||||
className="text-muted-foreground"
|
||||
style={{ textDecoration: "underline" }}
|
||||
>
|
||||
{env.PRODUCT_NAME}
|
||||
</Link>
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
16
packages/email/src/templates/_components/layout/header.tsx
Normal file
16
packages/email/src/templates/_components/layout/header.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Img } from "@react-email/components";
|
||||
|
||||
interface HeaderProps {
|
||||
readonly origin: string;
|
||||
}
|
||||
|
||||
export const Header = ({ origin }: HeaderProps) => {
|
||||
return (
|
||||
<Img
|
||||
src={`${origin}/images/logo-text.png`}
|
||||
alt="Turbostarter Logo"
|
||||
className="mb-10"
|
||||
height={45}
|
||||
/>
|
||||
);
|
||||
};
|
||||
91
packages/email/src/templates/_components/layout/layout.tsx
Normal file
91
packages/email/src/templates/_components/layout/layout.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
Container,
|
||||
Font,
|
||||
Head,
|
||||
Html,
|
||||
Section,
|
||||
Tailwind,
|
||||
} from "@react-email/components";
|
||||
|
||||
import { Footer } from "./footer";
|
||||
import { Header } from "./header";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
export const Layout = ({
|
||||
children,
|
||||
origin,
|
||||
locale,
|
||||
}: PropsWithChildren<{ origin?: string | null; locale?: string }>) => {
|
||||
return (
|
||||
<Html lang={locale}>
|
||||
<Head>
|
||||
<Font
|
||||
fontFamily="Geist"
|
||||
fallbackFontFamily="Arial"
|
||||
fontWeight={400}
|
||||
fontStyle="normal"
|
||||
webFont={{
|
||||
url: "https://fonts.gstatic.com/s/geist/v3/gyByhwUxId8gMEwYGFWNOITddY4.woff2",
|
||||
format: "woff2",
|
||||
}}
|
||||
/>
|
||||
</Head>
|
||||
<Tailwind
|
||||
config={{
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: "#ffffff",
|
||||
foreground: "#09090b",
|
||||
card: {
|
||||
DEFAULT: "#ffffff",
|
||||
foreground: "#09090b",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "#ffffff",
|
||||
foreground: "#09090b",
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: "#f14704",
|
||||
foreground: "#fff7ed",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "#f4f4f5",
|
||||
foreground: "#18181b",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "#f4f4f5",
|
||||
foreground: "#71717b",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "#f4f4f5",
|
||||
foreground: "#18181b",
|
||||
},
|
||||
success: {
|
||||
DEFAULT: "#4ade80",
|
||||
foreground: "#09090b",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "#e7000b",
|
||||
foreground: "#fff7ed",
|
||||
},
|
||||
border: "#e4e4e7",
|
||||
input: "#e4e4e7",
|
||||
ring: "#f14704",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Section className="p-1">
|
||||
<Container className="bg-card text-card-foreground rounded-lg p-6">
|
||||
{origin && <Header origin={origin} />}
|
||||
{children}
|
||||
{origin && <Footer origin={origin} />}
|
||||
</Container>
|
||||
</Section>
|
||||
</Tailwind>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user