-
+
-
+ Get started by editing{" "}
+
+ src/app/page.tsx ++ . +
+ - + Save and see your changes instantly. + +
diff --git a/apps/bot/prisma/migrations/20260722180000_phase6_webui/migration.sql b/apps/bot/prisma/migrations/20260722180000_phase6_webui/migration.sql new file mode 100644 index 0000000..6eb91de --- /dev/null +++ b/apps/bot/prisma/migrations/20260722180000_phase6_webui/migration.sql @@ -0,0 +1,46 @@ +-- AlterTable +ALTER TABLE "GuildSettings" ADD COLUMN "timezone" TEXT NOT NULL DEFAULT 'Europe/Berlin'; + +-- CreateTable +CREATE TABLE "DashboardAccessRule" ( + "id" TEXT NOT NULL, + "guildId" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + "modules" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "DashboardAccessRule_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "DashboardAuditLog" ( + "id" TEXT NOT NULL, + "guildId" TEXT, + "actorUserId" TEXT NOT NULL, + "action" TEXT NOT NULL, + "path" TEXT, + "before" JSONB, + "after" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "DashboardAuditLog_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "DashboardAccessRule_guildId_idx" ON "DashboardAccessRule"("guildId"); + +-- CreateIndex +CREATE UNIQUE INDEX "DashboardAccessRule_guildId_roleId_key" ON "DashboardAccessRule"("guildId", "roleId"); + +-- CreateIndex +CREATE INDEX "DashboardAuditLog_guildId_createdAt_idx" ON "DashboardAuditLog"("guildId", "createdAt"); + +-- CreateIndex +CREATE INDEX "DashboardAuditLog_actorUserId_createdAt_idx" ON "DashboardAuditLog"("actorUserId", "createdAt"); + +-- AddForeignKey +ALTER TABLE "DashboardAccessRule" ADD CONSTRAINT "DashboardAccessRule_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "DashboardAuditLog" ADD CONSTRAINT "DashboardAuditLog_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/bot/prisma/schema.prisma b/apps/bot/prisma/schema.prisma index 59df6aa..2411a9c 100644 --- a/apps/bot/prisma/schema.prisma +++ b/apps/bot/prisma/schema.prisma @@ -51,16 +51,47 @@ model Guild { socialFeeds SocialFeed[] scheduledMessages ScheduledMessage[] guildBackups GuildBackup[] + dashboardAccessRules DashboardAccessRule[] + dashboardAuditLogs DashboardAuditLog[] } model GuildSettings { id String @id @default(cuid()) guildId String @unique locale String @default("de") + timezone String @default("Europe/Berlin") moderationEnabled Boolean @default(true) guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) } +model DashboardAccessRule { + id String @id @default(cuid()) + guildId String + roleId String + modules Json + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + + @@unique([guildId, roleId]) + @@index([guildId]) +} + +model DashboardAuditLog { + id String @id @default(cuid()) + guildId String? + actorUserId String + action String + path String? + before Json? + after Json? + createdAt DateTime @default(now()) + guild Guild? @relation(fields: [guildId], references: [id], onDelete: Cascade) + + @@index([guildId, createdAt]) + @@index([actorUserId, createdAt]) +} + model User { id String @id createdAt DateTime @default(now()) diff --git a/apps/webui/.gitignore b/apps/webui/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/apps/webui/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/webui/README.md b/apps/webui/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/apps/webui/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## 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) - 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_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/apps/webui/eslint.config.mjs b/apps/webui/eslint.config.mjs new file mode 100644 index 0000000..719cea2 --- /dev/null +++ b/apps/webui/eslint.config.mjs @@ -0,0 +1,25 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), + { + ignores: [ + "node_modules/**", + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ], + }, +]; + +export default eslintConfig; diff --git a/apps/webui/next.config.ts b/apps/webui/next.config.ts new file mode 100644 index 0000000..1ca2391 --- /dev/null +++ b/apps/webui/next.config.ts @@ -0,0 +1,10 @@ +import path from 'path'; +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = { + output: 'standalone', + transpilePackages: ['@nexumi/shared'], + outputFileTracingRoot: path.join(__dirname, '../..') +}; + +export default nextConfig; diff --git a/apps/webui/package.json b/apps/webui/package.json new file mode 100644 index 0000000..8c80735 --- /dev/null +++ b/apps/webui/package.json @@ -0,0 +1,50 @@ +{ + "name": "@nexumi/webui", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint src --max-warnings=0", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "prisma:generate": "prisma generate --schema=../bot/prisma/schema.prisma" + }, + "dependencies": { + "@nexumi/shared": "workspace:^", + "@prisma/client": "^5.22.0", + "@radix-ui/react-avatar": "^1.2.3", + "@radix-ui/react-dropdown-menu": "^2.1.21", + "@radix-ui/react-label": "^2.1.12", + "@radix-ui/react-select": "^2.3.4", + "@radix-ui/react-separator": "^1.1.12", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-switch": "^1.3.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "dotenv": "^16.4.7", + "ioredis": "^5.4.2", + "lucide-react": "^1.25.0", + "next": "15.5.21", + "next-themes": "^0.4.6", + "react": "19.1.0", + "react-dom": "19.1.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.6.0", + "zod": "^3.24.1" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.5.21", + "prisma": "^5.22.0", + "tailwindcss": "^4", + "typescript": "^5", + "vitest": "^2.1.8" + } +} diff --git a/apps/webui/postcss.config.mjs b/apps/webui/postcss.config.mjs new file mode 100644 index 0000000..c7bcb4b --- /dev/null +++ b/apps/webui/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/apps/webui/public/file.svg b/apps/webui/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/apps/webui/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/webui/public/globe.svg b/apps/webui/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/apps/webui/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/webui/public/next.svg b/apps/webui/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/apps/webui/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/webui/public/vercel.svg b/apps/webui/public/vercel.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/apps/webui/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/webui/public/window.svg b/apps/webui/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/apps/webui/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/webui/src/app/favicon.ico b/apps/webui/src/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/apps/webui/src/app/favicon.ico differ diff --git a/apps/webui/src/app/globals.css b/apps/webui/src/app/globals.css new file mode 100644 index 0000000..a2dc41e --- /dev/null +++ b/apps/webui/src/app/globals.css @@ -0,0 +1,26 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/apps/webui/src/app/layout.tsx b/apps/webui/src/app/layout.tsx new file mode 100644 index 0000000..f7fa87e --- /dev/null +++ b/apps/webui/src/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + +
+ {children} + + + ); +} diff --git a/apps/webui/src/app/page.tsx b/apps/webui/src/app/page.tsx new file mode 100644 index 0000000..a932894 --- /dev/null +++ b/apps/webui/src/app/page.tsx @@ -0,0 +1,103 @@ +import Image from "next/image"; + +export default function Home() { + return ( +
+ src/app/page.tsx
+
+ .
+