├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── README.zh-CN.md ├── components.json ├── docs ├── api-reference.md ├── architecture.md ├── component-guide.md ├── deployment-guide.md ├── i18n-guide.md └── quick-start.md ├── env-template.txt ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── preview.png ├── public ├── android-chrome-192x192.png ├── apple-icon.png ├── favicon-16x16.png ├── favicon-32x32-original.png ├── favicon-32x32.png ├── favicon.ico ├── images │ ├── 07b1a04b-cc56-481f-a8cd-8e1618989898_0.png │ ├── 5a8ccbe9-3bee-4eff-aff1-ff0e7555881b_0.png │ ├── 5b45db9a-e2d7-49bd-a0aa-d1d96658f0ad_0.png │ ├── generator-background.jpg │ └── my-background.jpg ├── imgs │ ├── cnpay.png │ ├── icons │ │ ├── 1.svg │ │ ├── 2.svg │ │ ├── 3.svg │ │ ├── 4.svg │ │ ├── 5.svg │ │ └── 6.svg │ ├── logos │ │ ├── nextjs.svg │ │ ├── react.svg │ │ ├── shadcn.svg │ │ ├── supabase.svg │ │ ├── tailwindcss.svg │ │ └── vercel.svg │ ├── masks │ │ ├── circle.svg │ │ └── line.svg │ └── placeholder.png ├── logo.png ├── robots.txt.example ├── shortcut-icon.png ├── site.webmanifest └── sitemap.xml.example ├── src ├── app │ ├── (legal) │ │ ├── error.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── privacy-policy │ │ │ └── page.mdx │ │ └── terms-of-service │ │ │ └── page.mdx │ ├── [locale] │ │ ├── (admin) │ │ │ ├── admin │ │ │ │ ├── error.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── paid-orders │ │ │ │ │ └── page.tsx │ │ │ │ ├── posts │ │ │ │ │ ├── [uuid] │ │ │ │ │ │ └── edit │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── add │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ │ └── users │ │ │ │ │ └── page.tsx │ │ │ ├── error.tsx │ │ │ ├── layout.tsx │ │ │ └── loading.tsx │ │ ├── (default) │ │ │ ├── (console) │ │ │ │ ├── api-keys │ │ │ │ │ ├── create │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── error.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── loading.tsx │ │ │ │ ├── my-credits │ │ │ │ │ └── page.tsx │ │ │ │ └── my-orders │ │ │ │ │ └── page.tsx │ │ │ ├── error.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ ├── page.tsx │ │ │ └── posts │ │ │ │ ├── [slug] │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ ├── auth │ │ │ ├── error.tsx │ │ │ ├── loading.tsx │ │ │ └── signin │ │ │ │ ├── error.tsx │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ ├── blog │ │ │ ├── error.tsx │ │ │ ├── layout.tsx │ │ │ └── loading.tsx │ │ ├── error.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── not-found.tsx │ │ └── pay-success │ │ │ ├── [session_id] │ │ │ └── page.tsx │ │ │ ├── error.tsx │ │ │ └── loading.tsx │ ├── api │ │ ├── auth │ │ │ └── [...nextauth] │ │ │ │ └── route.ts │ │ ├── checkout │ │ │ └── route.ts │ │ ├── error.ts │ │ ├── image │ │ │ └── route.ts │ │ ├── ping │ │ │ └── route.ts │ │ └── stripe-notify │ │ │ └── route.ts │ ├── error.tsx │ ├── global-error.tsx │ ├── globals.css │ ├── layout.tsx │ ├── loading.tsx │ ├── not-found.global.tsx │ ├── page.tsx │ ├── sitemap.ts │ ├── template.tsx │ └── theme.css ├── auth │ ├── config.ts │ ├── index.ts │ └── session.tsx ├── components │ ├── blocks │ │ ├── blog-detail │ │ │ ├── crumb.tsx │ │ │ └── index.tsx │ │ ├── blog │ │ │ └── index.tsx │ │ ├── crumb │ │ │ └── index.tsx │ │ ├── empty │ │ │ └── index.tsx │ │ ├── footer │ │ │ └── index.tsx │ │ ├── form │ │ │ └── index.tsx │ │ ├── header │ │ │ └── index.tsx │ │ ├── hero │ │ │ ├── announcement-bar.tsx │ │ │ ├── bg.tsx │ │ │ ├── floating-image.tsx │ │ │ ├── happy-users.tsx │ │ │ ├── index.tsx │ │ │ └── particles-background.tsx │ │ ├── pricing │ │ │ └── index.tsx │ │ ├── table │ │ │ ├── copy.tsx │ │ │ ├── dropdown.tsx │ │ │ └── index.tsx │ │ └── toolbar │ │ │ └── index.tsx │ ├── console │ │ ├── layout.tsx │ │ ├── sidebar │ │ │ └── nav.tsx │ │ └── slots │ │ │ ├── form │ │ │ └── index.tsx │ │ │ └── table │ │ │ └── index.tsx │ ├── dashboard │ │ ├── header │ │ │ └── index.tsx │ │ ├── layout.tsx │ │ ├── sidebar │ │ │ ├── footer.tsx │ │ │ ├── header.tsx │ │ │ ├── index.tsx │ │ │ ├── nav.tsx │ │ │ └── user.tsx │ │ └── slots │ │ │ ├── form │ │ │ └── index.tsx │ │ │ └── table │ │ │ └── index.tsx │ ├── gallery │ │ └── gallery.tsx │ ├── icon │ │ └── index.tsx │ ├── image │ │ ├── Gallery.tsx │ │ ├── ImageGenerator.tsx │ │ └── Showcase.tsx │ ├── layout │ │ └── app-shell.tsx │ ├── locale │ │ └── toggle.tsx │ ├── markdown │ │ ├── index.tsx │ │ └── markdown.css │ ├── showcase │ │ └── index.tsx │ ├── sign │ │ ├── form.tsx │ │ ├── modal.tsx │ │ ├── sign_in.tsx │ │ ├── toggle.tsx │ │ └── user.tsx │ ├── theme-toggle.tsx │ └── ui │ │ ├── accordion.tsx │ │ ├── alert.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── collapsible.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── icon.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── motion.tsx │ │ ├── navigation-menu.tsx │ │ ├── radio-group.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── shared-background.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ └── tooltip.tsx ├── contexts │ ├── app.tsx │ └── theme.tsx ├── data │ └── install.sql ├── hooks │ ├── use-mobile.tsx │ ├── useMediaQuery.tsx │ └── useOneTapLogin.tsx ├── i18n.ts ├── i18n │ ├── locale.ts │ ├── messages │ │ ├── en.json │ │ └── zh.json │ ├── pages │ │ └── landing │ │ │ ├── en.json │ │ │ └── zh.json │ ├── request.ts │ ├── routing.ts │ └── utils.ts ├── lib │ ├── browser.ts │ ├── cache.ts │ ├── hash.ts │ ├── ip.ts │ ├── resp.ts │ ├── storage.ts │ ├── time.ts │ ├── tools.ts │ └── utils.ts ├── mdx-components.tsx ├── middleware.ts ├── models │ ├── apikey.ts │ ├── credit.ts │ ├── db.ts │ ├── order.ts │ ├── post.ts │ └── user.ts ├── providers │ ├── image │ │ ├── index.ts │ │ └── v1 │ │ │ ├── implementations │ │ │ └── flux.ts │ │ │ ├── index.ts │ │ │ ├── interface.ts │ │ │ └── types.ts │ └── index.ts ├── services │ ├── apikey.ts │ ├── constant.ts │ ├── credit.ts │ ├── order.ts │ ├── page.ts │ └── user.ts └── types │ ├── apikey.d.ts │ ├── blocks │ ├── base.d.ts │ ├── blog.d.ts │ ├── footer.d.ts │ ├── form.d.ts │ ├── header.d.ts │ ├── hero.d.ts │ ├── pricing.d.ts │ ├── section.d.ts │ ├── sidebar.d.ts │ └── table.d.ts │ ├── context.d.ts │ ├── credit.d.ts │ ├── global.d.ts │ ├── mdx.d.ts │ ├── next-auth.d.ts │ ├── order.d.ts │ ├── pages │ └── landing.d.ts │ ├── post.d.ts │ ├── slots │ ├── base.d.ts │ ├── form.d.ts │ └── table.d.ts │ └── user.d.ts ├── tailwind.config.ts ├── tsconfig.json └── vercel.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug报告 3 | about: 创建Bug报告以帮助我们改进 4 | title: '[BUG] ' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Bug描述 10 | 请清晰简洁地描述这个Bug 11 | 12 | ## 复现步骤 13 | 复现此行为的步骤: 14 | 1. 前往 '...' 15 | 2. 点击 '....' 16 | 3. 滚动至 '....' 17 | 4. 看到错误 18 | 19 | ## 预期行为 20 | 清晰简洁地描述您期望发生的情况 21 | 22 | ## 截图 23 | 如果适用,添加截图以帮助说明您的问题 24 | 25 | ## 环境信息 26 | - 操作系统: [例如 Windows, macOS, Linux] 27 | - 浏览器 [例如 Chrome, Safari, Firefox] 28 | - 版本 [例如 22] 29 | - Node.js版本: [例如 18.12.0] 30 | - npm版本: [例如 8.19.2] 31 | 32 | ## 额外上下文 33 | 在此处添加有关问题的任何其他上下文 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能请求 3 | about: 为这个项目提出一个想法 4 | title: '[功能] ' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## 当前问题 10 | 您的功能请求是否与问题相关?请清晰简洁地描述问题所在。例如,当 [...] 时我总是感到沮丧 11 | 12 | ## 解决方案 13 | 清晰简洁地描述您希望发生的事情 14 | 15 | ## 替代方案 16 | 清晰简洁地描述您考虑过的任何替代解决方案或功能 17 | 18 | ## 实现思路 19 | 如果有关于如何实现的具体想法,请在此处描述 20 | 21 | ## 额外上下文 22 | 在此处添加有关功能请求的任何其他上下文或截图 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # 描述 2 | 3 | 请描述这个PR的目的和解决的问题。如果它与某个issue相关,请引用该issue。 4 | 5 | ## 变更类型 6 | 7 | 请在相关选项前标记 [x]: 8 | 9 | - [ ] 功能新增 10 | - [ ] Bug修复 11 | - [ ] 代码风格更新 (格式化, 变量命名) 12 | - [ ] 重构 (无功能变化) 13 | - [ ] 构建相关变更 14 | - [ ] 文档更新 15 | - [ ] 其他(请描述): 16 | 17 | ## 测试情况 18 | 19 | 请描述您进行了哪些测试。 20 | 提供清晰的测试说明,以便审核者可以验证您的变更。 21 | 22 | ## 截图 (如适用) 23 | 24 | ## 检查清单: 25 | 26 | - [ ] 我的代码遵循本项目的代码风格 27 | - [ ] 我已经自我审查了自己的代码 28 | - [ ] 我已经为我的代码写了文档 29 | - [ ] 我已经为我的代码添加了测试 30 | - [ ] 新的和现有的单元测试在本地都通过了 31 | - [ ] 任何依赖项更改都已记录在CHANGELOG.md中 32 | - [ ] 我已更新了必要的文档 (如适用) 33 | 34 | ## 附加信息 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env.local 31 | .env.development 32 | .env.production 33 | 34 | # vercel 35 | .vercel 36 | .tmp 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | 42 | Makefile 43 | .wrangler 44 | wrangler.toml 45 | supabase -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "[javascript]": { 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | }, 6 | "i18n-ally.localesPaths": ["i18n/messages"], 7 | "i18n-ally.keystyle": "nested" 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 scottcwy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚡ Flux Generator 2 | 3 |
4 | 5 | [简体中文](./README.zh-CN.md) | English 6 | 7 |
8 | 9 | ✨ A clean and modern AI image generator built with Next.js, Tailwind CSS, and shadcn/ui. Generate stunning images with simple prompts. 10 | 11 | ## 🌟 Preview 12 | 13 |
14 |
15 | Flux Generator Interface 16 |
17 | 🎨 Clean and Modern Interface 18 |
19 |
20 | 21 | ## 🚀 Quick Start 22 | 23 | ```bash 24 | # Clone the repository 25 | git clone https://github.com/scottcwy/AI-Wallpaper.git 26 | 27 | # Install dependencies 28 | pnpm install 29 | 30 | # Start development server 31 | pnpm dev 32 | ``` 33 | 34 | ## ✨ Features 35 | 36 | - **🎨 AI Image Generation**: Create stunning images with simple text prompts 37 | - **⚡ Modern UI**: Built with Tailwind CSS and shadcn/ui components 38 | - **🌐 Internationalization**: Full i18n support with seamless language switching 39 | - **🚀 One-Click Deploy**: Deploy to Vercel with zero configuration 40 | - **📱 Responsive Design**: Perfect on desktop, tablet, and mobile devices 41 | - **🔐 Authentication**: Secure user system with multiple providers 42 | 43 | ## ⚙️ Configuration 44 | 45 | ```bash 46 | # Copy environment template 47 | cp .env.example .env.local 48 | 49 | # Required: Authentication secret for session JWT 50 | # Generate a strong secret, for example: 51 | # openssl rand -base64 32 52 | AUTH_SECRET="your_auth_secret_key" 53 | 54 | # Optional: OAuth providers 55 | # Enable Google or GitHub by providing client credentials and toggles 56 | AUTH_GOOGLE_ID="your_google_client_id" 57 | AUTH_GOOGLE_SECRET="your_google_client_secret" 58 | NEXT_PUBLIC_AUTH_GOOGLE_ENABLED="false" 59 | 60 | AUTH_GITHUB_ID="your_github_client_id" 61 | AUTH_GITHUB_SECRET="your_github_client_secret" 62 | NEXT_PUBLIC_AUTH_GITHUB_ENABLED="false" 63 | 64 | # Tip: In local development, if you forget to set AUTH_SECRET, 65 | # the app falls back to a dev-only secret to avoid 500 errors. 66 | # In production, always set AUTH_SECRET. 67 | ``` 68 | 69 | ## 🎯 Usage 70 | 71 | 1. Enter your image prompt 72 | 2. Click generate and wait for AI magic 73 | 3. Download or share your creation 74 | 75 | That's it! Simple and clean. 76 | 77 | ## 🚀 Deploy 78 | 79 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fscottcwy%2FAI-Wallpaper) 80 | 81 | One-click deployment to Vercel. No configuration needed. 82 | 83 | ## 🛠️ Tech Stack 84 | 85 | - **Next.js 14** - React framework with App Router 86 | - **Tailwind CSS** - Utility-first CSS framework 87 | - **shadcn/ui** - Beautiful and accessible components 88 | - **i18n** - Internationalization support 89 | - **Vercel** - Deployment platform 90 | 91 | ## 📄 License 92 | 93 | MIT License - see [LICENSE](LICENSE) for details. 94 | 95 | --- 96 | 97 | **Simple. Clean. Powerful. 🎨** 98 | 99 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 🎨 AI 壁纸生成器 2 | 3 |
4 | 5 | 简体中文 | [English](./README.md) 6 | 7 |
8 | 9 | ✨ 基于 Next.js 构建的 AI 壁纸生成器。使用tailwind css,shadcn ui,以及i18n系统,支持vercel一键部署。 10 | 11 | ## 🌟 平台预览 12 | 13 |
14 |
15 | AI 壁纸生成器界面 16 |
17 | 🎨 AI 壁纸生成界面 18 |
19 |
20 | 21 | ## 🛠️ 环境要求 22 | 23 | - Node.js 20.x 或更高版本 24 | - Next.js 14.2.9 25 | - pnpm (推荐) 或 npm 26 | 27 | ## 🚀 快速开始 28 | 29 | ```bash 30 | # 克隆仓库 31 | git clone https://github.com/scottcwy/AI-Wallpaper.git 32 | 33 | # 安装依赖 34 | pnpm install 35 | 36 | # 启动开发服务器 37 | pnpm dev 38 | ``` 39 | 40 | ## ✨ 核心特性 41 | 42 | - **🎨 AI 图像生成**: 通过简单的文本提示创建精美图像 43 | - **⚡ 现代界面**: 使用 Tailwind CSS 和 shadcn/ui 组件构建 44 | - **🌐 国际化**: 完整的 i18n 支持,无缝语言切换 45 | - **🚀 一键部署**: 零配置部署到 Vercel 46 | - **📱 响应式设计**: 在桌面、平板和移动设备上完美显示 47 | - **🔐 用户认证**: 支持多种提供商的安全用户系统 48 | 49 | ## ⚙️ 配置 50 | 51 | ```bash 52 | # 复制环境变量模板 53 | cp .env.example .env.local 54 | 55 | # 配置 AI 服务 56 | OPENAI_API_KEY="your_openai_api_key" 57 | 58 | # 可选:身份验证 59 | GOOGLE_CLIENT_ID="your_google_client_id" 60 | GOOGLE_CLIENT_SECRET="your_google_client_secret" 61 | ``` 62 | 63 | ## 🎯 使用方法 64 | 65 | 1. 输入您的图像提示词 66 | 2. 点击生成并等待 AI 魔法 67 | 3. 下载或分享您的创作 68 | 69 | 就是这么简单!简洁而强大。 70 | 71 | ## 🚀 部署 72 | 73 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fscottcwy%2FAI-Wallpaper) 74 | 75 | 一键部署到 Vercel,无需配置。 76 | 77 | ## 🛠️ 技术栈 78 | 79 | - **Next.js 14** - 带有 App Router 的 React 框架 80 | - **Tailwind CSS** - 实用优先的 CSS 框架 81 | - **shadcn/ui** - 美观且易用的组件库 82 | - **i18n** - 国际化支持 83 | - **Vercel** - 部署平台 84 | 85 | ## 📄 许可证 86 | 87 | MIT 许可证 - 详情请参阅 [LICENSE](LICENSE)。 88 | 89 | --- 90 | 91 | **简洁。干净。强大。🎨** 92 | 93 | 94 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /env-template.txt: -------------------------------------------------------------------------------- 1 | # 认证相关配置(Auth.js v5) 2 | # 必填:用于加密会话和JWT,请使用至少32位的强随机字符串 3 | AUTH_SECRET=your-auth-secret-key 4 | 5 | # 可选:仅当你的应用部署在反向代理后或需要显式信任主机时 6 | # AUTH_TRUST_HOST=true 7 | # 可选:如果使用自定义基础路径或特殊部署场景,可设置 AUTH_URL 8 | # AUTH_URL=http://localhost:3000 9 | 10 | # Coze API配置 11 | COZE_API_URL="https://api.coze.cn" 12 | COZE_API_KEY="pat_7UmKEVl4rtjCqJiAnlAy7jmP14vqRmROLsanFxs4ZhHyLJh2XRBD1ooW1ct3MRgm" 13 | COZE_BOT_ID="7488591335084146697" 14 | 15 | # 认证提供商配置 16 | NEXT_PUBLIC_AUTH_GOOGLE_ENABLED=false 17 | NEXT_PUBLIC_AUTH_GITHUB_ENABLED=false 18 | NEXT_PUBLIC_AUTH_GOOGLE_ONE_TAP_ENABLED=false 19 | 20 | # 网站URL配置 21 | NEXT_PUBLIC_WEB_URL=http://localhost:3000 22 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | import bundleAnalyzer from "@next/bundle-analyzer"; 2 | import createNextIntlPlugin from "next-intl/plugin"; 3 | import mdx from "@next/mdx"; 4 | 5 | const withBundleAnalyzer = bundleAnalyzer({ 6 | enabled: process.env.ANALYZE === "true", 7 | }); 8 | 9 | const withNextIntl = createNextIntlPlugin(); 10 | 11 | const withMDX = mdx({ 12 | options: { 13 | remarkPlugins: [], 14 | rehypePlugins: [], 15 | }, 16 | }); 17 | 18 | /** @type {import('next').NextConfig} */ 19 | const nextConfig = { 20 | output: "standalone", 21 | reactStrictMode: true, 22 | pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"], 23 | images: { 24 | domains: ['localhost', 'cdn.example.com'], 25 | remotePatterns: [ 26 | { 27 | protocol: "https", 28 | hostname: "*", 29 | }, 30 | ], 31 | }, 32 | experimental: { 33 | mdxRs: true, 34 | serverComponentsExternalPackages: ['sharp', 'canvas'], 35 | }, 36 | async redirects() { 37 | return []; 38 | }, 39 | }; 40 | 41 | export default withBundleAnalyzer(withNextIntl(withMDX(nextConfig))); 42 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/preview.png -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/apple-icon.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/favicon-32x32-original.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/favicon.ico -------------------------------------------------------------------------------- /public/images/07b1a04b-cc56-481f-a8cd-8e1618989898_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/images/07b1a04b-cc56-481f-a8cd-8e1618989898_0.png -------------------------------------------------------------------------------- /public/images/5a8ccbe9-3bee-4eff-aff1-ff0e7555881b_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/images/5a8ccbe9-3bee-4eff-aff1-ff0e7555881b_0.png -------------------------------------------------------------------------------- /public/images/5b45db9a-e2d7-49bd-a0aa-d1d96658f0ad_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/images/5b45db9a-e2d7-49bd-a0aa-d1d96658f0ad_0.png -------------------------------------------------------------------------------- /public/images/generator-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/images/generator-background.jpg -------------------------------------------------------------------------------- /public/images/my-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/images/my-background.jpg -------------------------------------------------------------------------------- /public/imgs/cnpay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/imgs/cnpay.png -------------------------------------------------------------------------------- /public/imgs/icons/1.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/imgs/icons/2.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/imgs/icons/3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/imgs/icons/4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/imgs/icons/5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/imgs/icons/6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/imgs/logos/nextjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/imgs/logos/vercel.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/imgs/masks/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/imgs/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/imgs/placeholder.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/logo.png -------------------------------------------------------------------------------- /public/robots.txt.example: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Disallow: /*?*q= 4 | Disallow: /privacy-policy 5 | Disallow: /terms-of-service 6 | Disallow: /admin/ 7 | Disallow: /(admin)/ 8 | 9 | # 站点地图 10 | Sitemap: https://www.example.com/sitemap.xml 11 | 12 | # 主机 13 | Host: https://www.example.com 14 | -------------------------------------------------------------------------------- /public/shortcut-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottcwy/Flux-Generator/20aba01aaaa29f88178a8b4a2feb84f35194b132/public/shortcut-icon.png -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"background_color":"#ffffff","display":"standalone","icons":[{"sizes":"192x192","src":"/android-chrome-192x192.png","type":"image/png"},{"sizes":"512x512","src":"/android-chrome-512x512.png","type":"image/png"}],"name":"Katana-NextJS","short_name":"Katana","theme_color":"#ffffff"} -------------------------------------------------------------------------------- /public/sitemap.xml.example: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://www.example.com 5 | 2022-02-28T07:28:24+00:00 6 | daily 7 | 1.0 8 | 9 | 10 | https://www.example.com/en 11 | 2022-02-28T07:28:24+00:00 12 | daily 13 | 1.0 14 | 15 | 16 | https://www.example.com/zh 17 | 2022-02-28T07:28:24+00:00 18 | daily 19 | 1.0 20 | 21 | 22 | https://www.example.com/blog 23 | 2022-02-28T07:28:24+00:00 24 | weekly 25 | 0.8 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/app/(legal)/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { Button } from '@/components/ui/button'; 5 | 6 | export default function LegalLayoutError({ 7 | error, 8 | reset, 9 | }: { 10 | error: Error & { digest?: string }; 11 | reset: () => void; 12 | }) { 13 | useEffect(() => { 14 | // u8bb0u5f55u9519u8befu5230u9519u8befu62a5u544au670du52a1 15 | console.error(error); 16 | }, [error]); 17 | 18 | return ( 19 |
20 |

