[feat] setup supabase + notice and cfgx fetching

This commit is contained in:
Purp1e
2025-03-15 04:18:57 +08:00
parent 815cdb55f1
commit 3c5a354db1
14 changed files with 280 additions and 10 deletions

View File

@@ -49,6 +49,8 @@
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/prefer-nullish-coalescing": "off",
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-vars": "warn",
"jsx-a11y/click-events-have-key-events": "off"
}
}

3
.gitignore vendored
View File

@@ -36,3 +36,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
.env
.env.*

BIN
bun.lockb

Binary file not shown.

View File

@@ -12,14 +12,15 @@
"tauri": "tauri",
"build": "tauri build",
"dev": "tauri dev",
"lint": "next lint && biome check src/",
"fix": "next lint --fix && biome check src/ --write",
"lint": "next lint",
"fix": "next lint --fix",
"prepare": "husky"
},
"dependencies": {
"@heroui/react": "^2.7.5",
"@icon-park/react": "^1.4.2",
"@reactuses/core": "6.0.1",
"@supabase/ssr": "^0.5.2",
"@tauri-apps/api": "2.1.0",
"@tauri-apps/plugin-clipboard-manager": "2.0.0",
"@tauri-apps/plugin-dialog": "~2.2.0",
@@ -38,6 +39,7 @@
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"swr": "^2.3.3",
"tauri-plugin-system-info-api": "^2.0.9",
"tauri-plugin-valtio": "1.1.1",
"throttle-debounce": "^5.0.2",

View File

@@ -0,0 +1,92 @@
"use client"
import { createClient } from "@/utils/supabase/client"
import { Chip, Code, Skeleton } from "@heroui/react"
import useSWR from "swr"
export default function Page() {
return <CfgxList />
}
function CfgxList() {
const noticeFetcher = async () => {
const supabase = createClient()
const { data /* , error */ } = await supabase
.from("CFGX")
.select(
"created_at, updated_at, cfgx_version, title, description, version, author, content, alias_list, key_list, value_list",
)
.order("created_at", { ascending: false })
return data as CfgxCardProps[]
}
const { data: cfgx /* , error */, isLoading } = useSWR<CfgxCardProps[]>(
"/api/cfgx",
noticeFetcher,
)
if (isLoading)
return [1, 2, 3].map((id) => (
<Skeleton className="mb-3 rounded-lg" key={id}>
<div className="h-16" />
</Skeleton>
))
return (
<ul className="flex flex-col gap-3">
{cfgx?.map((cfgx) => (
<CfgxCard key={cfgx.title} {...cfgx} />
))}
</ul>
)
}
interface CfgxCardProps {
created_at: string
updated_at: string
cfgx_version: string
title: string
description: string
version: string
author: string
content: string
alias_list?: {
id: string
info: string
value: string
}
key_list?: {
id: string
info: string
value: string
}
value_list?: {
id: string
info: string
value: string
}
}
function CfgxCard(props: CfgxCardProps) {
return (
<li className="flex flex-col gap-2 p-4 rounded-md bg-zinc-50 ">
<span className="flex items-center gap-3">
<h3 className="text-xl font-semibold">{props.title}</h3>
<Chip size="sm" className="bg-zinc-200">
{props.version}
</Chip>
<Chip size="sm" className="bg-zinc-200">
{props.author}
</Chip>
</span>
<p className="text-zinc-600">{props.description}</p>
<Code className="p-3 whitespace-pre-line">{props.content}</Code>
{props.alias_list && (
<p className="text-zinc-600">
{props.alias_list.id} {props.alias_list.info} {props.alias_list.value}
</p>
)}
</li>
)
}

View File

@@ -0,0 +1,52 @@
"use client"
import { Card, CardBody, CardHeader, CardIcon } from "@/components/window/Card"
import { cn } from "@heroui/react"
import { SettingConfig } from "@icon-park/react"
import { usePathname, useRouter } from "next/navigation"
export default function PreferenceLayout({
children,
}: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
return (
<Card className="h-full max-w-full overflow-y-scroll">
<CardHeader>
<CardIcon
type="menu"
onClick={() => router.push("/console/cfgx")}
className={cn(pathname === "/console/cfgx" && "bg-black/5")}
>
<SettingConfig /> CFGX
</CardIcon>
{/* <CardIcon
type="menu"
onClick={() => router.push("/console/path")}
className={cn(pathname === "/console/path" && "bg-black/5")}
>
<AssemblyLine /> 路径
</CardIcon>
<CardIcon
type="menu"
onClick={() => router.push("/console/replay")}
className={cn(pathname === "/console/replay" && "bg-black/5")}
>
<Videocamera /> 录像
</CardIcon> */}
{/* <CardTool>
<ToolButton>
<UploadOne />
云同步
</ToolButton>
<ToolButton>
<HardDisk />
保存
</ToolButton>
</CardTool> */}
</CardHeader>
<CardBody>{children}</CardBody>
</Card>
)
}

View File

@@ -1,4 +1,6 @@
"use client"
import { redirect } from "next/navigation"
export default function Page() {
return <div>Console</div>
redirect("/console/cfgx")
}

View File

@@ -1,7 +1,9 @@
"use client"
import Nav from "@/components/window/Nav"
export default function BaseLayout({ children }: { children: React.ReactNode }) {
export default function BaseLayout({
children,
}: { children: React.ReactNode }) {
return (
<div className="w-full h-full">
<Nav />

View File

@@ -13,7 +13,10 @@ export default function Providers({ children }: { children: React.ReactNode }) {
return (
<HeroUIProvider
className={cn("h-full bg-zinc-100/90 dark:bg-zinc-900", os === "macos" && "rounded-lg")}
className={cn(
"h-full bg-zinc-100/90 dark:bg-zinc-900",
os === "macos" && "rounded-lg",
)}
>
<NextThemesProvider attribute="class" defaultTheme="light">
<ToastProvider toastOffset={10} placement="top-center" />

View File

@@ -1,3 +1,4 @@
"use client"
import {
Card,
CardBody,
@@ -6,13 +7,15 @@ import {
CardTool,
} from "@/components/window/Card"
import { appStore } from "@/store/app"
import { createClient } from "@/utils/supabase/client"
import { Skeleton } from "@heroui/react"
import { Refresh, VolumeNotice } from "@icon-park/react"
import useSWR, { useSWRConfig } from "swr"
import { useSnapshot } from "valtio"
import { ToolButton } from "../window/ToolButton"
const Notice = () => {
void appStore.start()
const app = useSnapshot(appStore.state)
const { mutate } = useSWRConfig()
return (
<Card>
@@ -21,17 +24,54 @@ const Notice = () => {
<VolumeNotice />
</CardIcon>
<CardTool>
<ToolButton>
<ToolButton onClick={() => mutate("/api/notice")}>
<Refresh />
</ToolButton>
</CardTool>
</CardHeader>
<CardBody>
{app.notice || "不会真的有人要更新CSGO工具箱吧不会吧不会吧 xswl"}
<NoticeBody />
</CardBody>
</Card>
)
}
const NoticeBody = () => {
void appStore.start()
const app = useSnapshot(appStore.state)
const noticeFetcher = async () => {
const supabase = createClient()
const { data /* , error */ } = await supabase
.from("Notice")
.select("created_at, content, url")
.order("created_at", { ascending: false })
.single()
return data
}
const { data: notice /* , error */, isLoading } = useSWR(
"/api/notice",
noticeFetcher,
)
// if (error) return <>错误:{error}</>
if (isLoading)
return (
<Skeleton className="rounded-lg">
<div className="h-6" />
</Skeleton>
)
return (
<>
{notice?.content ||
app.notice ||
"不会真的有人要更新CSGO工具箱吧不会吧不会吧 xswl"}
</>
)
}
export default Notice

View File

@@ -11,7 +11,7 @@ interface CardProps {
const Card = ({ children }: CardProps) => {
return (
<div
className="dark:bg-white/[3%] dark:border-white/[6%] px-3 pt-3 pb-4 flex flex-col gap-2.5 border w-full rounded-lg bg-white/40 border-black/[6%]"
className="dark:bg-white/[3%] dark:border-white/[6%] px-3 py-3 flex flex-col gap-2.5 border w-full rounded-lg bg-white/40 border-black/[6%]"
data-tauri-drag-region
>
{children}

View File

@@ -0,0 +1,7 @@
import { createBrowserClient } from "@supabase/ssr"
export const createClient = () =>
createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
)

View File

@@ -0,0 +1,36 @@
import { createServerClient } from "@supabase/ssr"
import { type NextRequest, NextResponse } from "next/server"
export const createClient = (request: NextRequest) => {
// Create an unmodified response
let supabaseResponse = NextResponse.next({
request: {
headers: request.headers,
},
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value /* , options */ }) =>
request.cookies.set(name, value),
)
supabaseResponse = NextResponse.next({
request,
})
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options),
)
},
},
},
)
return supabaseResponse
}

View File

@@ -0,0 +1,29 @@
import { createServerClient } from "@supabase/ssr"
import type { cookies } from "next/headers"
export const createClient = (cookieStore: ReturnType<typeof cookies>) => {
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
// @ts-ignore
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
// @ts-ignore
cookieStore.set(name, value, options),
)
} catch {
// The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
},
)
}