diff --git a/README.md b/README.md
index b03d172..e54a01b 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ A Docker setup requires both Docker *and* Docker Compose.
Following the environment variables section of this README, update your newly created `.env.local` file with your configuration.
-5. **Initialize Prisma (optional)**
+5. **Initialize Prisma**
Because `web` uses a database for storing Git link statuses (and other things to come),
you will need to initialize the SQLite database.
@@ -59,6 +59,7 @@ A Docker setup requires both Docker *and* Docker Compose.
```bash
bunx prisma migrate dev --name init
+ ```
6. **Bring the container up**
diff --git a/app/account/dashboard/settings/page.tsx b/app/account/dashboard/settings/page.tsx
index 215ceb0..d9dd96b 100644
--- a/app/account/dashboard/settings/page.tsx
+++ b/app/account/dashboard/settings/page.tsx
@@ -2,10 +2,11 @@
import { motion } from "framer-motion"
import { SideMenu } from "@/components/pages/dashboard/SideMenu"
-//import { Switch } from "@/components/ui/switch"
-//import { Label } from "@/components/ui/label"
-//import { Card } from "@/components/ui/card"
+import { Switch } from "@/components/ui/switch"
+import { Label } from "@/components/ui/label"
+import { Card } from "@/components/ui/card"
import { ChangePassword } from "@/components/cards/dashboard/Settings/ChangePassword"
+import { useState, useEffect } from "react";
const fadeIn = {
initial: { opacity: 0, y: 20 },
@@ -14,6 +15,75 @@ const fadeIn = {
}
export default function Settings() {
+ const [settings, setSettings] = useState({
+ hideGenAI: false,
+ hideUpgrades: false,
+ hideCrypto: false
+ });
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const fetchSettings = async () => {
+ try {
+ setLoading(true);
+ const response = await fetch('/api/users/settings');
+ if (response.ok) {
+ const data = await response.json();
+ setSettings(data);
+ } else {
+ console.error('[!] Failed to fetch settings');
+ }
+ } catch (error) {
+ console.error('[!] Error fetching settings:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchSettings();
+ }, []);
+
+ const updateSetting = async (settingName: string, value: boolean) => {
+ setSettings(prev => ({
+ ...prev,
+ [settingName]: value
+ }));
+
+ try {
+ setLoading(true);
+ const response = await fetch('/api/users/settings', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ ...settings,
+ [settingName]: value
+ }),
+ });
+
+ if (response.ok) {
+ const updatedSettings = await response.json();
+ setSettings(updatedSettings);
+ } else {
+ console.error('[!] Failed to update settings');
+ setSettings(prev => ({
+ ...prev,
+ [settingName]: !value
+ }));
+ }
+ } catch (error) {
+ console.error('[!] Error updating settings:', error);
+ setSettings(prev => ({
+ ...prev,
+ [settingName]: !value
+ }));
+ } finally {
+ setLoading(false);
+ window.location.reload();
+ }
+ };
+
return (
@@ -23,51 +93,45 @@ export default function Settings() {
Settings
- {/* DISABLED FOR NOW
UI Settings
-
+ updateSetting('hideGenAI', checked)}
+ />
-
+ updateSetting('hideUpgrades', checked)}
+ />
-
+ updateSetting('hideCrypto', checked)}
+ />
-
-
- Notifications
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- */}
)
-}
\ No newline at end of file
+}
+
diff --git a/app/api/git/link/route.ts b/app/api/git/link/route.ts
index 25417e3..8387854 100644
--- a/app/api/git/link/route.ts
+++ b/app/api/git/link/route.ts
@@ -34,18 +34,27 @@ export async function POST(request: Request) {
}
const dbUsrCheck = await prisma.user.findUnique({
- where: {
- email,
- },
+ where: { email },
})
if (dbUsrCheck) {
- return NextResponse.json({ error: "Git account already linked" }, { status: 409 })
+ if (dbUsrCheck.username) {
+ return NextResponse.json({ error: "Git account already linked" }, { status: 409 })
+ } else {
+ await prisma.user.update({
+ where: { email },
+ data: { username },
+ })
+ return NextResponse.json({ success: true })
+ }
} else {
await prisma.user.create({
data: {
email,
username,
+ hideGenAI: false,
+ hideUpgrades: false,
+ hideCrypto: false,
},
})
return NextResponse.json({ success: true })
diff --git a/app/api/git/user/route.ts b/app/api/git/user/route.ts
index af901b6..8cc1bbd 100644
--- a/app/api/git/user/route.ts
+++ b/app/api/git/user/route.ts
@@ -18,6 +18,8 @@ export async function GET() {
if (!dbUser) {
return NextResponse.json({ message: "User not found in database" })
+ } else if (dbUser.username === null) {
+ return NextResponse.json({ error: "Git account not linked", dismissErr: true }, { status: 404 })
}
const response = await fetch(`${process.env.GITEA_API_URL}/users/${dbUser.username}`, {
diff --git a/app/api/users/settings/route.ts b/app/api/users/settings/route.ts
new file mode 100644
index 0000000..09b0757
--- /dev/null
+++ b/app/api/users/settings/route.ts
@@ -0,0 +1,75 @@
+import { type NextRequest, NextResponse } from "next/server";
+import { auth } from "@/auth";
+import { prisma } from "@/lib/prisma";
+import * as z from "zod";
+
+const SettingsSchema = z.object({
+ hideGenAI: z.boolean(),
+ hideUpgrades: z.boolean(),
+ hideCrypto: z.boolean(),
+});
+
+export async function GET() {
+ const session = await auth();
+ if (!session || !session.user?.email) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+ }
+ const { email } = session.user;
+
+ const user = await prisma.user.findUnique({ where: { email } });
+ if (user) {
+ return NextResponse.json({
+ hideGenAI: user.hideGenAI,
+ hideUpgrades: user.hideUpgrades,
+ hideCrypto: user.hideCrypto,
+ });
+ } else {
+ const newUser = await prisma.user.create({ data: { email } });
+ return NextResponse.json({
+ hideGenAI: newUser.hideGenAI,
+ hideUpgrades: newUser.hideUpgrades,
+ hideCrypto: newUser.hideCrypto,
+ });
+ }
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const session = await auth();
+ if (!session || !session.user?.email) {
+ return NextResponse.json({ error: "Unauthorized - Please login first" }, { status: 401 });
+ }
+ const jsonData = await request.json();
+ const parsedSettings = SettingsSchema.safeParse(jsonData);
+ if (!parsedSettings.success) {
+ return NextResponse.json({ error: "Invalid settings" }, { status: 400 });
+ }
+ const { hideGenAI, hideUpgrades, hideCrypto } = parsedSettings.data;
+ const { email } = session.user;
+
+ const user = await prisma.user.findUnique({ where: { email } });
+ if (user) {
+ const updatedUser = await prisma.user.update({
+ where: { email },
+ data: { hideGenAI, hideUpgrades, hideCrypto },
+ });
+ return NextResponse.json({
+ hideGenAI: updatedUser.hideGenAI,
+ hideUpgrades: updatedUser.hideUpgrades,
+ hideCrypto: updatedUser.hideCrypto,
+ });
+ } else {
+ const newUser = await prisma.user.create({
+ data: { email, hideGenAI, hideUpgrades, hideCrypto },
+ });
+ return NextResponse.json({
+ hideGenAI: newUser.hideGenAI,
+ hideUpgrades: newUser.hideUpgrades,
+ hideCrypto: newUser.hideCrypto,
+ });
+ }
+ } catch (error) {
+ console.error("[!] Error processing settings update:", error);
+ return NextResponse.json({ error: "Failed to update settings" }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/components/cards/dashboard/overview/LinkedAccounts.tsx b/components/cards/dashboard/overview/LinkedAccounts.tsx
index c1157dd..d31bc60 100644
--- a/components/cards/dashboard/overview/LinkedAccounts.tsx
+++ b/components/cards/dashboard/overview/LinkedAccounts.tsx
@@ -4,10 +4,10 @@ import { Badge } from "@/components/ui/badge";
import { Loader2 } from "lucide-react";
export const LinkedAccounts = () => {
- const [gitStatus, setGitStatus] = useState(false);
- const [isLoading, setIsLoading] = useState(false);
- const [isAdmin, setIsAdmin] = useState(false);
- const [error, setError] = useState(null);
+ const [gitStatus, setGitStatus] = useState(false)
+ const [isLoading, setIsLoading] = useState(false)
+ const [isAdmin, setIsAdmin] = useState(false)
+ const [error, setError] = useState(null)
useEffect(() => {
const fetchGitStatus = async () => {
@@ -16,17 +16,20 @@ export const LinkedAccounts = () => {
const response = await fetch("/api/git/user")
const data = await response.json()
if (!response.ok) {
- if (data.error) {
+ if (data.error && !data.dismissErr) {
throw new Error(data.error)
- } else {
+ } else if (response.status !== 404) {
throw new Error(`HTTP error: ${response.status}`)
+ } else {
+ console.log(data.error)
+ }
+ } else {
+ if (data.is_admin) {
+ setIsAdmin(true)
+ }
+ if (!data.message) {
+ setGitStatus(true)
}
- }
- if (data.is_admin) {
- setIsAdmin(true)
- }
- if (!data.message) {
- setGitStatus(true)
}
} catch (err: unknown) {
if (err instanceof Error) {
@@ -81,5 +84,5 @@ export const LinkedAccounts = () => {
- );
-};
\ No newline at end of file
+ )
+}
\ No newline at end of file
diff --git a/components/cards/dashboard/overview/QuickLinks.tsx b/components/cards/dashboard/overview/QuickLinks.tsx
new file mode 100644
index 0000000..6e540cb
--- /dev/null
+++ b/components/cards/dashboard/overview/QuickLinks.tsx
@@ -0,0 +1,39 @@
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import {
+ Mail,
+ Headset
+} from "lucide-react"
+import Link from "next/link"
+
+export const QuickLinks = () => {
+ return (
+
+
+ Quick Links
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/components/pages/dashboard/OverviewTab.tsx b/components/pages/dashboard/OverviewTab.tsx
index 72688e4..cee8dd1 100644
--- a/components/pages/dashboard/OverviewTab.tsx
+++ b/components/pages/dashboard/OverviewTab.tsx
@@ -1,12 +1,14 @@
//import { DiskUsage } from "@/components/cards/dashboard/DiskUsage"
import { WelcomeCard } from "@/components/cards/dashboard/overview/WelcomeCard"
import { LinkedAccounts } from "@/components/cards/dashboard/overview/LinkedAccounts"
+import { QuickLinks } from "@/components/cards/dashboard/overview/QuickLinks"
export const OverviewTab = () => (
{/* TODO: Disabled for later - */}
+
)
diff --git a/components/pages/dashboard/SideMenu.tsx b/components/pages/dashboard/SideMenu.tsx
index 8844a49..b9fd157 100644
--- a/components/pages/dashboard/SideMenu.tsx
+++ b/components/pages/dashboard/SideMenu.tsx
@@ -1,116 +1,177 @@
-import { LayoutDashboard, Crown, Settings, Sparkle, HardDriveDownload, Bitcoin, Headset, ChartSpline } from "lucide-react"
-import { Sidebar, SidebarMenuButton, SidebarGroup, SidebarContent, SidebarMenu, SidebarGroupContent, SidebarGroupLabel, SidebarMenuItem } from "@/components/ui/sidebar"
+"use client"
+
+import {
+ LayoutDashboard,
+ Crown,
+ Settings,
+ Sparkle,
+ HardDriveDownload,
+ Bitcoin,
+ Headset,
+ BarChartIcon,
+} from "lucide-react"
+import {
+ Sidebar,
+ SidebarMenuButton,
+ SidebarGroup,
+ SidebarContent,
+ SidebarMenu,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+} from "@/components/ui/sidebar"
import LogoutMenuItem from "@/components/custom/LogoutMenuItem"
-import React from "react"
-import Link from "next/link";
+import type React from "react"
+import Link from "next/link"
+import { useState, useEffect } from "react"
-const workspaceGroupSidebarItems = [
- {
- title: "Dashboard",
- url: "/account/dashboard",
- icon: LayoutDashboard,
- },
- {
- title: "Generative AI",
- url: "/account/dashboard/ai",
- icon: Sparkle,
- },
- {
- title: "Download Center",
- url: "/account/dashboard/downloads",
- icon: HardDriveDownload,
- },
-]
+export const SideMenu: React.FC = () => {
+ const [hideGenAI, setHideGenAI] = useState(true)
+ const [hideUpgrades, setHideUpgrades] = useState(true)
+ const [hideCrypto, setHideCrypto] = useState(true)
+ const [isLoading, setIsLoading] = useState(true)
-const toolsGroupSidebarItems = [
- {
- title: "Exchange Crypto",
- url: "/account/dashboard/exchange",
- icon: Bitcoin,
- },
- {
- title: "Statistics",
- url: "/account/dashboard/statistics",
- icon: ChartSpline,
- },
-]
+ useEffect(() => {
+ fetch("/api/users/settings")
+ .then((res) => res.json())
+ .then((data) => {
+ setHideGenAI(data.hideGenAI)
+ setHideUpgrades(data.hideUpgrades)
+ setHideCrypto(data.hideCrypto)
+ setIsLoading(false)
+ })
+ .catch((error) => {
+ console.error("Failed to fetch user settings:", error)
+ setIsLoading(false)
+ })
+ }, [])
-const accountGroupSidebarItems = [
- {
- title: "Upgrades",
- url: "/account/dashboard/upgrades",
- icon: Crown,
- },
- {
- title: "Support",
- url: "https://t.me/nerdsorg_talk",
- icon: Headset,
- },
- {
- title: "Settings",
- url: "/account/dashboard/settings",
- icon: Settings,
- }
-]
-
-export const SideMenu: React.FC = () => (
-
-
-
-
- Services
-
-
- {workspaceGroupSidebarItems.map((item) => (
-
+ return (
+
+
+
+
+ Services
+
+
+
-
-
- {item.title}
+
+
+ Dashboard
- ))}
-
-
-
-
- Tools
-
-
- {toolsGroupSidebarItems.map((item) => (
-
-
-
-
- {item.title}
-
-
-
- ))}
-
-
-
-
- Account
-
-
- {accountGroupSidebarItems.map((item) => (
-
-
-
-
- {item.title}
-
-
-
- ))}
-
-
-
-
-
-
-
-)
+
+ {isLoading ? (
+
+
+
+ ) : (
+ !hideGenAI && (
+
+
+
+
+ Generative AI
+
+
+
+ )
+ )}
+
+
+
+
+
+ Download Center
+
+
+
+
+
+
+
+
+ Tools
+
+
+ {isLoading ? (
+
+
+
+ ) : (
+ !hideCrypto && (
+
+
+
+
+ Exchange Crypto
+
+
+
+ )
+ )}
+
+
+
+
+
+ Statistics
+
+
+
+
+
+
+
+
+ Account
+
+
+ {isLoading ? (
+
+
+
+ ) : (
+ !hideUpgrades && (
+
+
+
+
+ Upgrades
+
+
+
+ )
+ )}
+
+
+
+
+
+ Support
+
+
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index a9b76aa..4d70715 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -8,10 +8,12 @@ datasource db {
}
model User {
- id String @id @default(cuid())
- email String @unique
- username String @unique
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
+ id String @id @default(cuid())
+ email String @unique
+ username String? @unique
+ hideGenAI Boolean @default(false)
+ hideUpgrades Boolean @default(false)
+ hideCrypto Boolean @default(false)
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
}
-