Merge remote-tracking branch 'origin/main' into cloudflare

This commit is contained in:
javayhu 2025-07-09 19:14:30 +08:00
commit da4b018e8d
11 changed files with 187 additions and 49 deletions

109
CLAUDE.md Normal file
View 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

View File

@ -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 = [

View File

@ -23,8 +23,10 @@ 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 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')}
@ -37,6 +39,8 @@ export default async function BillingLayout({ children }: BillingLayoutProps) {
{children}
</div>
</div>
</div>
</div>
</>
);
}

View File

@ -25,8 +25,10 @@ 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 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')}
@ -39,6 +41,8 @@ export default async function NotificationsLayout({
{children}
</div>
</div>
</div>
</div>
</>
);
}

View File

@ -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>
);

View File

@ -23,8 +23,10 @@ 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 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')}
@ -37,6 +39,8 @@ export default async function ProfileLayout({ children }: ProfileLayoutProps) {
{children}
</div>
</div>
</div>
</div>
</>
);
}

View File

@ -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>

View File

@ -25,8 +25,10 @@ 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 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')}
@ -39,6 +41,8 @@ export default async function SecurityLayout({
{children}
</div>
</div>
</div>
</div>
</>
);
}

View File

@ -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>

View File

@ -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>