[admin] feat: initial commit

(cherry picked from commit fe7a9f9dd3abe2bba351ce58435b5c4bd2d97f8a)
This commit is contained in:
Yangshun 2025-07-08 15:46:36 +08:00
parent b013d54ef5
commit 611dbc37e9
23 changed files with 985 additions and 202 deletions

View File

@ -0,0 +1,20 @@
REDDIT_CLIENT_ID=your_client_id
REDDIT_CLIENT_SECRET=your_client_secret
REDDIT_USER_AGENT=your_user_agent
GOOGLE_GENERATIVE_AI_API_KEY=your_api_key
DATABASE_URL=postgresql://postgres:[YOUR-PASSWORD]@db.[SUPABASE_REFERENCE_ID].supabase.co:6543/postgres?pgbouncer=true&connection_limit=30&pool_timeout=600
DIRECT_URL=postgresql://postgres:[YOUR-PASSWORD]@db.[SUPABASE_REFERENCE_ID].supabase.com:5432/postgres
NEXTAUTH_SECRET=secret_key
NEXTAUTH_URL=http://localhost:3000
AUTH_GOOGLE_ID=google_id
AUTH_GOOGLE_SECRET=google_secret
PASSWORD_KEY=some_random_value
GOOGLE_CHAT_WEBHOOK_URL=google_chat_webhook_url
CRON_SECRET=some_random_value

46
apps/admin/.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# primsa
/prisma/generated/*
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
tsconfig.tsbuildinfo

View File

@ -0,0 +1,2 @@
src/__generated__
src/supabase/database.types.ts

4
apps/admin/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

30
apps/admin/README.md Normal file
View File

@ -0,0 +1,30 @@
## Getting Started
First, run the development server:
```bash
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn/foundations/about-nextjs) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@ -0,0 +1,4 @@
import nextConfig from '@gfe/eslint-config/next';
/** @type {import("eslint").Linter.Config} */
export default nextConfig;

5
apps/admin/next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View File

@ -0,0 +1,22 @@
// @ts-check
import { PrismaPlugin } from '@prisma/nextjs-monorepo-workaround-plugin';
/**
* @type {import('next').NextConfig}
**/
const nextConfig = {
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
reactStrictMode: true,
experimental: {
optimizePackageImports: ['@mantine/core', '@mantine/hooks'],
},
webpack: (config, { isServer }) => {
if (isServer) {
config.plugins = [...config.plugins, new PrismaPlugin()];
}
return config;
},
};
export default nextConfig;

37
apps/admin/package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "@gfe/admin",
"version": "0.0.1",
"type": "module",
"scripts": {
"check": "npm run tsc && npm run lint",
"build": "next build",
"dev": "next dev",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx}\"",
"start": "next start",
"lint": "eslint src --fix",
"tsc": "tsc"
},
"dependencies": {
"clsx": "^2.1.1",
"next": "14.2.15",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@gfe/eslint-config": "workspace:*",
"@gfe/typescript-config": "workspace:*",
"@next/eslint-plugin-next": "14.2.15",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"eslint": "^9.26.0",
"postcss": "^8.4.32",
"prettier": "3.5.3",
"prettier-plugin-tailwindcss": "0.6.11",
"tailwindcss": "^3.4.17",
"typescript": "5.8.3"
}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

View File

@ -0,0 +1,25 @@
import clsx from 'clsx';
import type { Metadata } from 'next';
import '~/styles/globals.css';
import RootLayout from '../components/RootLayout';
type Props = Readonly<{
children: React.ReactNode;
}>;
export const metadata: Metadata = {
description: 'Admin dashboard',
title: 'GreatFrontEnd Admin',
};
export default function Layout({ children }: Props) {
return (
<html lang="en">
<body className={clsx('antialiased', 'bg-white')}>
<RootLayout>{children}</RootLayout>
</body>
</html>
);
}

View File

@ -0,0 +1,7 @@
export default function Page() {
return (
<div className="flex h-screen items-center justify-center">
<h1 className="text-2xl font-bold">Admin Dashboard</h1>
</div>
);
}

View File

@ -0,0 +1,9 @@
'use client';
type Props = Readonly<{
children: React.ReactNode;
}>;
export default function RootLayout({ children }: Props) {
return <>{children}</>;
}

View File

@ -0,0 +1,36 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: 'Inter var';
font-weight: 100 900;
font-display: swap;
font-style: normal;
font-named-instance: 'Regular';
src: url('./fonts/Inter-roman-latin.var.woff2') format('woff2');
}
@font-face {
font-family: 'Inter var';
font-weight: 100 900;
font-display: swap;
font-style: italic;
font-named-instance: 'Italic';
src: url('./fonts/Inter-italic-latin.var.woff2') format('woff2');
}
* {
box-sizing: border-box;
}
html,
body {
padding: 0;
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}

