feat: add user settings, sidebar hiding options, and quick links card
This commit is contained in:
parent
f659452169
commit
6f341bbd7a
@ -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**
|
||||
|
||||
|
@ -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 (
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<SideMenu />
|
||||
@ -23,51 +93,45 @@ export default function Settings() {
|
||||
<h1 className="text-3xl font-bold mb-6 text-foreground">Settings</h1>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<ChangePassword />
|
||||
{/* DISABLED FOR NOW
|
||||
<Card className="p-6">
|
||||
<h2 className="text-xl font-semibold mb-4">UI Settings</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="hide-ai">Hide Generative AI</Label>
|
||||
<Switch id="hide-ai" />
|
||||
<Switch
|
||||
id="hide-ai"
|
||||
checked={settings.hideGenAI}
|
||||
disabled={loading}
|
||||
onCheckedChange={(checked) => updateSetting('hideGenAI', checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="hide-upgrades">Hide all upgrades/roles</Label>
|
||||
<Switch id="hide-upgrades" />
|
||||
<Switch
|
||||
id="hide-upgrades"
|
||||
checked={settings.hideUpgrades}
|
||||
disabled={loading}
|
||||
onCheckedChange={(checked) => updateSetting('hideUpgrades', checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="hide-crypto">Hide crypto exchange</Label>
|
||||
<Switch id="hide-crypto" />
|
||||
<Switch
|
||||
id="hide-crypto"
|
||||
checked={settings.hideCrypto}
|
||||
disabled={loading}
|
||||
onCheckedChange={(checked) => updateSetting('hideCrypto', checked)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<h2 className="text-xl font-semibold mb-4">Notifications</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="hide-ai">Enable Notification System</Label>
|
||||
<Switch id="hide-ai" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="hide-upgrades">Browser Notifications (coming soon)</Label>
|
||||
<Switch id="hide-upgrades" disabled />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="hide-crypto">Hide crypto exchange</Label>
|
||||
<Switch id="hide-crypto" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
*/}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 })
|
||||
|
@ -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}`, {
|
||||
|
75
app/api/users/settings/route.ts
Normal file
75
app/api/users/settings/route.ts
Normal file
@ -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 });
|
||||
}
|
||||
}
|
@ -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<string | null>(null);
|
||||
const [gitStatus, setGitStatus] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isAdmin, setIsAdmin] = useState(false)
|
||||
const [error, setError] = useState<string | null>(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 = () => {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
39
components/cards/dashboard/overview/QuickLinks.tsx
Normal file
39
components/cards/dashboard/overview/QuickLinks.tsx
Normal file
@ -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 (
|
||||
<Card className="col-span-full md:col-span-1">
|
||||
<CardHeader>
|
||||
<CardTitle>Quick Links</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Link
|
||||
href="mailto:support@librecloud.cc"
|
||||
>
|
||||
<Button
|
||||
className="w-full mb-2"
|
||||
>
|
||||
<Headset />
|
||||
Support
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
href="https://mail.librecloud.cc"
|
||||
>
|
||||
<Button
|
||||
className="w-full mb-2"
|
||||
>
|
||||
<Mail />
|
||||
Webmail
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
@ -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 = () => (
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<WelcomeCard />
|
||||
{/* TODO: Disabled for later - <DiskUsage />*/}
|
||||
<LinkedAccounts />
|
||||
<QuickLinks />
|
||||
</div>
|
||||
)
|
||||
|
||||
|
@ -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 = () => (
|
||||
<div className="fixed left-0 top-16 h-[calc(100vh-4rem)] w-64 border-r bg-background z-10 hidden lg:block">
|
||||
<Sidebar className="h-full pt-16">
|
||||
<SidebarContent className="h-full bg-background">
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Services</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{workspaceGroupSidebarItems.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
return (
|
||||
<div className="fixed left-0 top-16 h-[calc(100vh-4rem)] w-64 border-r bg-background z-10 hidden lg:block">
|
||||
<Sidebar className="h-full pt-16">
|
||||
<SidebarContent className="h-full bg-background">
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Services</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
<Link href="/account/dashboard">
|
||||
<LayoutDashboard />
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Tools</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{toolsGroupSidebarItems.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Account</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{accountGroupSidebarItems.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<LogoutMenuItem />
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
</div>
|
||||
)
|
||||
|
||||
{isLoading ? (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuSkeleton showIcon />
|
||||
</SidebarMenuItem>
|
||||
) : (
|
||||
!hideGenAI && (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href="/account/dashboard/ai">
|
||||
<Sparkle />
|
||||
<span>Generative AI</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
)}
|
||||
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href="/account/dashboard/downloads">
|
||||
<HardDriveDownload />
|
||||
<span>Download Center</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Tools</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{isLoading ? (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuSkeleton showIcon />
|
||||
</SidebarMenuItem>
|
||||
) : (
|
||||
!hideCrypto && (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href="/account/dashboard/exchange">
|
||||
<Bitcoin />
|
||||
<span>Exchange Crypto</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
)}
|
||||
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href="/account/dashboard/statistics">
|
||||
<BarChartIcon />
|
||||
<span>Statistics</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Account</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{isLoading ? (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuSkeleton showIcon />
|
||||
</SidebarMenuItem>
|
||||
) : (
|
||||
!hideUpgrades && (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href="/account/dashboard/upgrades">
|
||||
<Crown />
|
||||
<span>Upgrades</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
)}
|
||||
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href="mailto:support@librecloud.cc">
|
||||
<Headset />
|
||||
<span>Support</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href="/account/dashboard/settings">
|
||||
<Settings />
|
||||
<span>Settings</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
<LogoutMenuItem />
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user