Merge remote-tracking branch 'origin/main' into cloudflare
This commit is contained in:
commit
a44e4a669c
41
.gitattributes
vendored
Normal file
41
.gitattributes
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# Set default behavior to automatically normalize line endings
|
||||
* text=auto
|
||||
|
||||
# Force LF line endings for text files
|
||||
*.js text eol=lf
|
||||
*.jsx text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.tsx text eol=lf
|
||||
*.json text eol=lf
|
||||
*.md text eol=lf
|
||||
*.mdx text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.css text eol=lf
|
||||
*.scss text eol=lf
|
||||
*.html text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.txt text eol=lf
|
||||
*.sh text eol=lf
|
||||
|
||||
# Ensure these files are always treated as text and get LF line endings
|
||||
.gitignore text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
.editorconfig text eol=lf
|
||||
*.config.js text eol=lf
|
||||
*.config.ts text eol=lf
|
||||
|
||||
# Binary files should be left untouched
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.svg binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.pdf binary
|
||||
*.zip binary
|
||||
*.tar.gz binary
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -4,6 +4,7 @@
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"Lokalise.i18n-ally",
|
||||
"unifiedjs.vscode-mdx",
|
||||
"eamodio.gitlens"
|
||||
"eamodio.gitlens",
|
||||
"editorconfig.editorconfig"
|
||||
]
|
||||
}
|
||||
|
109
CLAUDE.md
Normal file
109
CLAUDE.md
Normal file
@ -0,0 +1,109 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Core Development
|
||||
- `pnpm dev` - Start development server with content collections
|
||||
- `pnpm build` - Build the application and content collections
|
||||
- `pnpm start` - Start production server
|
||||
- `pnpm lint` - Run Biome linter (use for code quality checks)
|
||||
- `pnpm format` - Format code with Biome
|
||||
|
||||
### Database Operations (Drizzle ORM)
|
||||
- `pnpm db:generate` - Generate new migration files based on schema changes
|
||||
- `pnpm db:migrate` - Apply pending migrations to the database
|
||||
- `pnpm db:push` - Sync schema changes directly to the database (development only)
|
||||
- `pnpm db:studio` - Open Drizzle Studio for database inspection and management
|
||||
|
||||
### Content and Email
|
||||
- `pnpm content` - Process MDX content collections
|
||||
- `pnpm email` - Start email template development server on port 3333
|
||||
|
||||
## Project Architecture
|
||||
|
||||
This is a Next.js 15 full-stack SaaS application with the following key architectural components:
|
||||
|
||||
### Core Stack
|
||||
- **Framework**: Next.js 15 with App Router
|
||||
- **Database**: PostgreSQL with Drizzle ORM
|
||||
- **Authentication**: Better Auth with social providers (Google, GitHub)
|
||||
- **Payments**: Stripe integration with subscription and one-time payments
|
||||
- **UI**: Radix UI components with TailwindCSS
|
||||
- **State Management**: Zustand for client-side state
|
||||
- **Internationalization**: next-intl with English and Chinese locales
|
||||
- **Content**: Fumadocs for documentation and MDX for content
|
||||
- **Code Quality**: Biome for formatting and linting
|
||||
|
||||
### Key Directory Structure
|
||||
- `src/app/` - Next.js app router with internationalized routing
|
||||
- `src/components/` - Reusable React components organized by feature
|
||||
- `src/lib/` - Utility functions and shared code
|
||||
- `src/db/` - Database schema and migrations
|
||||
- `src/actions/` - Server actions for API operations
|
||||
- `src/stores/` - Zustand state management
|
||||
- `src/hooks/` - Custom React hooks
|
||||
- `src/config/` - Application configuration files
|
||||
- `src/i18n/` - Internationalization setup
|
||||
- `src/mail/` - Email templates and mail functionality
|
||||
- `src/payment/` - Stripe payment integration
|
||||
- `src/credits/` - Credit system implementation
|
||||
- `content/` - MDX content files for docs and blog
|
||||
- `messages/` - Translation files (en.json, zh.json) for internationalization
|
||||
|
||||
### Authentication & User Management
|
||||
- Uses Better Auth with PostgreSQL adapter
|
||||
- Supports email/password and social login (Google, GitHub)
|
||||
- Includes user management, email verification, and password reset
|
||||
- Admin plugin for user management and banning
|
||||
- Automatic newsletter subscription on user creation
|
||||
|
||||
### Payment System
|
||||
- Stripe integration for subscriptions and one-time payments
|
||||
- Three pricing tiers: Free, Pro (monthly/yearly), and Lifetime
|
||||
- Credit system with packages for pay-per-use features
|
||||
- Customer portal for subscription management
|
||||
|
||||
### Feature Modules
|
||||
- **Blog**: MDX-based blog with pagination and categories
|
||||
- **Docs**: Fumadocs-powered documentation
|
||||
- **AI Features**: Image generation with multiple providers (OpenAI, Replicate, etc.)
|
||||
- **Newsletter**: Email subscription system
|
||||
- **Analytics**: Multiple analytics providers support
|
||||
- **Storage**: S3 integration for file uploads
|
||||
|
||||
### Development Workflow
|
||||
1. Use TypeScript for all new code
|
||||
2. Follow Biome formatting rules (single quotes, trailing commas)
|
||||
3. Write server actions in `src/actions/`
|
||||
4. Use Zustand for client-side state management
|
||||
5. Implement database changes through Drizzle migrations
|
||||
6. Use Radix UI components for consistent UI
|
||||
7. Follow the established directory structure
|
||||
8. Use proper error handling with error.tsx and not-found.tsx
|
||||
9. Leverage Next.js 15 features like Server Actions
|
||||
10. Use `next-safe-action` for secure form submissions
|
||||
|
||||
### Configuration
|
||||
- Main config in `src/config/website.tsx`
|
||||
- Environment variables template in `env.example`
|
||||
- Database config in `drizzle.config.ts`
|
||||
- Biome config in `biome.json` with specific ignore patterns
|
||||
- TypeScript config with path aliases (@/* for src/*)
|
||||
|
||||
### Testing and Quality
|
||||
- Use Biome for linting and formatting
|
||||
- TypeScript for type safety
|
||||
- Environment variables for configuration
|
||||
- Proper error boundaries and not-found pages
|
||||
- Zod for runtime validation
|
||||
|
||||
## Important Notes
|
||||
|
||||
- The project uses pnpm as the package manager
|
||||
- Database schema is in `src/db/schema.ts` with auth, payment, and credit tables
|
||||
- Email templates are in `src/mail/templates/`
|
||||
- The app supports both light and dark themes
|
||||
- Content is managed through MDX files in the `content/` directory
|
||||
- The project includes comprehensive internationalization support
|
@ -2,4 +2,4 @@
|
||||
title: 组件
|
||||
description: 改进文档的额外组件
|
||||
index: true
|
||||
---
|
||||
---
|
||||
|
@ -1,11 +1,21 @@
|
||||
import { DashboardHeader } from '@/components/dashboard/dashboard-header';
|
||||
import { getSession } from '@/lib/server';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
interface UsersLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default async function UsersLayout({ children }: UsersLayoutProps) {
|
||||
// if is demo website, allow user to access admin and user pages, but data is fake
|
||||
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
|
||||
// Check if user is admin
|
||||
const session = await getSession();
|
||||
if (!session || (session.user.role !== 'admin' && !isDemo)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const t = await getTranslations('Dashboard.admin');
|
||||
|
||||
const breadcrumbs = [
|
||||
|
@ -23,18 +23,22 @@ export default async function BillingLayout({ children }: BillingLayoutProps) {
|
||||
<>
|
||||
<DashboardHeader breadcrumbs={breadcrumbs} />
|
||||
|
||||
<div className="px-4 lg:px-6 py-16">
|
||||
<div className="max-w-6xl mx-auto space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('billing.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('billing.description')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||
<div className="px-4 lg:px-6 space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('billing.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('billing.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -25,18 +25,22 @@ export default async function NotificationsLayout({
|
||||
<>
|
||||
<DashboardHeader breadcrumbs={breadcrumbs} />
|
||||
|
||||
<div className="px-4 lg:px-6 py-16">
|
||||
<div className="max-w-6xl mx-auto space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('notification.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('notification.description')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||
<div className="px-4 lg:px-6 space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('notification.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('notification.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -2,7 +2,7 @@ import { NewsletterFormCard } from '@/components/settings/notification/newslette
|
||||
|
||||
export default function NotificationPage() {
|
||||
return (
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
<div className="grid gap-8 @lg/main:grid-cols-2">
|
||||
<NewsletterFormCard />
|
||||
</div>
|
||||
);
|
||||
|
@ -23,18 +23,22 @@ export default async function ProfileLayout({ children }: ProfileLayoutProps) {
|
||||
<>
|
||||
<DashboardHeader breadcrumbs={breadcrumbs} />
|
||||
|
||||
<div className="px-4 lg:px-6 py-16">
|
||||
<div className="max-w-6xl mx-auto space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('profile.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('profile.description')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||
<div className="px-4 lg:px-6 space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('profile.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('profile.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -3,7 +3,7 @@ import { UpdateNameCard } from '@/components/settings/profile/update-name-card';
|
||||
|
||||
export default function ProfilePage() {
|
||||
return (
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
<div className="flex flex-col gap-8">
|
||||
<UpdateAvatarCard />
|
||||
<UpdateNameCard />
|
||||
</div>
|
||||
|
@ -25,18 +25,22 @@ export default async function SecurityLayout({
|
||||
<>
|
||||
<DashboardHeader breadcrumbs={breadcrumbs} />
|
||||
|
||||
<div className="px-4 lg:px-6 py-16">
|
||||
<div className="max-w-6xl mx-auto space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('security.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('security.description')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||
<div className="px-4 lg:px-6 space-y-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{t('security.title')}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t('security.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -3,7 +3,7 @@ import { PasswordCardWrapper } from '@/components/settings/security/password-car
|
||||
|
||||
export default function SecurityPage() {
|
||||
return (
|
||||
<div className="grid gap-8 grid-cols-1">
|
||||
<div className="flex flex-col gap-8">
|
||||
<PasswordCardWrapper />
|
||||
<DeleteAccountCard />
|
||||
</div>
|
||||
|
@ -29,6 +29,9 @@ export function DashboardHeader({
|
||||
breadcrumbs,
|
||||
actions,
|
||||
}: DashboardHeaderProps) {
|
||||
// if is demo website, allow user to access admin and user pages, but data is fake
|
||||
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
|
||||
|
||||
return (
|
||||
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
@ -69,7 +72,7 @@ export function DashboardHeader({
|
||||
<div className="ml-auto flex items-center gap-3 px-4">
|
||||
{actions}
|
||||
|
||||
<ThemeSelector />
|
||||
{isDemo && <ThemeSelector />}
|
||||
<ModeSwitcher />
|
||||
<LocaleSwitcher />
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user