u51fau73b0u4e86u4e00u4e9bu95eeu9898

21 |

22 | u5f88u62b1u6b49uff0cu6211u4eecu9047u5230u4e86u4e00u4e2au9519u8befu3002u8bf7u5c1du8bd5u5237u65b0u9875u9762u6216u8fd4u56deu9996u9875u3002 23 |

24 |
25 | 28 | 31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/app/(legal)/layout.tsx: -------------------------------------------------------------------------------- 1 | import "@/app/globals.css"; 2 | 3 | import { MdOutlineHome } from "react-icons/md"; 4 | import { Metadata } from "next"; 5 | import React from "react"; 6 | import { getTranslations } from "next-intl/server"; 7 | 8 | export async function generateMetadata(): Promise { 9 | const t = await getTranslations(); 10 | 11 | return { 12 | title: { 13 | template: `%s | ${t("metadata.title")}`, 14 | default: t("metadata.title"), 15 | }, 16 | description: t("metadata.description"), 17 | keywords: t("metadata.keywords"), 18 | }; 19 | } 20 | 21 | export default function LegalLayout({ 22 | children, 23 | }: { 24 | children: React.ReactNode; 25 | }) { 26 | return ( 27 | 28 | 29 |
30 | 34 | 35 | {/* */} 36 | 37 |
38 | {children} 39 |
40 |
41 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/app/(legal)/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | export default function LegalLayoutLoading() { 4 | return ( 5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/[locale]/(admin)/admin/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { Button } from '@/components/ui/button'; 5 | import { useTranslations } from 'next-intl'; 6 | 7 | export default function AdminPageError({ 8 | error, 9 | reset, 10 | }: { 11 | error: Error & { digest?: string }; 12 | reset: () => void; 13 | }) { 14 | const t = useTranslations('Error'); 15 | 16 | useEffect(() => { 17 | // 记录错误到错误报告服务 18 | console.error(error); 19 | }, [error]); 20 | 21 | return ( 22 |
23 |

{t('title')}

24 |

25 | {t('message')} 26 |

27 |
28 | 31 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/app/[locale]/(admin)/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import Empty from "@/components/blocks/empty"; 2 | 3 | export default function () { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/[locale]/(admin)/admin/paid-orders/page.tsx: -------------------------------------------------------------------------------- 1 | import { TableColumn } from "@/types/blocks/table"; 2 | import TableSlot from "@/components/dashboard/slots/table"; 3 | import { Table as TableSlotType } from "@/types/slots/table"; 4 | import { getPaiedOrders } from "@/models/order"; 5 | import moment from "moment"; 6 | 7 | export default async function () { 8 | const orders = await getPaiedOrders(1, 50); 9 | 10 | const columns: TableColumn[] = [ 11 | { name: "order_no", title: "Order No" }, 12 | { name: "paid_email", title: "Paid Email" }, 13 | { name: "product_name", title: "Product Name" }, 14 | { name: "amount", title: "Amount" }, 15 | { 16 | name: "created_at", 17 | title: "Created At", 18 | callback: (row) => moment(row.created_at).format("YYYY-MM-DD HH:mm:ss"), 19 | }, 20 | ]; 21 | 22 | const table: TableSlotType = { 23 | title: "Paid Orders", 24 | columns, 25 | data: orders, 26 | }; 27 | 28 | return ; 29 | } 30 | -------------------------------------------------------------------------------- /src/app/[locale]/(admin)/admin/posts/page.tsx: -------------------------------------------------------------------------------- 1 | import Dropdown from "@/components/blocks/table/dropdown"; 2 | import { NavItem } from "@/types/blocks/base"; 3 | import { Post } from "@/types/post"; 4 | import TableSlot from "@/components/dashboard/slots/table"; 5 | import { Table as TableSlotType } from "@/types/slots/table"; 6 | import { getAllPosts } from "@/models/post"; 7 | import moment from "moment"; 8 | 9 | export default async function () { 10 | const posts = await getAllPosts(); 11 | 12 | const table: TableSlotType = { 13 | title: "Posts", 14 | toolbar: { 15 | items: [ 16 | { 17 | title: "Add Post", 18 | icon: "RiAddLine", 19 | url: "/admin/posts/add", 20 | }, 21 | ], 22 | }, 23 | columns: [ 24 | { 25 | name: "title", 26 | title: "Title", 27 | }, 28 | { 29 | name: "description", 30 | title: "Description", 31 | }, 32 | { 33 | name: "slug", 34 | title: "Slug", 35 | }, 36 | { 37 | name: "locale", 38 | title: "Locale", 39 | }, 40 | { 41 | name: "status", 42 | title: "Status", 43 | }, 44 | { 45 | name: "created_at", 46 | title: "Created At", 47 | callback: (item: Post) => { 48 | return moment(item.created_at).format("YYYY-MM-DD HH:mm:ss"); 49 | }, 50 | }, 51 | { 52 | callback: (item: Post) => { 53 | const items: NavItem[] = [ 54 | { 55 | title: "Edit", 56 | icon: "RiEditLine", 57 | url: `/admin/posts/${item.uuid}/edit`, 58 | }, 59 | { 60 | title: "View", 61 | icon: "RiEyeLine", 62 | url: `/${item.locale}/posts/${item.slug}`, 63 | target: "_blank", 64 | }, 65 | ]; 66 | 67 | return ; 68 | }, 69 | }, 70 | ], 71 | data: posts, 72 | empty_message: "No posts found", 73 | }; 74 | 75 | return ; 76 | } 77 | -------------------------------------------------------------------------------- /src/app/[locale]/(admin)/admin/users/page.tsx: -------------------------------------------------------------------------------- 1 | import { TableColumn } from "@/types/blocks/table"; 2 | import TableSlot from "@/components/dashboard/slots/table"; 3 | import { Table as TableSlotType } from "@/types/slots/table"; 4 | import { getUsers } from "@/models/user"; 5 | import moment from "moment"; 6 | 7 | export default async function () { 8 | const users = await getUsers(1, 50); 9 | 10 | const columns: TableColumn[] = [ 11 | { name: "uuid", title: "UUID" }, 12 | { name: "email", title: "Email" }, 13 | { name: "nickname", title: "Name" }, 14 | { 15 | name: "avatar_url", 16 | title: "Avatar", 17 | callback: (row) => ( 18 | 19 | ), 20 | }, 21 | { 22 | name: "created_at", 23 | title: "Created At", 24 | callback: (row) => moment(row.created_at).format("YYYY-MM-DD HH:mm:ss"), 25 | }, 26 | ]; 27 | 28 | const table: TableSlotType = { 29 | title: "All Users", 30 | columns, 31 | data: users, 32 | }; 33 | 34 | return ; 35 | } 36 | -------------------------------------------------------------------------------- /src/app/[locale]/(admin)/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { Button } from '@/components/ui/button'; 5 | import { useTranslations } from 'next-intl'; 6 | 7 | export default function AdminLayoutError({ 8 | error, 9 | reset, 10 | }: { 11 | error: Error & { digest?: string }; 12 | reset: () => void; 13 | }) { 14 | const t = useTranslations('Error'); 15 | 16 | useEffect(() => { 17 | // u8bb0u5f55u9519u8befu5230u9519u8befu62a5u544au670du52a1 18 | console.error(error); 19 | }, [error]); 20 | 21 | return ( 22 |
23 |

{t('title')}

24 |

25 | {t('message')} 26 |

27 |
28 | 31 | 42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/app/[locale]/(admin)/layout.tsx: -------------------------------------------------------------------------------- 1 | import DashboardLayout from "@/components/dashboard/layout"; 2 | import Empty from "@/components/blocks/empty"; 3 | import { ReactNode } from "react"; 4 | import { Sidebar } from "@/types/blocks/sidebar"; 5 | import { getUserInfo } from "@/services/user"; 6 | import { redirect } from "next/navigation"; 7 | 8 | export default async function AdminLayout({ 9 | children, 10 | }: { 11 | children: ReactNode; 12 | }) { 13 | const userInfo = await getUserInfo(); 14 | if (!userInfo || !userInfo.email) { 15 | redirect("/auth/signin"); 16 | } 17 | 18 | const adminEmails = process.env.ADMIN_EMAILS?.split(","); 19 | if (!adminEmails?.includes(userInfo?.email)) { 20 | return ; 21 | } 22 | 23 | const sidebar: Sidebar = { 24 | brand: { 25 | title: "Katana-NextJS", 26 | logo: { 27 | src: "/logo.png", 28 | alt: "Katana-NextJS", 29 | }, 30 | url: "", 31 | }, 32 | nav: { 33 | items: [ 34 | { 35 | title: "Users", 36 | url: "/admin/users", 37 | icon: "RiUserLine", 38 | }, 39 | { 40 | title: "Orders", 41 | icon: "RiOrderPlayLine", 42 | is_expand: true, 43 | children: [ 44 | { 45 | title: "Paid Orders", 46 | url: "/admin/paid-orders", 47 | }, 48 | ], 49 | }, 50 | { 51 | title: "Posts", 52 | url: "/admin/posts", 53 | icon: "RiArticleLine", 54 | }, 55 | ], 56 | }, 57 | social: { 58 | items: [ 59 | { 60 | title: "Home", 61 | url: "", 62 | target: "_blank", 63 | icon: "RiHomeLine", 64 | }, 65 | { 66 | title: "Github", 67 | url: "", 68 | target: "_blank", 69 | icon: "RiGithubLine", 70 | }, 71 | { 72 | title: "Discord", 73 | url: "https://discord.gg/HQNnrzjZQS", 74 | target: "_blank", 75 | icon: "RiDiscordLine", 76 | }, 77 | { 78 | title: "X", 79 | url: "", 80 | target: "_blank", 81 | icon: "RiTwitterLine", 82 | }, 83 | ], 84 | }, 85 | }; 86 | 87 | return {children}; 88 | } 89 | -------------------------------------------------------------------------------- /src/app/[locale]/(admin)/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | export default function AdminLayoutLoading() { 4 | return ( 5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/[locale]/(default)/(console)/api-keys/create/page.tsx: -------------------------------------------------------------------------------- 1 | import { ApikeyStatus, insertApikey } from "@/models/apikey"; 2 | 3 | import { Apikey } from "@/types/apikey"; 4 | import Empty from "@/components/blocks/empty"; 5 | import FormSlot from "@/components/console/slots/form"; 6 | import { Form as FormSlotType } from "@/types/slots/form"; 7 | import { getIsoTimestr } from "@/lib/time"; 8 | import { getNonceStr } from "@/lib/hash"; 9 | import { getTranslations } from "next-intl/server"; 10 | import { getUserUuid } from "@/services/user"; 11 | 12 | export default async function () { 13 | const t = await getTranslations(); 14 | 15 | const user_uuid = await getUserUuid(); 16 | if (!user_uuid) { 17 | return ; 18 | } 19 | 20 | const form: FormSlotType = { 21 | title: t("api_keys.create_api_key"), 22 | crumb: { 23 | items: [ 24 | { 25 | title: t("api_keys.title"), 26 | url: "/api-keys", 27 | }, 28 | { 29 | title: t("api_keys.create_api_key"), 30 | is_active: true, 31 | }, 32 | ], 33 | }, 34 | fields: [ 35 | { 36 | title: t("api_keys.form.name"), 37 | name: "title", 38 | type: "text", 39 | placeholder: t("api_keys.form.name_placeholder"), 40 | validation: { 41 | required: true, 42 | }, 43 | }, 44 | ], 45 | passby: { 46 | user_uuid, 47 | }, 48 | submit: { 49 | button: { 50 | title: t("api_keys.form.submit"), 51 | }, 52 | handler: async (data: FormData, passby: any) => { 53 | "use server"; 54 | 55 | const { user_uuid } = passby; 56 | if (!user_uuid) { 57 | throw new Error("no auth"); 58 | } 59 | 60 | const title = data.get("title") as string; 61 | if (!title || !title.trim()) { 62 | throw new Error("invalid params"); 63 | } 64 | 65 | const key = `sk-${getNonceStr(32)}`; 66 | 67 | const apikey: Apikey = { 68 | user_uuid, 69 | api_key: key, 70 | title, 71 | created_at: getIsoTimestr(), 72 | status: ApikeyStatus.Created, 73 | }; 74 | 75 | try { 76 | await insertApikey(apikey); 77 | 78 | return { 79 | status: "success", 80 | message: "apikey created", 81 | redirect_url: "/api-keys", 82 | }; 83 | } catch (e: any) { 84 | console.error(e); 85 | throw new Error("create api key failed: " + e.message); 86 | } 87 | }, 88 | }, 89 | }; 90 | 91 | return ; 92 | } 93 | -------------------------------------------------------------------------------- /src/app/[locale]/(default)/(console)/api-keys/page.tsx: -------------------------------------------------------------------------------- 1 | import Empty from "@/components/blocks/empty"; 2 | import TableSlot from "@/components/console/slots/table"; 3 | import { Table as TableSlotType } from "@/types/slots/table"; 4 | import { getTranslations } from "next-intl/server"; 5 | import { getUserApikeys } from "@/models/apikey"; 6 | import { getUserUuid } from "@/services/user"; 7 | import moment from "moment"; 8 | 9 | export default async function () { 10 | const t = await getTranslations(); 11 | 12 | const user_uuid = await getUserUuid(); 13 | if (!user_uuid) { 14 | return ; 15 | } 16 | 17 | const data = await getUserApikeys(user_uuid); 18 | 19 | const table: TableSlotType = { 20 | title: t("api_keys.title"), 21 | tip: { 22 | title: t("api_keys.tip"), 23 | }, 24 | toolbar: { 25 | items: [ 26 | { 27 | title: t("api_keys.create_api_key"), 28 | url: "/api-keys/create", 29 | icon: "RiAddLine", 30 | }, 31 | ], 32 | }, 33 | columns: [ 34 | { 35 | title: t("api_keys.table.name"), 36 | name: "title", 37 | }, 38 | { 39 | title: t("api_keys.table.key"), 40 | name: "api_key", 41 | type: "copy", 42 | callback: (item: any) => { 43 | return item.api_key.slice(0, 4) + "..." + item.api_key.slice(-4); 44 | }, 45 | }, 46 | { 47 | title: t("api_keys.table.created_at"), 48 | name: "created_at", 49 | callback: (item: any) => { 50 | return moment(item.created_at).fromNow(); 51 | }, 52 | }, 53 | ], 54 | data, 55 | empty_message: t("api_keys.no_api_keys"), 56 | }; 57 | 58 | return ; 59 | } 60 | -------------------------------------------------------------------------------- /src/app/[locale]/(default)/(console)/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { Button } from '@/components/ui/button'; 5 | import { useTranslations } from 'next-intl'; 6 | 7 | export default function ConsoleLayoutError({ 8 | error, 9 | reset, 10 | }: { 11 | error: Error & { digest?: string }; 12 | reset: () => void; 13 | }) { 14 | const t = useTranslations('Error'); 15 | 16 | useEffect(() => { 17 | // u8bb0u5f55u9519u8befu5230u9519u8befu62a5u544au670du52a1 18 | console.error(error); 19 | }, [error]); 20 | 21 | return ( 22 |
23 |

{t('title')}

24 |

25 | {t('message')} 26 |

27 |
28 | 31 | 42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/app/[locale]/(default)/(console)/layout.tsx: -------------------------------------------------------------------------------- 1 | import ConsoleLayout from "@/components/console/layout"; 2 | import { ReactNode } from "react"; 3 | import { Sidebar } from "@/types/blocks/sidebar"; 4 | import { getTranslations } from "next-intl/server"; 5 | import { getUserInfo } from "@/services/user"; 6 | import { redirect } from "next/navigation"; 7 | 8 | export default async function ({ children }: { children: ReactNode }) { 9 | const userInfo = await getUserInfo(); 10 | if (!userInfo || !userInfo.email) { 11 | redirect("/auth/signin"); 12 | } 13 | 14 | const t = await getTranslations(); 15 | 16 | const sidebar: Sidebar = { 17 | nav: { 18 | items: [ 19 | { 20 | title: t("user.my_orders"), 21 | url: "/my-orders", 22 | icon: "RiOrderPlayLine", 23 | is_active: false, 24 | }, 25 | { 26 | title: t("my_credits.title"), 27 | url: "/my-credits", 28 | icon: "RiBankCardLine", 29 | is_active: false, 30 | }, 31 | { 32 | title: t("api_keys.title"), 33 | url: "/api-keys", 34 | icon: "RiKey2Line", 35 | is_active: false, 36 | }, 37 | ], 38 | }, 39 | }; 40 | 41 | return {children}; 42 | } 43 | -------------------------------------------------------------------------------- /src/app/[locale]/(default)/(console)/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from '@/components/ui/skeleton'; 2 | 3 | export default function ConsoleLayoutLoading() { 4 | return ( 5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/[locale]/(default)/(console)/my-credits/page.tsx: -------------------------------------------------------------------------------- 1 | import Empty from "@/components/blocks/empty"; 2 | import TableSlot from "@/components/console/slots/table"; 3 | import { Table as TableSlotType } from "@/types/slots/table"; 4 | import { getCreditsByUserUuid } from "@/models/credit"; 5 | import { getTranslations } from "next-intl/server"; 6 | import { getUserCredits } from "@/services/credit"; 7 | import { getUserUuid } from "@/services/user"; 8 | import moment from "moment"; 9 | 10 | export default async function () { 11 | const t = await getTranslations(); 12 | 13 | const user_uuid = await getUserUuid(); 14 | 15 | if (!user_uuid) { 16 | return ; 17 | } 18 | 19 | const data = await getCreditsByUserUuid(user_uuid, 1, 100); 20 | 21 | const userCredits = await getUserCredits(user_uuid); 22 | 23 | const table: TableSlotType = { 24 | title: t("my_credits.title"), 25 | tip: { 26 | title: t("my_credits.left_tip", { 27 | left_credits: userCredits?.left_credits || 0, 28 | }), 29 | }, 30 | toolbar: { 31 | items: [ 32 | { 33 | title: t("my_credits.recharge"), 34 | url: "/#pricing", 35 | target: "_blank", 36 | }, 37 | ], 38 | }, 39 | columns: [ 40 | { 41 | title: t("my_credits.table.trans_no"), 42 | name: "trans_no", 43 | }, 44 | { 45 | title: t("my_credits.table.trans_type"), 46 | name: "trans_type", 47 | }, 48 | { 49 | title: t("my_credits.table.credits"), 50 | name: "credits", 51 | }, 52 | { 53 | title: t("my_credits.table.updated_at"), 54 | name: "created_at", 55 | callback: (v: any) => { 56 | return moment(v.created_at).format("YYYY-MM-DD HH:mm:ss"); 57 | }, 58 | }, 59 | ], 60 | data, 61 | empty_message: t("my_credits.no_credits"), 62 | }; 63 | 64 | return ; 65 | } 66 | -------------------------------------------------------------------------------- /src/app/[locale]/(default)/(console)/my-orders/page.tsx: -------------------------------------------------------------------------------- 1 | import { getOrdersByPaidEmail, getOrdersByUserUuid } from "@/models/order"; 2 | import { getUserEmail, getUserUuid } from "@/services/user"; 3 | 4 | import { TableColumn } from "@/types/blocks/table"; 5 | import TableSlot from "@/components/console/slots/table"; 6 | import { Table as TableSlotType } from "@/types/slots/table"; 7 | import { getTranslations } from "next-intl/server"; 8 | import moment from "moment"; 9 | import { redirect } from "next/navigation"; 10 | 11 | export default async function () { 12 | const t = await getTranslations(); 13 | 14 | const user_uuid = await getUserUuid(); 15 | const user_email = await getUserEmail(); 16 | 17 | const callbackUrl = `${process.env.NEXT_PUBLIC_WEB_URL}/my-orders`; 18 | if (!user_uuid) { 19 | redirect(`/auth/signin?callbackUrl=${encodeURIComponent(callbackUrl)}`); 20 | } 21 | 22 | let orders = await getOrdersByUserUuid(user_uuid); 23 | if (!orders || orders.length === 0) { 24 | orders = await getOrdersByPaidEmail(user_email); 25 | } 26 | 27 | const columns: TableColumn[] = [ 28 | { name: "order_no", title: t("my_orders.table.order_no") }, 29 | { name: "paid_email", title: t("my_orders.table.email") }, 30 | { name: "product_name", title: t("my_orders.table.product_name") }, 31 | { 32 | name: "amount", 33 | title: t("my_orders.table.amount"), 34 | callback: (item: any) => 35 | `${item.currency.toUpperCase() === "CNY" ? "¥" : "$"} ${ 36 | item.amount / 100 37 | }`, 38 | }, 39 | { 40 | name: "paid_at", 41 | title: t("my_orders.table.paid_at"), 42 | callback: (item: any) => 43 | moment(item.paid_at).format("YYYY-MM-DD HH:mm:ss"), 44 | }, 45 | ]; 46 | 47 | const table: TableSlotType = { 48 | title: t("my_orders.title"), 49 | description: t("my_orders.description"), 50 | toolbar: { 51 | items: [ 52 | { 53 | title: t("my_orders.read_docs"), 54 | icon: "RiBookLine", 55 | url: "", 56 | target: "_blank", 57 | variant: "outline", 58 | }, 59 | { 60 | title: t("my_orders.join_discord"), 61 | icon: "RiDiscordFill", 62 | url: "https://discord.gg/HQNnrzjZQS", 63 | target: "_blank", 64 | }, 65 | ], 66 | }, 67 | columns: columns, 68 | data: orders, 69 | empty_message: t("my_orders.no_orders"), 70 | }; 71 | 72 | return ; 73 | } 74 | -------------------------------------------------------------------------------- /src/app/[locale]/(default)/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { Button } from '@/components/ui/button'; 5 | import { useTranslations } from 'next-intl'; 6 | 7 | export default function DefaultLayoutError({ 8 | error, 9 | reset, 10 | }: { 11 | error: Error & { digest?: string }; 12 | reset: () => void; 13 | }) { 14 | const t = useTranslations('Error'); 15 | 16 | useEffect(() => { 17 | // 记录错误到错误报告服务 18 | console.error(error); 19 | }, [error]); 20 | 21 | return ( 22 |
23 |

{t('title')}

24 |

25 | {t('message')} 26 |

27 |
28 | 31 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/app/[locale]/(default)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from "@/components/blocks/footer"; 2 | import Header from "@/components/blocks/header"; 3 | import { ReactNode } from "react"; 4 | import { getLandingPage } from "@/services/page"; 5 | import dynamic from 'next/dynamic'; 6 | import Image from "next/image"; 7 | 8 | const SharedBackground = dynamic(() => import('@/components/ui/shared-background'), { 9 | ssr: false 10 | }); 11 | 12 | export default async function DefaultLayout({ 13 | children, 14 | params: { locale }, 15 | }: { 16 | children: ReactNode; 17 | params: { locale: string }; 18 | }) { 19 | const page = await getLandingPage(locale); 20 | 21 | return ( 22 | <> 23 | {/* 固定的背景图片 - 应用于整个layout */} 24 |
25 | Background 32 |
33 | 34 | {/* 注释掉SharedBackground以允许自定义背景图片显示 */} 35 | {/* */} 36 | {page.header &&
} 37 |
{children}
38 | {page.footer &&