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.
|
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),
|
Because `web` uses a database for storing Git link statuses (and other things to come),
|
||||||
you will need to initialize the SQLite database.
|
you will need to initialize the SQLite database.
|
||||||
@ -59,6 +59,7 @@ A Docker setup requires both Docker *and* Docker Compose.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
bunx prisma migrate dev --name init
|
bunx prisma migrate dev --name init
|
||||||
|
```
|
||||||
|
|
||||||
6. **Bring the container up**
|
6. **Bring the container up**
|
||||||
|
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
import { SideMenu } from "@/components/pages/dashboard/SideMenu"
|
import { SideMenu } from "@/components/pages/dashboard/SideMenu"
|
||||||
//import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
//import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
//import { Card } from "@/components/ui/card"
|
import { Card } from "@/components/ui/card"
|
||||||
import { ChangePassword } from "@/components/cards/dashboard/Settings/ChangePassword"
|
import { ChangePassword } from "@/components/cards/dashboard/Settings/ChangePassword"
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
const fadeIn = {
|
const fadeIn = {
|
||||||
initial: { opacity: 0, y: 20 },
|
initial: { opacity: 0, y: 20 },
|
||||||
@ -14,6 +15,75 @@ const fadeIn = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Settings() {
|
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 (
|
return (
|
||||||
<div className="flex flex-1 overflow-hidden">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
<SideMenu />
|
<SideMenu />
|
||||||
@ -23,47 +93,40 @@ export default function Settings() {
|
|||||||
<h1 className="text-3xl font-bold mb-6 text-foreground">Settings</h1>
|
<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">
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<ChangePassword />
|
<ChangePassword />
|
||||||
{/* DISABLED FOR NOW
|
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h2 className="text-xl font-semibold mb-4">UI Settings</h2>
|
<h2 className="text-xl font-semibold mb-4">UI Settings</h2>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label htmlFor="hide-ai">Hide Generative AI</Label>
|
<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>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label htmlFor="hide-upgrades">Hide all upgrades/roles</Label>
|
<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>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label htmlFor="hide-crypto">Hide crypto exchange</Label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@ -71,3 +134,4 @@ export default function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,18 +34,27 @@ export async function POST(request: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dbUsrCheck = await prisma.user.findUnique({
|
const dbUsrCheck = await prisma.user.findUnique({
|
||||||
where: {
|
where: { email },
|
||||||
email,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (dbUsrCheck) {
|
if (dbUsrCheck) {
|
||||||
|
if (dbUsrCheck.username) {
|
||||||
return NextResponse.json({ error: "Git account already linked" }, { status: 409 })
|
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 {
|
} else {
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
email,
|
email,
|
||||||
username,
|
username,
|
||||||
|
hideGenAI: false,
|
||||||
|
hideUpgrades: false,
|
||||||
|
hideCrypto: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return NextResponse.json({ success: true })
|
return NextResponse.json({ success: true })
|
||||||
|
@ -18,6 +18,8 @@ export async function GET() {
|
|||||||
|
|
||||||
if (!dbUser) {
|
if (!dbUser) {
|
||||||
return NextResponse.json({ message: "User not found in database" })
|
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}`, {
|
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";
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
export const LinkedAccounts = () => {
|
export const LinkedAccounts = () => {
|
||||||
const [gitStatus, setGitStatus] = useState(false);
|
const [gitStatus, setGitStatus] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [isAdmin, setIsAdmin] = useState(false);
|
const [isAdmin, setIsAdmin] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchGitStatus = async () => {
|
const fetchGitStatus = async () => {
|
||||||
@ -16,18 +16,21 @@ export const LinkedAccounts = () => {
|
|||||||
const response = await fetch("/api/git/user")
|
const response = await fetch("/api/git/user")
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (data.error) {
|
if (data.error && !data.dismissErr) {
|
||||||
throw new Error(data.error)
|
throw new Error(data.error)
|
||||||
} else {
|
} else if (response.status !== 404) {
|
||||||
throw new Error(`HTTP error: ${response.status}`)
|
throw new Error(`HTTP error: ${response.status}`)
|
||||||
|
} else {
|
||||||
|
console.log(data.error)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if (data.is_admin) {
|
if (data.is_admin) {
|
||||||
setIsAdmin(true)
|
setIsAdmin(true)
|
||||||
}
|
}
|
||||||
if (!data.message) {
|
if (!data.message) {
|
||||||
setGitStatus(true)
|
setGitStatus(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
setError(err.message)
|
setError(err.message)
|
||||||
@ -81,5 +84,5 @@ export const LinkedAccounts = () => {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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 { DiskUsage } from "@/components/cards/dashboard/DiskUsage"
|
||||||
import { WelcomeCard } from "@/components/cards/dashboard/overview/WelcomeCard"
|
import { WelcomeCard } from "@/components/cards/dashboard/overview/WelcomeCard"
|
||||||
import { LinkedAccounts } from "@/components/cards/dashboard/overview/LinkedAccounts"
|
import { LinkedAccounts } from "@/components/cards/dashboard/overview/LinkedAccounts"
|
||||||
|
import { QuickLinks } from "@/components/cards/dashboard/overview/QuickLinks"
|
||||||
|
|
||||||
export const OverviewTab = () => (
|
export const OverviewTab = () => (
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<WelcomeCard />
|
<WelcomeCard />
|
||||||
{/* TODO: Disabled for later - <DiskUsage />*/}
|
{/* TODO: Disabled for later - <DiskUsage />*/}
|
||||||
<LinkedAccounts />
|
<LinkedAccounts />
|
||||||
|
<QuickLinks />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,59 +1,53 @@
|
|||||||
import { LayoutDashboard, Crown, Settings, Sparkle, HardDriveDownload, Bitcoin, Headset, ChartSpline } from "lucide-react"
|
"use client"
|
||||||
import { Sidebar, SidebarMenuButton, SidebarGroup, SidebarContent, SidebarMenu, SidebarGroupContent, SidebarGroupLabel, SidebarMenuItem } from "@/components/ui/sidebar"
|
|
||||||
|
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 LogoutMenuItem from "@/components/custom/LogoutMenuItem"
|
||||||
import React from "react"
|
import type React from "react"
|
||||||
import Link from "next/link";
|
import Link from "next/link"
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
|
||||||
const workspaceGroupSidebarItems = [
|
export const SideMenu: React.FC = () => {
|
||||||
{
|
const [hideGenAI, setHideGenAI] = useState(true)
|
||||||
title: "Dashboard",
|
const [hideUpgrades, setHideUpgrades] = useState(true)
|
||||||
url: "/account/dashboard",
|
const [hideCrypto, setHideCrypto] = useState(true)
|
||||||
icon: LayoutDashboard,
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Generative AI",
|
|
||||||
url: "/account/dashboard/ai",
|
|
||||||
icon: Sparkle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Download Center",
|
|
||||||
url: "/account/dashboard/downloads",
|
|
||||||
icon: HardDriveDownload,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const toolsGroupSidebarItems = [
|
useEffect(() => {
|
||||||
{
|
fetch("/api/users/settings")
|
||||||
title: "Exchange Crypto",
|
.then((res) => res.json())
|
||||||
url: "/account/dashboard/exchange",
|
.then((data) => {
|
||||||
icon: Bitcoin,
|
setHideGenAI(data.hideGenAI)
|
||||||
},
|
setHideUpgrades(data.hideUpgrades)
|
||||||
{
|
setHideCrypto(data.hideCrypto)
|
||||||
title: "Statistics",
|
setIsLoading(false)
|
||||||
url: "/account/dashboard/statistics",
|
})
|
||||||
icon: ChartSpline,
|
.catch((error) => {
|
||||||
},
|
console.error("Failed to fetch user settings:", error)
|
||||||
]
|
setIsLoading(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
const accountGroupSidebarItems = [
|
return (
|
||||||
{
|
|
||||||
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">
|
<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">
|
<Sidebar className="h-full pt-16">
|
||||||
<SidebarContent className="h-full bg-background">
|
<SidebarContent className="h-full bg-background">
|
||||||
@ -61,50 +55,116 @@ export const SideMenu: React.FC = () => (
|
|||||||
<SidebarGroupLabel>Services</SidebarGroupLabel>
|
<SidebarGroupLabel>Services</SidebarGroupLabel>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{workspaceGroupSidebarItems.map((item) => (
|
<SidebarMenuItem>
|
||||||
<SidebarMenuItem key={item.title}>
|
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<Link href={item.url}>
|
<Link href="/account/dashboard">
|
||||||
<item.icon />
|
<LayoutDashboard />
|
||||||
<span>{item.title}</span>
|
<span>Dashboard</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
|
||||||
|
{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>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
|
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Tools</SidebarGroupLabel>
|
<SidebarGroupLabel>Tools</SidebarGroupLabel>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{toolsGroupSidebarItems.map((item) => (
|
{isLoading ? (
|
||||||
<SidebarMenuItem key={item.title}>
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuSkeleton showIcon />
|
||||||
|
</SidebarMenuItem>
|
||||||
|
) : (
|
||||||
|
!hideCrypto && (
|
||||||
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<Link href={item.url}>
|
<Link href="/account/dashboard/exchange">
|
||||||
<item.icon />
|
<Bitcoin />
|
||||||
<span>{item.title}</span>
|
<span>Exchange Crypto</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton asChild>
|
||||||
|
<Link href="/account/dashboard/statistics">
|
||||||
|
<BarChartIcon />
|
||||||
|
<span>Statistics</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
|
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Account</SidebarGroupLabel>
|
<SidebarGroupLabel>Account</SidebarGroupLabel>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{accountGroupSidebarItems.map((item) => (
|
{isLoading ? (
|
||||||
<SidebarMenuItem key={item.title}>
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuSkeleton showIcon />
|
||||||
|
</SidebarMenuItem>
|
||||||
|
) : (
|
||||||
|
!hideUpgrades && (
|
||||||
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<Link href={item.url}>
|
<Link href="/account/dashboard/upgrades">
|
||||||
<item.icon />
|
<Crown />
|
||||||
<span>{item.title}</span>
|
<span>Upgrades</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</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 />
|
<LogoutMenuItem />
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
@ -113,4 +173,5 @@ export const SideMenu: React.FC = () => (
|
|||||||
</Sidebar>
|
</Sidebar>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -10,8 +10,10 @@ datasource db {
|
|||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
email String @unique
|
email String @unique
|
||||||
username String @unique
|
username String? @unique
|
||||||
|
hideGenAI Boolean @default(false)
|
||||||
|
hideUpgrades Boolean @default(false)
|
||||||
|
hideCrypto Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user