├── .npmrc ├── public ├── afd.png ├── og.png ├── zs.jpeg ├── logo.png ├── 1-black.png ├── 2-black.png ├── favicon.ico ├── 302banner1.jpg ├── nexty_og.webp ├── screenshot.png ├── favicon-16x16.png ├── nexty_og_zh.webp ├── twitter-image.png ├── opengraph-image.png ├── apple-touch-icon.png ├── android-chrome-512x512.png ├── sitemap.xml ├── robots.txt ├── sitemap-0.xml └── logo.svg ├── types ├── request.ts ├── usage.ts ├── layout.ts ├── user.ts ├── sideNav.ts ├── siteConfig.ts └── subscribe.ts ├── .prettierignore ├── assets └── fonts │ ├── Inter-Bold.ttf │ ├── Inter-Regular.ttf │ ├── CalSans-SemiBold.ttf │ ├── CalSans-SemiBold.woff │ └── CalSans-SemiBold.woff2 ├── postcss.config.js ├── lib ├── lemonsqueezy │ ├── lemons.ts │ ├── subscriptionFromStorage.ts │ └── subscription.ts ├── utils.ts ├── response │ └── responseUtils.ts ├── session.ts ├── clsxm.ts ├── redis.ts ├── axios.ts ├── prisma.ts ├── verifyUtils │ └── verifyUtils.ts ├── data.ts ├── upgrade │ └── upgrade.ts ├── constants.ts ├── auth.ts └── usage │ └── usage.ts ├── pages └── api │ └── auth │ └── [...nextauth].ts ├── app ├── providers.tsx ├── (about) │ ├── layout.tsx │ └── [...slug] │ │ └── page.tsx ├── (home) │ ├── layout.tsx │ ├── page.tsx │ └── homePage.tsx ├── (dashboard) │ ├── layout.tsx │ └── billing │ │ ├── loading.tsx │ │ └── page.tsx ├── api │ ├── restricted.ts │ ├── completion │ │ └── route.ts │ └── payment │ │ ├── subscribe │ │ └── route.ts │ │ └── webhook │ │ └── route.ts ├── BaiDuAnalytics.tsx ├── GoogleAnalytics.tsx ├── login │ └── page.tsx └── layout.tsx ├── components ├── ui │ ├── skeleton.tsx │ ├── avatar.tsx │ ├── alert.tsx │ ├── button.tsx │ ├── card.tsx │ ├── alert-dialog.tsx │ └── dropdown-menu.tsx ├── ThemeProvider.tsx ├── layout │ ├── AboutLayout.tsx │ ├── HomeLayout.tsx │ └── DashboardLayout.tsx ├── dashboard │ ├── shell.tsx │ ├── card-skeleton.tsx │ ├── dashboard-header.tsx │ ├── nav.tsx │ └── billing-form.tsx ├── MainHeader.tsx ├── icons │ ├── PossessPoint.tsx │ ├── Twitter.tsx │ ├── GitHub.tsx │ └── CrownIcon.tsx ├── mdx │ ├── callout.tsx │ ├── mdx-card.tsx │ └── mdx-components.tsx ├── TailwindIndicator.tsx ├── UserAvatar.tsx ├── Header.tsx ├── social-icons │ ├── index.tsx │ └── icons.tsx ├── Footer.tsx ├── subscribe │ ├── SubscribeCard.tsx │ └── Subscribe.tsx ├── ContactMe.tsx ├── UserAuthForm.tsx ├── DropDown.tsx ├── Icons.tsx └── UserAccountHeader.tsx ├── components.json ├── gtag.js ├── config ├── dashboard.ts └── site.ts ├── docker-compose.yml ├── .vscode └── settings.json ├── .gitignore ├── styles ├── loading.css ├── mdx.css └── globals.css ├── next.config.js ├── tsconfig.json ├── LICENSE ├── sitemap.ts ├── prisma └── schema.prisma ├── next-sitemap.config.js ├── .env.example ├── tailwind.config.js ├── contentlayer.config.js ├── package.json ├── README-zh.md ├── README.md └── content └── about └── privacy.mdx /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true -------------------------------------------------------------------------------- /public/afd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/afd.png -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/og.png -------------------------------------------------------------------------------- /public/zs.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/zs.jpeg -------------------------------------------------------------------------------- /types/request.ts: -------------------------------------------------------------------------------- 1 | export interface Res { 2 | message: string; 3 | code: number; 4 | } -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/logo.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .next 4 | build 5 | .contentlayer 6 | postgres 7 | *.md -------------------------------------------------------------------------------- /public/1-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/1-black.png -------------------------------------------------------------------------------- /public/2-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/2-black.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/302banner1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/302banner1.jpg -------------------------------------------------------------------------------- /public/nexty_og.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/nexty_og.webp -------------------------------------------------------------------------------- /public/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/screenshot.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/nexty_og_zh.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/nexty_og_zh.webp -------------------------------------------------------------------------------- /public/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/twitter-image.png -------------------------------------------------------------------------------- /public/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/opengraph-image.png -------------------------------------------------------------------------------- /assets/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/assets/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/assets/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/CalSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/assets/fonts/CalSans-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/CalSans-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/assets/fonts/CalSans-SemiBold.woff -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /assets/fonts/CalSans-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijunext/smart-excel-ai/HEAD/assets/fonts/CalSans-SemiBold.woff2 -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /lib/lemonsqueezy/lemons.ts: -------------------------------------------------------------------------------- 1 | import { LemonsqueezyClient } from "lemonsqueezy.ts"; 2 | 3 | export const client = new LemonsqueezyClient(process.env.LEMON_SQUEEZY_API_KEY as string); -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth" 2 | 3 | import { authOptions } from "@/lib/auth" 4 | 5 | // @see ./lib/auth 6 | export default NextAuth(authOptions) 7 | -------------------------------------------------------------------------------- /types/usage.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface DateRemaining { 3 | userTodayRemaining: number; // 今天剩余次数 4 | boostPackRemaining: number; // 加油包剩余次数 5 | userDateRemaining: number; // 上面二者总的剩余次数 6 | } 7 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /lib/response/responseUtils.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | export function unauthorizedResponse(message: string) { 4 | return NextResponse.json({ error: message }, { status: 401 }); 5 | } -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://www.smartexcel.cc/sitemap-0.xml 4 | -------------------------------------------------------------------------------- /types/layout.ts: -------------------------------------------------------------------------------- 1 | import { UserInfo } from "@/types/user"; 2 | 3 | export interface HomeLayoutChildren { 4 | children?: React.ReactNode; 5 | } 6 | export interface HomeLayoutProps extends HomeLayoutChildren { 7 | user?: UserInfo; 8 | } -------------------------------------------------------------------------------- /lib/session.ts: -------------------------------------------------------------------------------- 1 | import { getServerSession } from "next-auth/next"; 2 | 3 | import { authOptions } from "@/lib/auth"; 4 | 5 | export async function getCurrentUser() { 6 | const session = await getServerSession(authOptions) 7 | 8 | return session?.user 9 | } 10 | -------------------------------------------------------------------------------- /lib/clsxm.ts: -------------------------------------------------------------------------------- 1 | import clsx, { ClassValue } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | /** Merge classes with tailwind-merge with clsx full feature */ 5 | export default function clsxm(...classes: ClassValue[]) { 6 | return twMerge(clsx(...classes)); 7 | } 8 | -------------------------------------------------------------------------------- /app/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SessionProvider } from "next-auth/react"; 4 | 5 | type Props = { 6 | children?: React.ReactNode; 7 | }; 8 | 9 | export const NextAuthProvider = ({ children }: Props) => { 10 | return {children}; 11 | }; 12 | -------------------------------------------------------------------------------- /lib/redis.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from '@upstash/redis' 2 | 3 | const redis = new Redis({ 4 | url: `${process.env.UPSTASH_REDIS_REST_URL}`, 5 | token: `${process.env.UPSTASH_REDIS_REST_TOKEN}`, 6 | retry: { 7 | retries: 5, 8 | backoff: (retryCount) => Math.exp(retryCount) * 50, 9 | }, 10 | }) 11 | 12 | export default redis -------------------------------------------------------------------------------- /app/(about)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { AboutLayout } from "@/components/layout/AboutLayout"; 2 | import { HomeLayoutChildren } from "@/types/layout"; 3 | 4 | export default async function AboutPageLayout({ 5 | children, 6 | }: HomeLayoutChildren) { 7 | return ( 8 | <> 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /components/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ThemeProvider as NextThemesProvider } from "next-themes" 5 | import { ThemeProviderProps } from "next-themes/dist/types" 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children} 9 | } 10 | -------------------------------------------------------------------------------- /lib/axios.ts: -------------------------------------------------------------------------------- 1 | import Axios from "axios"; 2 | 3 | export const axios = Axios.create({ 4 | baseURL: "/", 5 | }); 6 | 7 | axios.interceptors.response.use( 8 | (response) => response.data, 9 | (error) => { 10 | const message = error.response?.data?.message || error.message; 11 | // toast.error(message); 12 | console.log(message); 13 | return Promise.reject(error); 14 | } 15 | ); -------------------------------------------------------------------------------- /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.js", 8 | "css": "styles/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /gtag.js: -------------------------------------------------------------------------------- 1 | export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GOOGLE_ID 2 | 3 | export const pageview = url => { 4 | window.gtag("config", GA_TRACKING_ID, { 5 | page_path: url, 6 | }) 7 | } 8 | 9 | export const event = ({ action, category, label, value }) => { 10 | window.gtag("event", action, { 11 | event_category: category, 12 | event_label: label, 13 | value: value, 14 | }) 15 | } -------------------------------------------------------------------------------- /components/layout/AboutLayout.tsx: -------------------------------------------------------------------------------- 1 | import { HomeLayoutProps } from "@/types/layout"; 2 | 3 | export const AboutLayout = ({ children, user }: HomeLayoutProps) => { 4 | return ( 5 |
6 |
7 | {children} 8 |
9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /components/layout/HomeLayout.tsx: -------------------------------------------------------------------------------- 1 | import { HomeLayoutProps } from "@/types/layout"; 2 | 3 | export const HomeLayout = ({ children, user }: HomeLayoutProps) => { 4 | return ( 5 |
6 |
7 | {children} 8 |
9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /config/dashboard.ts: -------------------------------------------------------------------------------- 1 | import { DashboardConfig } from "@/types/sideNav"; 2 | 3 | export const dashboardConfig: DashboardConfig = { 4 | mainNav: [ 5 | 6 | ], 7 | sidebarNav: [ 8 | // { 9 | // title: "History", 10 | // href: "/history", 11 | // icon: "post", 12 | // }, 13 | { 14 | title: "Billing", 15 | href: "/billing", 16 | icon: "billing", 17 | }, 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # * 2 | User-agent: * 3 | Allow: / 4 | 5 | # AhrefsBot 6 | User-agent: AhrefsBot 7 | Disallow: / 8 | 9 | # SemrushBot 10 | User-agent: SemrushBot 11 | Disallow: / 12 | 13 | # MJ12bot 14 | User-agent: MJ12bot 15 | Disallow: / 16 | 17 | # DotBot 18 | User-agent: DotBot 19 | Disallow: / 20 | 21 | # Host 22 | Host: https://www.smartexcel.cc 23 | 24 | # Sitemaps 25 | Sitemap: https://www.smartexcel.cc/sitemap.xml 26 | -------------------------------------------------------------------------------- /components/layout/DashboardLayout.tsx: -------------------------------------------------------------------------------- 1 | import { HomeLayoutProps } from "@/types/layout"; 2 | import { notFound } from "next/navigation"; 3 | 4 | export const DashboardLayout = ({ children, user }: HomeLayoutProps) => { 5 | if (!user) { 6 | return notFound(); 7 | } 8 | return ( 9 |
10 |
{children}
11 |
12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-var 5 | var prisma: PrismaClient 6 | } 7 | 8 | let prisma: PrismaClient; 9 | 10 | if (process.env.NODE_ENV === "production") { 11 | prisma = new PrismaClient(); 12 | } else { 13 | if (!global.prisma) { 14 | global.prisma = new PrismaClient(); 15 | } 16 | prisma = global.prisma; 17 | } 18 | export default prisma; 19 | -------------------------------------------------------------------------------- /components/dashboard/shell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | interface DashboardShellProps extends React.HTMLAttributes {} 6 | 7 | export function DashboardShell({ 8 | children, 9 | className, 10 | ...props 11 | }: DashboardShellProps) { 12 | return ( 13 |
14 | {children} 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /app/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { HomeLayout } from "@/components/layout/HomeLayout"; 2 | import { getCurrentUser } from "@/lib/session"; 3 | import { HomeLayoutChildren } from "@/types/layout"; 4 | import { UserInfo } from "@/types/user"; 5 | 6 | export default async function HomePageLayout({ children }: HomeLayoutChildren) { 7 | const user = (await getCurrentUser()) as UserInfo; 8 | 9 | return ( 10 | <> 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /app/(dashboard)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { DashboardLayout } from "@/components/layout/DashboardLayout"; 2 | import { getCurrentUser } from "@/lib/session"; 3 | import { HomeLayoutChildren } from "@/types/layout"; 4 | import { UserInfo } from "@/types/user"; 5 | 6 | export default async function HomePageLayout({ children }: HomeLayoutChildren) { 7 | const user = (await getCurrentUser()) as UserInfo; 8 | 9 | return ( 10 | <> 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # docker-compose.yml 2 | # how to use? see my blog: https://weijunext.com/article/b33a5545-fd26-47a6-8641-3c7467fb3910 3 | version: '3.1' 4 | services: 5 | db: 6 | image: postgres 7 | volumes: 8 | - ./postgres:/var/lib/postgresql/data 9 | restart: always 10 | ports: 11 | - 5432:5432 12 | environment: 13 | - POSTGRES_USER=myuser 14 | - POSTGRES_PASSWORD=mypassword 15 | 16 | adminer: 17 | image: adminer 18 | restart: always 19 | ports: 20 | - 8080:8080 21 | -------------------------------------------------------------------------------- /public/sitemap-0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://www.smartexcel.cc2024-08-12T06:13:41.519Zdaily0.7 4 | -------------------------------------------------------------------------------- /components/MainHeader.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | 4 | export default function MainHeader() { 5 | return ( 6 | 7 | header text 14 | 15 | Smart Excel 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "css.validate": false, 3 | "editor.formatOnSave": true, 4 | "editor.tabSize": 2, 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "explicit", 7 | "source.organizeImports": "explicit" 8 | }, 9 | "headwind.runOnSave": false, 10 | "typescript.preferences.importModuleSpecifier": "non-relative", 11 | "eslint.validate": ["javascript", "javascriptreact", "typescript"], 12 | "typescript.tsdk": "node_modules/typescript/lib", 13 | "commentTranslate.source": "Bing", 14 | "cSpell.words": [ 15 | "contentlayer", 16 | "lemonsqueezy" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /app/(dashboard)/billing/loading.tsx: -------------------------------------------------------------------------------- 1 | import { CardSkeleton } from "@/components/dashboard/card-skeleton"; 2 | import { DashboardHeader } from "@/components/dashboard/dashboard-header"; 3 | import { DashboardShell } from "@/components/dashboard/shell"; 4 | 5 | export default function DashboardBillingLoading() { 6 | return ( 7 | 8 | 12 |
13 | 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/verifyUtils/verifyUtils.ts: -------------------------------------------------------------------------------- 1 | import redis from "@/lib/redis"; 2 | 3 | export async function verifyReferer(request: Request) { 4 | const referer = request.headers.get('referer'); 5 | if (!referer || !referer.includes(process.env.REFERER_MAIN_URL as string)) { 6 | return false; 7 | } 8 | return true; 9 | } 10 | 11 | export async function verifyToken(request: Request) { 12 | const token = request.headers.get('token'); 13 | if (!token) { 14 | return false; 15 | } 16 | const userId = await redis.get(token) + ''; 17 | if (!userId) { 18 | return false; 19 | } 20 | return userId; 21 | } -------------------------------------------------------------------------------- /components/dashboard/card-skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Card, 3 | CardContent, 4 | CardFooter, 5 | CardHeader, 6 | } from "@/components/ui/card"; 7 | import { Skeleton } from "@/components/ui/skeleton"; 8 | 9 | export function CardSkeleton() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /components/dashboard/dashboard-header.tsx: -------------------------------------------------------------------------------- 1 | interface DashboardHeaderProps { 2 | heading: string 3 | text?: string 4 | children?: React.ReactNode 5 | } 6 | 7 | export function DashboardHeader({ 8 | heading, 9 | text, 10 | children, 11 | }: DashboardHeaderProps) { 12 | return ( 13 |
14 |
15 |

{heading}

16 | {text &&

{text}

} 17 |
18 | {children} 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /types/user.ts: -------------------------------------------------------------------------------- 1 | import { User } from "@prisma/client"; 2 | 3 | export type Role = 0 | 2; // 0 Standard User; 2 Member User 4 | 5 | export type RedisUserId = string | null 6 | 7 | export interface UserId { 8 | userId: string; 9 | } 10 | 11 | export interface RemainingParams { 12 | userId: string; 13 | role?: Role; 14 | } 15 | 16 | export interface UserInfo { 17 | userId: string; 18 | username: string; 19 | avatar?: string; 20 | platform: string; 21 | email: string; 22 | role: Role; 23 | membershipExpire?: number; 24 | accessToken?: string; 25 | } 26 | 27 | export interface PrismaUser extends User { } -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | .env 39 | 40 | # idea 41 | .idea 42 | .contentlayer 43 | postgres -------------------------------------------------------------------------------- /app/api/restricted.ts: -------------------------------------------------------------------------------- 1 | import { getServerSession } from "next-auth/next" 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | import { authOptions } from '@/lib/auth'; 4 | 5 | export default async (req: NextApiRequest, res: NextApiResponse) => { 6 | const session = await getServerSession(req, res, authOptions) 7 | 8 | if (session) { 9 | res.send({ 10 | content: 11 | "This is protected content. You can access this content because you are signed in.", 12 | }) 13 | } else { 14 | res.send({ 15 | error: "You must be signed in to view the protected content on this page.", 16 | }) 17 | } 18 | } -------------------------------------------------------------------------------- /components/icons/PossessPoint.tsx: -------------------------------------------------------------------------------- 1 | import clsxm from "@/lib/clsxm"; 2 | 3 | export default function PossessPoint({ className }: { className?: string }) { 4 | return ( 5 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /types/sideNav.ts: -------------------------------------------------------------------------------- 1 | import { Icons } from "@/components/Icons" 2 | 3 | export type NavItem = { 4 | title: string 5 | href: string 6 | disabled?: boolean 7 | } 8 | 9 | export type MainNavItem = NavItem 10 | 11 | export type NavLink = { 12 | title: string 13 | href: string 14 | icon?: string 15 | } 16 | 17 | export type SidebarNavItem = { 18 | title: string 19 | disabled?: boolean 20 | external?: boolean 21 | icon?: keyof typeof Icons 22 | } & ( 23 | | { 24 | href: string 25 | items?: never 26 | } 27 | | { 28 | href?: string 29 | items: NavLink[] 30 | } 31 | ) 32 | 33 | export type DashboardConfig = { 34 | mainNav: MainNavItem[] 35 | sidebarNav: SidebarNavItem[] 36 | } -------------------------------------------------------------------------------- /components/mdx/callout.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | interface CalloutProps { 4 | icon?: string 5 | children?: React.ReactNode 6 | type?: "default" | "warning" | "danger" 7 | } 8 | 9 | export function Callout({ 10 | children, 11 | icon, 12 | type = "default", 13 | ...props 14 | }: CalloutProps) { 15 | return ( 16 |
23 | {icon && {icon}} 24 |
{children}
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /styles/loading.css: -------------------------------------------------------------------------------- 1 | .loading { 2 | display: inline-flex; 3 | align-items: center; 4 | } 5 | 6 | .loading .spacer { 7 | margin-right: 2px; 8 | } 9 | 10 | .loading span { 11 | animation-name: blink; 12 | animation-duration: 1.4s; 13 | animation-iteration-count: infinite; 14 | animation-fill-mode: both; 15 | width: 5px; 16 | height: 5px; 17 | border-radius: 50%; 18 | display: inline-block; 19 | margin: 0 1px; 20 | } 21 | 22 | .loading span:nth-of-type(2) { 23 | animation-delay: 0.2s; 24 | } 25 | 26 | .loading span:nth-of-type(3) { 27 | animation-delay: 0.4s; 28 | } 29 | 30 | @keyframes blink { 31 | 0% { 32 | opacity: 0.2; 33 | } 34 | 20% { 35 | opacity: 1; 36 | } 37 | 100% { 38 | opacity: 0.2; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /components/TailwindIndicator.tsx: -------------------------------------------------------------------------------- 1 | export function TailwindIndicator() { 2 | if (process.env.NODE_ENV === "production") return null 3 | 4 | return ( 5 |
6 |
xs
7 |
8 | sm 9 |
10 |
md
11 |
lg
12 |
xl
13 |
2xl
14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /app/BaiDuAnalytics.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Script from "next/script"; 4 | 5 | const BaiDuAnalytics = () => { 6 | return ( 7 | <> 8 |