View File

@ -0,0 +1,145 @@
// @ts-check
const defaultTheme = require('tailwindcss/defaultTheme');
const neutral = {
100: '#f4f4f5',
200: '#e4e4e7',
300: '#d4d4d8',
400: '#a1a1aa',
50: '#fafafa',
500: '#71717a',
600: '#52525b',
700: '#484851',
800: '#303036',
900: '#252429',
950: '#18181b',
};
const indigo = {
dark: '#7063f3',
darker: '#5133cf',
darkest: '#3a2888',
DEFAULT: '#948cf9',
light: '#bab5fd',
lighter: '#d6d5fe',
lightest: '#f3f0ff',
};
const green = {
dark: '#1a6a22',
darker: '#0c3310',
darkest: '#071c09',
DEFAULT: '#28a635',
light: '#39ea4a',
lighter: '#97ffa2',
lightest: '#ebffed',
};
const orange = {
dark: '#aa6729',
darker: '#3f260f',
darkest: '#221508',
DEFAULT: '#f8963c',
light: '#ffbf85',
lighter: '#ffe3ca',
lightest: '#fff9f3',
};
const red = {
dark: '#ad2a2a',
darker: '#551515',
darkest: '#2f0b0b',
DEFAULT: '#ff5353',
light: '#ff8d8d',
lighter: '#ffe1e1',
lightest: '#fff8f8',
};
const blue = {
dark: '#1d6379',
darker: '#0e303b',
darkest: '#081a20',
DEFAULT: '#2e9cbe',
light: '#47c5ee',
lighter: '#c0f0ff',
lightest: '#f1fcff',
};
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./app/**/*.{js,jsx,ts,tsx,md,mdx}',
'./src/**/*.{js,jsx,ts,tsx,md,mdx}',
'./questions/**/*.{js,jsx,ts,tsx,md,mdx}',
],
darkMode: ['class', '[data-mode="dark"]'],
plugins: [
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/container-queries'),
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
theme: {
extend: {
animation: {
marquee: 'marquee linear infinite',
marquee2: 'marquee2 linear infinite',
'slide-up': 'slide-up 0.3s ease-out',
},
boxShadow: {
glow: '0 0 4px rgb(0 0 0 / 0.1)',
},
colors: {
blue,
brand: indigo,
danger: red,
gray: neutral,
green,
indigo,
info: blue,
neutral,
orange,
red,
success: green,
warning: orange,
},
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans],
},
fontSize: {
'2xs': ['0.625rem', { lineHeight: '0.875rem' }],
},
keyframes: {
marquee: {
'0%': { transform: 'translateX(0%)' },
'100%': { transform: 'translateX(-100%)' },
},
marquee2: {
'0%': { transform: 'translateX(100%)' },
'100%': { transform: 'translateX(0%)' },
},
'slide-up': {
'0%': { transform: 'translateY(100%)' },
'100%': { transform: 'translateY(0%)' },
},
},
letterSpacing: {
1: '.015625em',
2: '.03125em',
3: '.046875em',
4: '.0625em',
},
opacity: {
1: '0.01',
15: '0.15',
2.5: '0.025',
7.5: '0.075',
},
zIndex: {
// Modified from: https://github.com/twbs/bootstrap/blob/main/scss/_variables.scss#L1133
fixed: 30,
},
},
},
};

18
apps/admin/tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@gfe/typescript-config/nextjs.json",
"compilerOptions": {
"plugins": [
{
"name": "next"
}
],
"baseUrl": "./src",
"paths": {
"~/*": ["*"],
"~/prisma/client": ["../prisma/generated/client"]
},
"strictNullChecks": true
},
"include": ["next-env.d.ts", "src", ".next/types/**/*.ts", "types.d.ts"]
}

11
apps/admin/types.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import type { DefaultUser } from 'next-auth';
declare module 'next-auth' {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface Session {
user: DefaultUser & {
id: string;
isAdmin?: boolean;
};
}
}

11
apps/admin/vercel.json Normal file
View File

@ -0,0 +1,11 @@
{
"github": {
"silent": true
},
"crons": [
{
"path": "/api/reddit/crawl",
"schedule": "0 */3 * * *"
}
]
}

View File

@ -5,7 +5,8 @@
"private": true,
"exports": {
".": "./index.js",
"./next": "./next.js"
"./next": "./next.js",
"./react": "./react.js"
},
"devDependencies": {
"@eslint/js": "^9.26.0",

File diff suppressed because it is too large Load Diff