[feat] setup supabase + notice and cfgx fetching
This commit is contained in:
@@ -49,6 +49,8 @@
|
|||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/prefer-nullish-coalescing": "off",
|
"@typescript-eslint/prefer-nullish-coalescing": "off",
|
||||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
"@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"
|
"jsx-a11y/click-events-have-key-events": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -36,3 +36,6 @@ yarn-error.log*
|
|||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
@@ -12,14 +12,15 @@
|
|||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"build": "tauri build",
|
"build": "tauri build",
|
||||||
"dev": "tauri dev",
|
"dev": "tauri dev",
|
||||||
"lint": "next lint && biome check src/",
|
"lint": "next lint",
|
||||||
"fix": "next lint --fix && biome check src/ --write",
|
"fix": "next lint --fix",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroui/react": "^2.7.5",
|
"@heroui/react": "^2.7.5",
|
||||||
"@icon-park/react": "^1.4.2",
|
"@icon-park/react": "^1.4.2",
|
||||||
"@reactuses/core": "6.0.1",
|
"@reactuses/core": "6.0.1",
|
||||||
|
"@supabase/ssr": "^0.5.2",
|
||||||
"@tauri-apps/api": "2.1.0",
|
"@tauri-apps/api": "2.1.0",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "2.0.0",
|
"@tauri-apps/plugin-clipboard-manager": "2.0.0",
|
||||||
"@tauri-apps/plugin-dialog": "~2.2.0",
|
"@tauri-apps/plugin-dialog": "~2.2.0",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"swr": "^2.3.3",
|
||||||
"tauri-plugin-system-info-api": "^2.0.9",
|
"tauri-plugin-system-info-api": "^2.0.9",
|
||||||
"tauri-plugin-valtio": "1.1.1",
|
"tauri-plugin-valtio": "1.1.1",
|
||||||
"throttle-debounce": "^5.0.2",
|
"throttle-debounce": "^5.0.2",
|
||||||
|
|||||||
92
src/app/(main)/console/cfgx/page.tsx
Normal file
92
src/app/(main)/console/cfgx/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
52
src/app/(main)/console/layout.tsx
Normal file
52
src/app/(main)/console/layout.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
import { redirect } from "next/navigation"
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return <div>Console</div>
|
redirect("/console/cfgx")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import Nav from "@/components/window/Nav"
|
import Nav from "@/components/window/Nav"
|
||||||
|
|
||||||
export default function BaseLayout({ children }: { children: React.ReactNode }) {
|
export default function BaseLayout({
|
||||||
|
children,
|
||||||
|
}: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full">
|
<div className="w-full h-full">
|
||||||
<Nav />
|
<Nav />
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ export default function Providers({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HeroUIProvider
|
<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">
|
<NextThemesProvider attribute="class" defaultTheme="light">
|
||||||
<ToastProvider toastOffset={10} placement="top-center" />
|
<ToastProvider toastOffset={10} placement="top-center" />
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
"use client"
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
@@ -6,13 +7,15 @@ import {
|
|||||||
CardTool,
|
CardTool,
|
||||||
} from "@/components/window/Card"
|
} from "@/components/window/Card"
|
||||||
import { appStore } from "@/store/app"
|
import { appStore } from "@/store/app"
|
||||||
|
import { createClient } from "@/utils/supabase/client"
|
||||||
|
import { Skeleton } from "@heroui/react"
|
||||||
import { Refresh, VolumeNotice } from "@icon-park/react"
|
import { Refresh, VolumeNotice } from "@icon-park/react"
|
||||||
|
import useSWR, { useSWRConfig } from "swr"
|
||||||
import { useSnapshot } from "valtio"
|
import { useSnapshot } from "valtio"
|
||||||
import { ToolButton } from "../window/ToolButton"
|
import { ToolButton } from "../window/ToolButton"
|
||||||
|
|
||||||
const Notice = () => {
|
const Notice = () => {
|
||||||
void appStore.start()
|
const { mutate } = useSWRConfig()
|
||||||
const app = useSnapshot(appStore.state)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -21,17 +24,54 @@ const Notice = () => {
|
|||||||
<VolumeNotice /> 公告
|
<VolumeNotice /> 公告
|
||||||
</CardIcon>
|
</CardIcon>
|
||||||
<CardTool>
|
<CardTool>
|
||||||
<ToolButton>
|
<ToolButton onClick={() => mutate("/api/notice")}>
|
||||||
<Refresh />
|
<Refresh />
|
||||||
刷新
|
刷新
|
||||||
</ToolButton>
|
</ToolButton>
|
||||||
</CardTool>
|
</CardTool>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
{app.notice || "不会真的有人要更新CSGO工具箱吧,不会吧不会吧 xswl"}
|
<NoticeBody />
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</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
|
export default Notice
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface CardProps {
|
|||||||
const Card = ({ children }: CardProps) => {
|
const Card = ({ children }: CardProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<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
|
data-tauri-drag-region
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
7
src/utils/supabase/client.ts
Normal file
7
src/utils/supabase/client.ts
Normal 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!,
|
||||||
|
)
|
||||||
36
src/utils/supabase/middleware.ts
Normal file
36
src/utils/supabase/middleware.ts
Normal 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
|
||||||
|
}
|
||||||
29
src/utils/supabase/server.ts
Normal file
29
src/utils/supabase/server.ts
Normal 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.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user