create-blitzpack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +452 -0
- package/package.json +57 -0
- package/template/.dockerignore +59 -0
- package/template/.github/workflows/ci.yml +157 -0
- package/template/.husky/pre-commit +1 -0
- package/template/.husky/pre-push +1 -0
- package/template/.lintstagedrc.cjs +4 -0
- package/template/.nvmrc +1 -0
- package/template/.prettierrc +9 -0
- package/template/.vscode/settings.json +13 -0
- package/template/CLAUDE.md +175 -0
- package/template/CONTRIBUTING.md +32 -0
- package/template/Dockerfile +90 -0
- package/template/GETTING_STARTED.md +35 -0
- package/template/LICENSE +21 -0
- package/template/README.md +116 -0
- package/template/apps/api/.dockerignore +51 -0
- package/template/apps/api/.env.local.example +62 -0
- package/template/apps/api/emails/account-deleted-email.tsx +69 -0
- package/template/apps/api/emails/components/email-layout.tsx +154 -0
- package/template/apps/api/emails/config.ts +22 -0
- package/template/apps/api/emails/password-changed-email.tsx +88 -0
- package/template/apps/api/emails/password-reset-email.tsx +86 -0
- package/template/apps/api/emails/verification-email.tsx +85 -0
- package/template/apps/api/emails/welcome-email.tsx +70 -0
- package/template/apps/api/package.json +84 -0
- package/template/apps/api/prisma/migrations/20251012111439_init/migration.sql +13 -0
- package/template/apps/api/prisma/migrations/20251018162629_add_better_auth_fields/migration.sql +67 -0
- package/template/apps/api/prisma/migrations/20251019142208_add_user_role_enum/migration.sql +5 -0
- package/template/apps/api/prisma/migrations/20251019182151_user_auth/migration.sql +7 -0
- package/template/apps/api/prisma/migrations/20251019211416_faster_session_lookup/migration.sql +2 -0
- package/template/apps/api/prisma/migrations/20251119124337_add_upload_model/migration.sql +26 -0
- package/template/apps/api/prisma/migrations/20251120071241_add_scope_to_account/migration.sql +2 -0
- package/template/apps/api/prisma/migrations/20251120072608_add_oauth_token_expiration_fields/migration.sql +10 -0
- package/template/apps/api/prisma/migrations/20251120144705_add_audit_logs/migration.sql +29 -0
- package/template/apps/api/prisma/migrations/20251127123614_remove_impersonated_by/migration.sql +8 -0
- package/template/apps/api/prisma/migrations/20251127125630_remove_audit_logs/migration.sql +11 -0
- package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
- package/template/apps/api/prisma/schema.prisma +116 -0
- package/template/apps/api/prisma/seed.ts +159 -0
- package/template/apps/api/prisma.config.ts +14 -0
- package/template/apps/api/src/app.ts +377 -0
- package/template/apps/api/src/common/logger.service.ts +227 -0
- package/template/apps/api/src/config/env.ts +60 -0
- package/template/apps/api/src/config/rate-limit.ts +29 -0
- package/template/apps/api/src/hooks/auth.ts +122 -0
- package/template/apps/api/src/plugins/auth.ts +198 -0
- package/template/apps/api/src/plugins/database.ts +45 -0
- package/template/apps/api/src/plugins/logger.ts +33 -0
- package/template/apps/api/src/plugins/multipart.ts +16 -0
- package/template/apps/api/src/plugins/scalar.ts +20 -0
- package/template/apps/api/src/plugins/schedule.ts +52 -0
- package/template/apps/api/src/plugins/services.ts +66 -0
- package/template/apps/api/src/plugins/swagger.ts +56 -0
- package/template/apps/api/src/routes/accounts.ts +91 -0
- package/template/apps/api/src/routes/admin-sessions.ts +92 -0
- package/template/apps/api/src/routes/metrics.ts +71 -0
- package/template/apps/api/src/routes/password.ts +46 -0
- package/template/apps/api/src/routes/sessions.ts +53 -0
- package/template/apps/api/src/routes/stats.ts +38 -0
- package/template/apps/api/src/routes/uploads-serve.ts +27 -0
- package/template/apps/api/src/routes/uploads.ts +154 -0
- package/template/apps/api/src/routes/users.ts +114 -0
- package/template/apps/api/src/routes/verification.ts +90 -0
- package/template/apps/api/src/server.ts +34 -0
- package/template/apps/api/src/services/accounts.service.ts +125 -0
- package/template/apps/api/src/services/authorization.service.ts +162 -0
- package/template/apps/api/src/services/email.service.ts +170 -0
- package/template/apps/api/src/services/file-storage.service.ts +267 -0
- package/template/apps/api/src/services/metrics.service.ts +175 -0
- package/template/apps/api/src/services/password.service.ts +56 -0
- package/template/apps/api/src/services/sessions.service.spec.ts +134 -0
- package/template/apps/api/src/services/sessions.service.ts +276 -0
- package/template/apps/api/src/services/stats.service.ts +273 -0
- package/template/apps/api/src/services/uploads.service.ts +163 -0
- package/template/apps/api/src/services/users.service.spec.ts +249 -0
- package/template/apps/api/src/services/users.service.ts +198 -0
- package/template/apps/api/src/utils/file-validation.ts +108 -0
- package/template/apps/api/start.sh +33 -0
- package/template/apps/api/test/helpers/fastify-app.ts +24 -0
- package/template/apps/api/test/helpers/mock-authorization.ts +16 -0
- package/template/apps/api/test/helpers/mock-logger.ts +28 -0
- package/template/apps/api/test/helpers/mock-prisma.ts +30 -0
- package/template/apps/api/test/helpers/test-db.ts +125 -0
- package/template/apps/api/test/integration/auth-flow.integration.spec.ts +449 -0
- package/template/apps/api/test/integration/password.integration.spec.ts +427 -0
- package/template/apps/api/test/integration/rate-limit.integration.spec.ts +51 -0
- package/template/apps/api/test/integration/sessions.integration.spec.ts +445 -0
- package/template/apps/api/test/integration/users.integration.spec.ts +211 -0
- package/template/apps/api/test/setup.ts +31 -0
- package/template/apps/api/tsconfig.json +26 -0
- package/template/apps/api/vitest.config.ts +35 -0
- package/template/apps/web/.env.local.example +11 -0
- package/template/apps/web/components.json +24 -0
- package/template/apps/web/next.config.ts +22 -0
- package/template/apps/web/package.json +56 -0
- package/template/apps/web/postcss.config.js +5 -0
- package/template/apps/web/public/apple-icon.png +0 -0
- package/template/apps/web/public/icon.png +0 -0
- package/template/apps/web/public/robots.txt +3 -0
- package/template/apps/web/src/app/(admin)/admin/layout.tsx +222 -0
- package/template/apps/web/src/app/(admin)/admin/page.tsx +157 -0
- package/template/apps/web/src/app/(admin)/admin/sessions/page.tsx +18 -0
- package/template/apps/web/src/app/(admin)/admin/users/page.tsx +20 -0
- package/template/apps/web/src/app/(auth)/forgot-password/page.tsx +177 -0
- package/template/apps/web/src/app/(auth)/login/page.tsx +159 -0
- package/template/apps/web/src/app/(auth)/reset-password/page.tsx +245 -0
- package/template/apps/web/src/app/(auth)/signup/page.tsx +153 -0
- package/template/apps/web/src/app/dashboard/change-password/page.tsx +255 -0
- package/template/apps/web/src/app/dashboard/page.tsx +296 -0
- package/template/apps/web/src/app/error.tsx +32 -0
- package/template/apps/web/src/app/examples/file-upload/page.tsx +200 -0
- package/template/apps/web/src/app/favicon.ico +0 -0
- package/template/apps/web/src/app/global-error.tsx +96 -0
- package/template/apps/web/src/app/globals.css +22 -0
- package/template/apps/web/src/app/icon.png +0 -0
- package/template/apps/web/src/app/layout.tsx +34 -0
- package/template/apps/web/src/app/not-found.tsx +28 -0
- package/template/apps/web/src/app/page.tsx +192 -0
- package/template/apps/web/src/components/admin/activity-feed.tsx +101 -0
- package/template/apps/web/src/components/admin/charts/auth-breakdown-chart.tsx +114 -0
- package/template/apps/web/src/components/admin/charts/chart-tooltip.tsx +124 -0
- package/template/apps/web/src/components/admin/charts/realtime-metrics-chart.tsx +511 -0
- package/template/apps/web/src/components/admin/charts/role-distribution-chart.tsx +102 -0
- package/template/apps/web/src/components/admin/charts/session-activity-chart.tsx +90 -0
- package/template/apps/web/src/components/admin/charts/user-growth-chart.tsx +108 -0
- package/template/apps/web/src/components/admin/health-indicator.tsx +175 -0
- package/template/apps/web/src/components/admin/refresh-control.tsx +90 -0
- package/template/apps/web/src/components/admin/session-revoke-all-dialog.tsx +79 -0
- package/template/apps/web/src/components/admin/session-revoke-dialog.tsx +74 -0
- package/template/apps/web/src/components/admin/sessions-management-table.tsx +372 -0
- package/template/apps/web/src/components/admin/stat-card.tsx +137 -0
- package/template/apps/web/src/components/admin/user-create-dialog.tsx +152 -0
- package/template/apps/web/src/components/admin/user-delete-dialog.tsx +73 -0
- package/template/apps/web/src/components/admin/user-edit-dialog.tsx +170 -0
- package/template/apps/web/src/components/admin/users-management-table.tsx +285 -0
- package/template/apps/web/src/components/auth/email-verification-banner.tsx +85 -0
- package/template/apps/web/src/components/auth/github-button.tsx +40 -0
- package/template/apps/web/src/components/auth/google-button.tsx +54 -0
- package/template/apps/web/src/components/auth/protected-route.tsx +66 -0
- package/template/apps/web/src/components/auth/redirect-if-authenticated.tsx +31 -0
- package/template/apps/web/src/components/auth/with-auth.tsx +30 -0
- package/template/apps/web/src/components/error/error-card.tsx +47 -0
- package/template/apps/web/src/components/error/forbidden.tsx +25 -0
- package/template/apps/web/src/components/landing/command-block.tsx +64 -0
- package/template/apps/web/src/components/landing/feature-card.tsx +60 -0
- package/template/apps/web/src/components/landing/included-feature-card.tsx +63 -0
- package/template/apps/web/src/components/landing/logo.tsx +41 -0
- package/template/apps/web/src/components/landing/tech-badge.tsx +11 -0
- package/template/apps/web/src/components/layout/auth-nav.tsx +58 -0
- package/template/apps/web/src/components/layout/footer.tsx +3 -0
- package/template/apps/web/src/config/landing-data.ts +152 -0
- package/template/apps/web/src/config/site.ts +5 -0
- package/template/apps/web/src/hooks/api/__tests__/use-users.test.tsx +181 -0
- package/template/apps/web/src/hooks/api/use-admin-sessions.ts +75 -0
- package/template/apps/web/src/hooks/api/use-admin-stats.ts +33 -0
- package/template/apps/web/src/hooks/api/use-sessions.ts +52 -0
- package/template/apps/web/src/hooks/api/use-uploads.ts +156 -0
- package/template/apps/web/src/hooks/api/use-users.ts +149 -0
- package/template/apps/web/src/hooks/use-mobile.ts +21 -0
- package/template/apps/web/src/hooks/use-realtime-metrics.ts +120 -0
- package/template/apps/web/src/lib/__tests__/utils.test.ts +29 -0
- package/template/apps/web/src/lib/api.ts +151 -0
- package/template/apps/web/src/lib/auth.ts +13 -0
- package/template/apps/web/src/lib/env.ts +52 -0
- package/template/apps/web/src/lib/form-utils.ts +11 -0
- package/template/apps/web/src/lib/utils.ts +1 -0
- package/template/apps/web/src/providers.tsx +34 -0
- package/template/apps/web/src/store/atoms.ts +15 -0
- package/template/apps/web/src/test/helpers/test-utils.tsx +44 -0
- package/template/apps/web/src/test/setup.ts +8 -0
- package/template/apps/web/tailwind.config.ts +5 -0
- package/template/apps/web/tsconfig.json +26 -0
- package/template/apps/web/vitest.config.ts +32 -0
- package/template/assets/logo-512.png +0 -0
- package/template/assets/logo.svg +4 -0
- package/template/docker-compose.prod.yml +66 -0
- package/template/docker-compose.yml +36 -0
- package/template/eslint.config.ts +119 -0
- package/template/package.json +77 -0
- package/template/packages/tailwind-config/package.json +9 -0
- package/template/packages/tailwind-config/theme.css +179 -0
- package/template/packages/types/package.json +29 -0
- package/template/packages/types/src/__tests__/schemas.test.ts +255 -0
- package/template/packages/types/src/api-response.ts +53 -0
- package/template/packages/types/src/health-check.ts +11 -0
- package/template/packages/types/src/pagination.ts +41 -0
- package/template/packages/types/src/role.ts +5 -0
- package/template/packages/types/src/session.ts +48 -0
- package/template/packages/types/src/stats.ts +113 -0
- package/template/packages/types/src/upload.ts +51 -0
- package/template/packages/types/src/user.ts +36 -0
- package/template/packages/types/tsconfig.json +5 -0
- package/template/packages/types/vitest.config.ts +21 -0
- package/template/packages/ui/components.json +21 -0
- package/template/packages/ui/package.json +108 -0
- package/template/packages/ui/src/__tests__/button.test.tsx +70 -0
- package/template/packages/ui/src/alert-dialog.tsx +141 -0
- package/template/packages/ui/src/alert.tsx +66 -0
- package/template/packages/ui/src/animated-theme-toggler.tsx +167 -0
- package/template/packages/ui/src/avatar.tsx +53 -0
- package/template/packages/ui/src/badge.tsx +36 -0
- package/template/packages/ui/src/button.tsx +84 -0
- package/template/packages/ui/src/card.tsx +92 -0
- package/template/packages/ui/src/checkbox.tsx +32 -0
- package/template/packages/ui/src/data-table/data-table-column-header.tsx +68 -0
- package/template/packages/ui/src/data-table/data-table-pagination.tsx +99 -0
- package/template/packages/ui/src/data-table/data-table-toolbar.tsx +55 -0
- package/template/packages/ui/src/data-table/data-table-view-options.tsx +63 -0
- package/template/packages/ui/src/data-table/data-table.tsx +167 -0
- package/template/packages/ui/src/dialog.tsx +143 -0
- package/template/packages/ui/src/dropdown-menu.tsx +257 -0
- package/template/packages/ui/src/empty-state.tsx +52 -0
- package/template/packages/ui/src/file-upload-input.tsx +202 -0
- package/template/packages/ui/src/form.tsx +168 -0
- package/template/packages/ui/src/hooks/use-mobile.ts +19 -0
- package/template/packages/ui/src/icons/brand-icons.tsx +16 -0
- package/template/packages/ui/src/input.tsx +21 -0
- package/template/packages/ui/src/label.tsx +24 -0
- package/template/packages/ui/src/lib/utils.ts +6 -0
- package/template/packages/ui/src/password-input.tsx +102 -0
- package/template/packages/ui/src/popover.tsx +48 -0
- package/template/packages/ui/src/radio-group.tsx +45 -0
- package/template/packages/ui/src/scroll-area.tsx +58 -0
- package/template/packages/ui/src/select.tsx +187 -0
- package/template/packages/ui/src/separator.tsx +28 -0
- package/template/packages/ui/src/sheet.tsx +139 -0
- package/template/packages/ui/src/sidebar.tsx +726 -0
- package/template/packages/ui/src/skeleton-variants.tsx +87 -0
- package/template/packages/ui/src/skeleton.tsx +13 -0
- package/template/packages/ui/src/slider.tsx +63 -0
- package/template/packages/ui/src/sonner.tsx +25 -0
- package/template/packages/ui/src/spinner.tsx +16 -0
- package/template/packages/ui/src/switch.tsx +31 -0
- package/template/packages/ui/src/table.tsx +116 -0
- package/template/packages/ui/src/tabs.tsx +66 -0
- package/template/packages/ui/src/textarea.tsx +18 -0
- package/template/packages/ui/src/tooltip.tsx +61 -0
- package/template/packages/ui/src/user-avatar.tsx +97 -0
- package/template/packages/ui/test-config.js +3 -0
- package/template/packages/ui/tsconfig.json +12 -0
- package/template/packages/ui/turbo.json +18 -0
- package/template/packages/ui/vitest.config.ts +17 -0
- package/template/packages/ui/vitest.setup.ts +1 -0
- package/template/packages/utils/package.json +23 -0
- package/template/packages/utils/src/__tests__/utils.test.ts +223 -0
- package/template/packages/utils/src/array.ts +18 -0
- package/template/packages/utils/src/async.ts +3 -0
- package/template/packages/utils/src/date.ts +77 -0
- package/template/packages/utils/src/errors.ts +73 -0
- package/template/packages/utils/src/number.ts +11 -0
- package/template/packages/utils/src/string.ts +13 -0
- package/template/packages/utils/tsconfig.json +5 -0
- package/template/packages/utils/vitest.config.ts +21 -0
- package/template/pnpm-workspace.yaml +4 -0
- package/template/tsconfig.base.json +32 -0
- package/template/turbo.json +133 -0
- package/template/vitest.shared.ts +26 -0
- package/template/vitest.workspace.ts +9 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules
|
|
3
|
+
.pnpm-store
|
|
4
|
+
|
|
5
|
+
# Build outputs
|
|
6
|
+
dist
|
|
7
|
+
build
|
|
8
|
+
.next
|
|
9
|
+
*.tsbuildinfo
|
|
10
|
+
|
|
11
|
+
# Development files
|
|
12
|
+
.env
|
|
13
|
+
.env.local
|
|
14
|
+
.env.*.local
|
|
15
|
+
*.log
|
|
16
|
+
npm-debug.log*
|
|
17
|
+
yarn-debug.log*
|
|
18
|
+
yarn-error.log*
|
|
19
|
+
pnpm-debug.log*
|
|
20
|
+
|
|
21
|
+
# Testing
|
|
22
|
+
coverage
|
|
23
|
+
test-results
|
|
24
|
+
.vitest
|
|
25
|
+
|
|
26
|
+
# IDE
|
|
27
|
+
.vscode
|
|
28
|
+
.idea
|
|
29
|
+
*.swp
|
|
30
|
+
*.swo
|
|
31
|
+
.DS_Store
|
|
32
|
+
|
|
33
|
+
# Git
|
|
34
|
+
.git
|
|
35
|
+
.gitignore
|
|
36
|
+
.gitattributes
|
|
37
|
+
|
|
38
|
+
# Documentation
|
|
39
|
+
*.md
|
|
40
|
+
!README.md
|
|
41
|
+
|
|
42
|
+
# CI/CD
|
|
43
|
+
.github
|
|
44
|
+
.gitlab-ci.yml
|
|
45
|
+
.travis.yml
|
|
46
|
+
|
|
47
|
+
# Misc
|
|
48
|
+
.cache
|
|
49
|
+
.temp
|
|
50
|
+
tmp
|
|
51
|
+
temp
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
NODE_ENV=development
|
|
2
|
+
API_URL=http://localhost:8080
|
|
3
|
+
FRONTEND_URL=http://localhost:3000
|
|
4
|
+
PORT=8080
|
|
5
|
+
|
|
6
|
+
# Database connection string for local Docker PostgreSQL
|
|
7
|
+
# Match credentials from docker-compose.yml
|
|
8
|
+
# Note: Test database will automatically use app_dev_test (suffix added by test runner)
|
|
9
|
+
DATABASE_URL=postgresql://postgres:postgres_dev_password@localhost:5432/app_dev
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Cookie secret for signing cookies
|
|
13
|
+
COOKIE_SECRET=dev-secret-change-in-production-use-random-string
|
|
14
|
+
|
|
15
|
+
# Logging verbosity level
|
|
16
|
+
# - minimal: Only errors and critical warnings (recommended for production)
|
|
17
|
+
# - normal: Standard HTTP requests, business events, errors (default)
|
|
18
|
+
# - detailed: Include debug info, SQL queries, cache operations
|
|
19
|
+
# - verbose: Everything including request/response bodies, timing breakdowns
|
|
20
|
+
LOG_LEVEL=normal
|
|
21
|
+
|
|
22
|
+
# Better Auth configuration
|
|
23
|
+
BETTER_AUTH_SECRET=dev-secret-change-in-production-must-be-at-least-32-characters
|
|
24
|
+
# Base URL of your API for Better Auth callbacks
|
|
25
|
+
BETTER_AUTH_URL=http://localhost:8080
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Email Service Configuration
|
|
30
|
+
# You MUST verify your domain in Resend dashboard before sending emails
|
|
31
|
+
RESEND_API_KEY=re_xxxxxxxxxxxx
|
|
32
|
+
EMAIL_FROM=noreply@yourdomain.com
|
|
33
|
+
|
|
34
|
+
# OAuth Provider Credentials (Optional but recommended)
|
|
35
|
+
# Google and GitHub OAuth are enabled by default.
|
|
36
|
+
# Google OAuth Setup:
|
|
37
|
+
# 1. Go to: https://console.cloud.google.com/apis/credentials
|
|
38
|
+
# 2. Create OAuth 2.0 Client ID (Web application)
|
|
39
|
+
# 3. Add authorized redirect URI: http://localhost:8080/api/auth/callback/google
|
|
40
|
+
# 4. For production, add: https://<domain>.com/api/auth/callback/google
|
|
41
|
+
GOOGLE_CLIENT_ID=
|
|
42
|
+
GOOGLE_CLIENT_SECRET=
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# GitHub OAuth Setup:
|
|
46
|
+
# 1. Go to: https://github.com/settings/developers
|
|
47
|
+
# 2. Create new OAuth App
|
|
48
|
+
# 3. Set Authorization callback URL: http://localhost:8080/api/auth/callback/github
|
|
49
|
+
# 4. For production, add: https://<domain>.com/api/auth/callback/github
|
|
50
|
+
GITHUB_CLIENT_ID=
|
|
51
|
+
GITHUB_CLIENT_SECRET=
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# File Storage Configuration (OPTIONAL)
|
|
55
|
+
# Defaults to local file storage if not configured
|
|
56
|
+
# For S3-compatible storage (AWS S3, Cloudflare R2, MinIO, etc.)
|
|
57
|
+
# STORAGE_TYPE=local
|
|
58
|
+
# S3_BUCKET=
|
|
59
|
+
# S3_REGION=auto
|
|
60
|
+
# S3_ACCESS_KEY_ID=
|
|
61
|
+
# S3_SECRET_ACCESS_KEY=
|
|
62
|
+
# S3_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Link, Text } from '@react-email/components';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { EmailLayout } from './components/email-layout';
|
|
5
|
+
import { emailConfig } from './config';
|
|
6
|
+
|
|
7
|
+
interface AccountDeletedEmailProps {
|
|
8
|
+
userEmail: string;
|
|
9
|
+
deletedAt: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function AccountDeletedEmail({
|
|
13
|
+
userEmail = 'user@example.com',
|
|
14
|
+
deletedAt = new Date().toLocaleString('en-US', {
|
|
15
|
+
dateStyle: 'medium',
|
|
16
|
+
timeStyle: 'short',
|
|
17
|
+
}),
|
|
18
|
+
}: AccountDeletedEmailProps) {
|
|
19
|
+
const { appName, supportEmail } = emailConfig;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<EmailLayout
|
|
23
|
+
preview={`Your ${appName} account has been deleted`}
|
|
24
|
+
heading="Account deleted"
|
|
25
|
+
>
|
|
26
|
+
<Text style={paragraph}>
|
|
27
|
+
Your account (<strong>{userEmail}</strong>) has been permanently deleted
|
|
28
|
+
on {deletedAt}.
|
|
29
|
+
</Text>
|
|
30
|
+
|
|
31
|
+
<Text style={paragraph}>
|
|
32
|
+
All your data has been removed from our systems. This action cannot be
|
|
33
|
+
undone.
|
|
34
|
+
</Text>
|
|
35
|
+
|
|
36
|
+
<Text style={paragraph}>
|
|
37
|
+
If you didn't request this deletion or believe this was done in error,
|
|
38
|
+
please contact us immediately at{' '}
|
|
39
|
+
<Link href={`mailto:${supportEmail}`} style={link}>
|
|
40
|
+
{supportEmail}
|
|
41
|
+
</Link>
|
|
42
|
+
.
|
|
43
|
+
</Text>
|
|
44
|
+
|
|
45
|
+
<Text style={paragraphSmall}>
|
|
46
|
+
Thank you for being part of {appName}. We're sorry to see you go.
|
|
47
|
+
</Text>
|
|
48
|
+
</EmailLayout>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const paragraph: React.CSSProperties = {
|
|
53
|
+
color: '#334155',
|
|
54
|
+
fontSize: '15px',
|
|
55
|
+
lineHeight: '24px',
|
|
56
|
+
margin: '0 0 24px',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const paragraphSmall: React.CSSProperties = {
|
|
60
|
+
color: '#94a3b8',
|
|
61
|
+
fontSize: '13px',
|
|
62
|
+
lineHeight: '20px',
|
|
63
|
+
margin: '24px 0 0',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const link: React.CSSProperties = {
|
|
67
|
+
color: '#2563eb',
|
|
68
|
+
textDecoration: 'underline',
|
|
69
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Container,
|
|
4
|
+
Head,
|
|
5
|
+
Heading,
|
|
6
|
+
Hr,
|
|
7
|
+
Html,
|
|
8
|
+
Img,
|
|
9
|
+
Link,
|
|
10
|
+
Preview,
|
|
11
|
+
Section,
|
|
12
|
+
Text,
|
|
13
|
+
} from '@react-email/components';
|
|
14
|
+
import * as React from 'react';
|
|
15
|
+
|
|
16
|
+
import { emailConfig } from '../config';
|
|
17
|
+
|
|
18
|
+
interface EmailLayoutProps {
|
|
19
|
+
preview: string;
|
|
20
|
+
heading: string;
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
showFooterLinks?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function EmailLayout({
|
|
26
|
+
preview,
|
|
27
|
+
heading,
|
|
28
|
+
children,
|
|
29
|
+
showFooterLinks = true,
|
|
30
|
+
}: EmailLayoutProps) {
|
|
31
|
+
const { appName, logoUrl, primaryColor, supportEmail } = emailConfig;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Html>
|
|
35
|
+
<Head />
|
|
36
|
+
<Preview>{preview}</Preview>
|
|
37
|
+
<Body style={main}>
|
|
38
|
+
<Container style={container}>
|
|
39
|
+
<Section style={logoSection}>
|
|
40
|
+
{logoUrl ? (
|
|
41
|
+
<Img src={logoUrl} width="48" height="48" alt={appName} />
|
|
42
|
+
) : (
|
|
43
|
+
<Text style={{ ...logoText, color: primaryColor }}>
|
|
44
|
+
{appName}
|
|
45
|
+
</Text>
|
|
46
|
+
)}
|
|
47
|
+
</Section>
|
|
48
|
+
|
|
49
|
+
<Section style={contentSection}>
|
|
50
|
+
<Heading style={h1}>{heading}</Heading>
|
|
51
|
+
{children}
|
|
52
|
+
</Section>
|
|
53
|
+
|
|
54
|
+
<Hr style={hr} />
|
|
55
|
+
|
|
56
|
+
<Section style={footer}>
|
|
57
|
+
{showFooterLinks && (
|
|
58
|
+
<Text style={footerLinks}>
|
|
59
|
+
<Link href={`mailto:${supportEmail}`} style={footerLink}>
|
|
60
|
+
Contact Support
|
|
61
|
+
</Link>
|
|
62
|
+
</Text>
|
|
63
|
+
)}
|
|
64
|
+
<Text style={footerText}>
|
|
65
|
+
© {new Date().getFullYear()} {appName}. All rights reserved.
|
|
66
|
+
</Text>
|
|
67
|
+
<Text style={footerMuted}>
|
|
68
|
+
If you didn't expect this email, you can safely ignore it.
|
|
69
|
+
</Text>
|
|
70
|
+
</Section>
|
|
71
|
+
</Container>
|
|
72
|
+
</Body>
|
|
73
|
+
</Html>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const main: React.CSSProperties = {
|
|
78
|
+
backgroundColor: '#f8fafc',
|
|
79
|
+
fontFamily:
|
|
80
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif',
|
|
81
|
+
padding: '40px 0',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const container: React.CSSProperties = {
|
|
85
|
+
backgroundColor: '#ffffff',
|
|
86
|
+
margin: '0 auto',
|
|
87
|
+
maxWidth: '560px',
|
|
88
|
+
borderRadius: '12px',
|
|
89
|
+
border: '1px solid #e2e8f0',
|
|
90
|
+
overflow: 'hidden',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const logoSection: React.CSSProperties = {
|
|
94
|
+
padding: '32px 40px 24px',
|
|
95
|
+
textAlign: 'center',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const logoText: React.CSSProperties = {
|
|
99
|
+
fontSize: '24px',
|
|
100
|
+
fontWeight: '700',
|
|
101
|
+
margin: '0',
|
|
102
|
+
letterSpacing: '-0.5px',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const contentSection: React.CSSProperties = {
|
|
106
|
+
padding: '0 40px 32px',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const h1: React.CSSProperties = {
|
|
110
|
+
color: '#0f172a',
|
|
111
|
+
fontSize: '24px',
|
|
112
|
+
fontWeight: '600',
|
|
113
|
+
lineHeight: '32px',
|
|
114
|
+
margin: '0 0 24px',
|
|
115
|
+
letterSpacing: '-0.25px',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const hr: React.CSSProperties = {
|
|
119
|
+
borderColor: '#e2e8f0',
|
|
120
|
+
borderTop: 'none',
|
|
121
|
+
margin: '0',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const footer: React.CSSProperties = {
|
|
125
|
+
padding: '24px 40px',
|
|
126
|
+
backgroundColor: '#f8fafc',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const footerLinks: React.CSSProperties = {
|
|
130
|
+
textAlign: 'center',
|
|
131
|
+
margin: '0 0 16px',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const footerLink: React.CSSProperties = {
|
|
135
|
+
color: '#64748b',
|
|
136
|
+
fontSize: '13px',
|
|
137
|
+
textDecoration: 'underline',
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const footerText: React.CSSProperties = {
|
|
141
|
+
color: '#64748b',
|
|
142
|
+
fontSize: '13px',
|
|
143
|
+
lineHeight: '20px',
|
|
144
|
+
textAlign: 'center',
|
|
145
|
+
margin: '0 0 8px',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const footerMuted: React.CSSProperties = {
|
|
149
|
+
color: '#94a3b8',
|
|
150
|
+
fontSize: '12px',
|
|
151
|
+
lineHeight: '18px',
|
|
152
|
+
textAlign: 'center',
|
|
153
|
+
margin: '0',
|
|
154
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email template configuration.
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: Update these values before deploying to production.
|
|
5
|
+
* The logo must be hosted at a publicly accessible URL.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface EmailConfig {
|
|
9
|
+
appName: string;
|
|
10
|
+
websiteUrl: string;
|
|
11
|
+
supportEmail: string;
|
|
12
|
+
primaryColor: string;
|
|
13
|
+
logoUrl: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const emailConfig: EmailConfig = {
|
|
17
|
+
appName: 'Your App',
|
|
18
|
+
websiteUrl: 'https://yourapp.com',
|
|
19
|
+
supportEmail: 'support@yourapp.com',
|
|
20
|
+
primaryColor: '#2563eb',
|
|
21
|
+
logoUrl: undefined,
|
|
22
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Button, Text } from '@react-email/components';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { EmailLayout } from './components/email-layout';
|
|
5
|
+
import { emailConfig } from './config';
|
|
6
|
+
|
|
7
|
+
interface PasswordChangedEmailProps {
|
|
8
|
+
userEmail: string;
|
|
9
|
+
changedAt: string;
|
|
10
|
+
resetPasswordUrl: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function PasswordChangedEmail({
|
|
14
|
+
userEmail = 'user@example.com',
|
|
15
|
+
changedAt = new Date().toLocaleString('en-US', {
|
|
16
|
+
dateStyle: 'medium',
|
|
17
|
+
timeStyle: 'short',
|
|
18
|
+
}),
|
|
19
|
+
resetPasswordUrl = 'https://yourapp.com/forgot-password',
|
|
20
|
+
}: PasswordChangedEmailProps) {
|
|
21
|
+
const { appName } = emailConfig;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<EmailLayout
|
|
25
|
+
preview={`Your ${appName} password was changed`}
|
|
26
|
+
heading="Your password was changed"
|
|
27
|
+
>
|
|
28
|
+
<Text style={paragraph}>
|
|
29
|
+
The password for your account (<strong>{userEmail}</strong>) was
|
|
30
|
+
successfully changed on {changedAt}.
|
|
31
|
+
</Text>
|
|
32
|
+
|
|
33
|
+
<Text style={paragraph}>
|
|
34
|
+
If you made this change, no further action is required.
|
|
35
|
+
</Text>
|
|
36
|
+
|
|
37
|
+
<Text style={warningText}>
|
|
38
|
+
If you didn't change your password, your account may be compromised.
|
|
39
|
+
Reset your password immediately:
|
|
40
|
+
</Text>
|
|
41
|
+
|
|
42
|
+
<Button style={dangerButton} href={resetPasswordUrl}>
|
|
43
|
+
Reset Password
|
|
44
|
+
</Button>
|
|
45
|
+
|
|
46
|
+
<Text style={paragraphSmall}>
|
|
47
|
+
For security, this email was sent to notify you of account changes.
|
|
48
|
+
</Text>
|
|
49
|
+
</EmailLayout>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const paragraph: React.CSSProperties = {
|
|
54
|
+
color: '#334155',
|
|
55
|
+
fontSize: '15px',
|
|
56
|
+
lineHeight: '24px',
|
|
57
|
+
margin: '0 0 24px',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const warningText: React.CSSProperties = {
|
|
61
|
+
color: '#b45309',
|
|
62
|
+
fontSize: '15px',
|
|
63
|
+
lineHeight: '24px',
|
|
64
|
+
margin: '0 0 24px',
|
|
65
|
+
padding: '16px',
|
|
66
|
+
backgroundColor: '#fef3c7',
|
|
67
|
+
borderRadius: '8px',
|
|
68
|
+
border: '1px solid #fcd34d',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const paragraphSmall: React.CSSProperties = {
|
|
72
|
+
color: '#94a3b8',
|
|
73
|
+
fontSize: '13px',
|
|
74
|
+
lineHeight: '20px',
|
|
75
|
+
margin: '24px 0 0',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const dangerButton: React.CSSProperties = {
|
|
79
|
+
borderRadius: '8px',
|
|
80
|
+
backgroundColor: '#dc2626',
|
|
81
|
+
color: '#ffffff',
|
|
82
|
+
fontSize: '15px',
|
|
83
|
+
fontWeight: '600',
|
|
84
|
+
textDecoration: 'none',
|
|
85
|
+
textAlign: 'center',
|
|
86
|
+
display: 'inline-block',
|
|
87
|
+
padding: '12px 32px',
|
|
88
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Button, Link, Text } from '@react-email/components';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { EmailLayout } from './components/email-layout';
|
|
5
|
+
import { emailConfig } from './config';
|
|
6
|
+
|
|
7
|
+
interface PasswordResetEmailProps {
|
|
8
|
+
resetUrl: string;
|
|
9
|
+
userEmail: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function PasswordResetEmail({
|
|
13
|
+
resetUrl = 'https://example.com/reset-password?token=abc123',
|
|
14
|
+
userEmail = 'user@example.com',
|
|
15
|
+
}: PasswordResetEmailProps) {
|
|
16
|
+
const { primaryColor } = emailConfig;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<EmailLayout preview="Reset your password" heading="Reset your password">
|
|
20
|
+
<Text style={paragraph}>
|
|
21
|
+
We received a request to reset the password for{' '}
|
|
22
|
+
<strong>{userEmail}</strong>.
|
|
23
|
+
</Text>
|
|
24
|
+
|
|
25
|
+
<Text style={paragraph}>
|
|
26
|
+
Click the button below to choose a new password:
|
|
27
|
+
</Text>
|
|
28
|
+
|
|
29
|
+
<Button
|
|
30
|
+
style={{ ...button, backgroundColor: primaryColor }}
|
|
31
|
+
href={resetUrl}
|
|
32
|
+
>
|
|
33
|
+
Reset Password
|
|
34
|
+
</Button>
|
|
35
|
+
|
|
36
|
+
<Text style={paragraphMuted}>Or copy this link into your browser:</Text>
|
|
37
|
+
<Link href={resetUrl} style={link}>
|
|
38
|
+
{resetUrl}
|
|
39
|
+
</Link>
|
|
40
|
+
|
|
41
|
+
<Text style={paragraphSmall}>
|
|
42
|
+
This link expires in 1 hour. If you didn't request this, you can safely
|
|
43
|
+
ignore this email — your password won't be changed.
|
|
44
|
+
</Text>
|
|
45
|
+
</EmailLayout>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const paragraph: React.CSSProperties = {
|
|
50
|
+
color: '#334155',
|
|
51
|
+
fontSize: '15px',
|
|
52
|
+
lineHeight: '24px',
|
|
53
|
+
margin: '0 0 24px',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const paragraphMuted: React.CSSProperties = {
|
|
57
|
+
color: '#64748b',
|
|
58
|
+
fontSize: '13px',
|
|
59
|
+
lineHeight: '20px',
|
|
60
|
+
margin: '24px 0 8px',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const paragraphSmall: React.CSSProperties = {
|
|
64
|
+
color: '#94a3b8',
|
|
65
|
+
fontSize: '13px',
|
|
66
|
+
lineHeight: '20px',
|
|
67
|
+
margin: '24px 0 0',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const button: React.CSSProperties = {
|
|
71
|
+
borderRadius: '8px',
|
|
72
|
+
color: '#ffffff',
|
|
73
|
+
fontSize: '15px',
|
|
74
|
+
fontWeight: '600',
|
|
75
|
+
textDecoration: 'none',
|
|
76
|
+
textAlign: 'center',
|
|
77
|
+
display: 'inline-block',
|
|
78
|
+
padding: '12px 32px',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const link: React.CSSProperties = {
|
|
82
|
+
color: '#2563eb',
|
|
83
|
+
fontSize: '13px',
|
|
84
|
+
textDecoration: 'underline',
|
|
85
|
+
wordBreak: 'break-all',
|
|
86
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Button, Link, Text } from '@react-email/components';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { EmailLayout } from './components/email-layout';
|
|
5
|
+
import { emailConfig } from './config';
|
|
6
|
+
|
|
7
|
+
interface VerificationEmailProps {
|
|
8
|
+
verificationUrl: string;
|
|
9
|
+
userEmail: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function VerificationEmail({
|
|
13
|
+
verificationUrl = 'https://example.com/verify?token=abc123',
|
|
14
|
+
userEmail = 'user@example.com',
|
|
15
|
+
}: VerificationEmailProps) {
|
|
16
|
+
const { appName, primaryColor } = emailConfig;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<EmailLayout
|
|
20
|
+
preview={`Verify your email to get started with ${appName}`}
|
|
21
|
+
heading="Verify your email"
|
|
22
|
+
>
|
|
23
|
+
<Text style={paragraph}>
|
|
24
|
+
Thanks for signing up! Please verify your email address (
|
|
25
|
+
<strong>{userEmail}</strong>) to complete your registration.
|
|
26
|
+
</Text>
|
|
27
|
+
|
|
28
|
+
<Button
|
|
29
|
+
style={{ ...button, backgroundColor: primaryColor }}
|
|
30
|
+
href={verificationUrl}
|
|
31
|
+
>
|
|
32
|
+
Verify Email Address
|
|
33
|
+
</Button>
|
|
34
|
+
|
|
35
|
+
<Text style={paragraphMuted}>Or copy this link into your browser:</Text>
|
|
36
|
+
<Link href={verificationUrl} style={link}>
|
|
37
|
+
{verificationUrl}
|
|
38
|
+
</Link>
|
|
39
|
+
|
|
40
|
+
<Text style={paragraphSmall}>
|
|
41
|
+
This link expires in 24 hours. If you didn't create an account, ignore
|
|
42
|
+
this email.
|
|
43
|
+
</Text>
|
|
44
|
+
</EmailLayout>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const paragraph: React.CSSProperties = {
|
|
49
|
+
color: '#334155',
|
|
50
|
+
fontSize: '15px',
|
|
51
|
+
lineHeight: '24px',
|
|
52
|
+
margin: '0 0 24px',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const paragraphMuted: React.CSSProperties = {
|
|
56
|
+
color: '#64748b',
|
|
57
|
+
fontSize: '13px',
|
|
58
|
+
lineHeight: '20px',
|
|
59
|
+
margin: '24px 0 8px',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const paragraphSmall: React.CSSProperties = {
|
|
63
|
+
color: '#94a3b8',
|
|
64
|
+
fontSize: '13px',
|
|
65
|
+
lineHeight: '20px',
|
|
66
|
+
margin: '24px 0 0',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const button: React.CSSProperties = {
|
|
70
|
+
borderRadius: '8px',
|
|
71
|
+
color: '#ffffff',
|
|
72
|
+
fontSize: '15px',
|
|
73
|
+
fontWeight: '600',
|
|
74
|
+
textDecoration: 'none',
|
|
75
|
+
textAlign: 'center',
|
|
76
|
+
display: 'inline-block',
|
|
77
|
+
padding: '12px 32px',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const link: React.CSSProperties = {
|
|
81
|
+
color: '#2563eb',
|
|
82
|
+
fontSize: '13px',
|
|
83
|
+
textDecoration: 'underline',
|
|
84
|
+
wordBreak: 'break-all',
|
|
85
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Button, Text } from '@react-email/components';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { EmailLayout } from './components/email-layout';
|
|
5
|
+
import { emailConfig } from './config';
|
|
6
|
+
|
|
7
|
+
interface WelcomeEmailProps {
|
|
8
|
+
userName: string;
|
|
9
|
+
dashboardUrl: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function WelcomeEmail({
|
|
13
|
+
userName = 'there',
|
|
14
|
+
dashboardUrl = 'https://yourapp.com/dashboard',
|
|
15
|
+
}: WelcomeEmailProps) {
|
|
16
|
+
const { appName, primaryColor } = emailConfig;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<EmailLayout
|
|
20
|
+
preview={`Welcome to ${appName}! Your account is ready.`}
|
|
21
|
+
heading={`Welcome to ${appName}!`}
|
|
22
|
+
>
|
|
23
|
+
<Text style={paragraph}>Hi {userName},</Text>
|
|
24
|
+
|
|
25
|
+
<Text style={paragraph}>
|
|
26
|
+
Your email has been verified and your account is all set up. We're
|
|
27
|
+
excited to have you on board.
|
|
28
|
+
</Text>
|
|
29
|
+
|
|
30
|
+
<Text style={paragraph}>Get started by exploring your dashboard:</Text>
|
|
31
|
+
|
|
32
|
+
<Button
|
|
33
|
+
style={{ ...button, backgroundColor: primaryColor }}
|
|
34
|
+
href={dashboardUrl}
|
|
35
|
+
>
|
|
36
|
+
Go to Dashboard
|
|
37
|
+
</Button>
|
|
38
|
+
|
|
39
|
+
<Text style={paragraphSmall}>
|
|
40
|
+
If you have any questions, just reply to this email — we're here to
|
|
41
|
+
help.
|
|
42
|
+
</Text>
|
|
43
|
+
</EmailLayout>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const paragraph: React.CSSProperties = {
|
|
48
|
+
color: '#334155',
|
|
49
|
+
fontSize: '15px',
|
|
50
|
+
lineHeight: '24px',
|
|
51
|
+
margin: '0 0 24px',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const paragraphSmall: React.CSSProperties = {
|
|
55
|
+
color: '#94a3b8',
|
|
56
|
+
fontSize: '13px',
|
|
57
|
+
lineHeight: '20px',
|
|
58
|
+
margin: '24px 0 0',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const button: React.CSSProperties = {
|
|
62
|
+
borderRadius: '8px',
|
|
63
|
+
color: '#ffffff',
|
|
64
|
+
fontSize: '15px',
|
|
65
|
+
fontWeight: '600',
|
|
66
|
+
textDecoration: 'none',
|
|
67
|
+
textAlign: 'center',
|
|
68
|
+
display: 'inline-block',
|
|
69
|
+
padding: '12px 32px',
|
|
70
|
+
};
|