docs:create .env.exmaple style(admin):change welcome back text, add tooltip for some button change, incrase navbar size

This commit is contained in:
2026-03-12 23:27:47 +07:00 Unverified
parent 5052b05ea2
commit 2e291d871a
8 changed files with 149 additions and 58 deletions
+8 -5
View File
@@ -6,6 +6,7 @@ import Avatar from "@/components/avatar";
import SearchBox, { type Filters, type ParsedQuery } from "@/components/search";
import { Button } from "@/components/ui/button";
import { type characters, element } from "@/lib/db/schema";
import { SimpleTooltip } from "@/components/tooltip";
/** Filters and their value providers */
const filters: Filters = {
@@ -46,11 +47,13 @@ export function CharManager({
className="bg-input sticky top-2"
onQueryChange={setQuery}
/>
<Button className="h-10 w-10" asChild>
<Link href="/admin/char/create">
<UserPlus />
</Link>
</Button>
<SimpleTooltip text="เพิ่มตัวละคร" side="right">
<Button className="h-10 w-10" asChild>
<Link href="/admin/char/create">
<UserPlus />
</Link>
</Button>
</SimpleTooltip>
</div>
<div className="flex flex-wrap gap-4 justify-center">
{filteredChars.map((char) => (
+82 -29
View File
@@ -4,45 +4,98 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { SidebarMenuButton } from "@/components/ui/sidebar";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
export function VersionCreateDialogForm() {
return <form>Todo</form>;
}
export function RandomWelcomeMessage({ list }: { list?: string[] }) {
const messages = list || [
"What are we cooking today?",
"I'm NOT an EMERGENCY FOOD!!",
"Why are you here?",
"The emergency food is ready sir.",
"Who left the artifact stats like that?",
"Don't forget to hydrate, boss.",
"Another day, another reroll.",
"Still reviewing artifacts? Respect.",
"Let's make these builds shine.",
"You came back… impressive.",
"Processing luck… 0.3% found.",
"Someone's gotta keep this event alive.",
"New submissions incoming!",
"Welcome back, brave reviewer.",
"Did you bring Mora this time?",
"Your approval power is unmatched.",
"Remember: critique kindly, roast fairly.",
"Time to bless—or curse—some artifacts.",
"Don't worry, no 5★ drama yet.",
"Admin mode: activated.",
];
export function RandomWelcomeMessage() {
const items = [
{
"header": "ความลับแห่งแรงจันทราผัน",
"message": "แรงจันทราผัน (Kuuvahki) ใน Nod-Krai มีคุณสมบัติเหมือนแรงแม่เหล็ก ซึ่งเป็นกุญแจสำคัญในการแก้ปริศนาโบราณ"
},
{
"header": "การล่าอันบ้าคลั่ง: Wild Hunt",
"message": "เมื่อเผชิญหน้ากับกองทัพ Wild Hunt การลด HP ให้เหลือ 0 เป็นเพียงจุดเริ่มต้นเท่านั้น คุณต้องลดค่า Max HP ของพวกมันให้เหลือ 0 ในขณะที่พวกมันติดสถานะ Grief-Stricken เพื่อกำจัดพวกมันอย่างถาวร "
},
{
"header": "ประกายจันทร์: Ascendant Gleam",
"message": "การจัดทีมที่มีตัวละครจาก Nod-Krai อย่างน้อย 2 ตัว จะเปิดใช้งาน Ascendant Gleam เพื่อให้ใช้ความสามรถตัวละครได้อย่างเต็มที่"
},
{
"header": "มรดกแห่งดวงจันทร์ทั้งสาม",
"message": "Nod-Krai เคยถูกเรียกว่า Nephilheim และถูกเชื่อว่าเป็นเศษเสี้ยวของดวงจันทร์ดวงที่สี่ซึ่งแตกสลายก่อนจะก่อตัวเสร็จสิ้น ทำให้ดินแดนแห่งนี้มีความเชื่อมโยงกับพลังจันทราอย่างลึกซึ้ง"
},
{
"header": "คำสาบานของผู้เฝ้าประภาคาร",
"message": "กลุ่ม Lightkeepers ยึดถือคติ 'ใช้เลือดและกระดูกเป็นเชื้อเพลิง' พวกเขาเฝ้าประภาคารที่ Piramida มานานกว่า 500 ปีเพื่อไม่ให้ความมืดจาก Abyss ล่มสลายดินแดนของมนุษย์"
},
{
"header": "อาวุธชำระล้าง Terpikeraunas",
"message": "Arrow of Terpikeraunas เป็นโบราณวัตถุจาก Hyperborea ที่สามารถเปลี่ยนพลังงานจาก Light Realm ให้เป็นเปลวไฟที่ใช้บริสุทธิ์พลังงานจาก Void Realm (Abyss) ได้"
},
{
"header": "วงล้อจันทรา: สิ่งมาก่อนวิชั่น",
"message": "ใน Nod-Krai ผู้คนอาจได้รับ 'Moon Wheel' (กงล้อจันทรา) ซึ่งเป็นอุปกรณ์ที่ช่วยให้สามารถควบคุมแรงจันทราผันเพื่อปกป้องตนเองในถิ่นทุรกันดาร"
},
{
"header": "การรุกรานของ Rerir",
"message": "Rerir หนึ่งในห้ามหาบาปแห่ง Khaenri'ah เป็นผู้อยู่เบื้องหลังการโจมตีของ Wild Hunt เขามีเป้าหมายในการรวบรวมชิ้นส่วนของดวงจันทร์เพื่อฟื้นฟูร่างกายที่แตกสลายของตนเอง "
},
{
"header": "เมืองสีทองที่ล่มสลาย Hyperborea",
"message": "Hyperborea ตั้งอยู่ทางตอนเหนือสุดของ Nod-Krai เมืองนี้ล่มสลายลงหลังโดน Celestial Nail ปักใส่ ปัจจุบันกลายเป็นซากปรักหักพังที่เต็มไปด้วยความลับของภาพสะท้อนแห่งสวรรค์ "
},
{
"header": "นวัตกรรมจาก Clink-Clank Krumkake",
"message": "ร้านงานฝีมือ Krumkake เป็นศูนย์กลางของการประดิษฐ์ใน Nod-Krai มีการผสมผสานเทคโนโลยีเฟืองจาก Fontaine และพลังงานคูวากิเพื่อสร้างอุปกรณ์สำรวจที่ไม่มีใครเหมือน "
},
{
"header": "ความเชื่อของลูกหลานจันทราน้ำค้างแข็ง",
"message": "ลูกหลานจันทราน้ำค้างแข็งได้เคารพศรัทธาต่อ 'เทพจันทรา' และได้ละทิ้งอารยธรรมทองคำยุคเก่า"
},
// {
// "header": "สายใยครอบครัวในดินแดน Elysium",
// "message": "Nod-Krai ได้รับฉายาว่า Elysium เนื่องจากเป็นที่รวมตัวของคนนอกกฎหมายและนักผจญภัยจากทั่ว Teyvat ธีมหลักของตัวละครที่นี่จึงมักเป็นเรื่องของ 'ครอบครัวที่เลือกเอง' (Found Family) "
// },
{
"header": "นกไนติงเกลของ Solovei",
"message": "ตราสัญลักษณ์ของผู้เฝ้าประภาคารคือ นกไนติงเกลคาบไส้ตะเกียงที่ลุกไหม้ ซึ่งมาจากสัญลักษณ์ประจำตระกูลของ Solovei ผู้ก่อตั้งองค์กรและ Starshyna คนแรก "
},
{
"header": "จันทราทั้ง 3",
"message": "ในอดีตของ Teyvat เคยมีดวงจันทร์ทั้ง 3 ได้แก่ จันทราน้ำค้างแข็ง จันทราสีรุ้ง และจันทรานิรันดร์"
},
{
"header": "Genshin impact",
"message": "Action RPG แบบ Open World สไตล์อนิเมะ ที่โดดเด่นด้วยระบบการต่อสู้โดยใช้พลังธาตุและการสำรวจโลกกว้างที่อิสระ"
}
];
const [message, setMessage] = useState("");
const [index, setIndex] = useState(-1);
// biome-ignore lint/correctness/useExhaustiveDependencies: false
useEffect(
() => setMessage(messages[Math.floor(Math.random() * messages.length)]),
[messages.length],
useEffect(() => {
setIndex(Math.floor(Math.random() * items.length));
}, [items.length]);
if (index === -1) return (
<div className="flex flex-col gap-0.5 mt-1 items-center">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-4 w-64" />
</div>
);
return message;
const item = items[index];
return (
<div className="flex flex-col gap-0.5 mt-1">
<div className="text-sm flex items-center justify-center">{item.header}</div>
<div className="opacity-60 text-xs ">{item.message}</div>
</div>
);
}
export function SidebarLink({
+15 -15
View File
@@ -105,17 +105,17 @@ function DropdownMenu({
type="button"
onClick={() => setOpenSub(isOpen ? null : item.name)}
className={cn(
"flex items-center gap-2 w-full px-2 py-1.5 rounded-md text-left",
"transition-colors text-xs",
"flex items-center gap-2 w-full px-2.5 py-2 rounded-md text-left",
"transition-colors text-[13px]",
active
? "bg-white/15 text-white"
: "text-white/60 hover:text-white hover:bg-white/10",
)}
>
<Icon size={14} name={item.name} className="shrink-0" />
<Icon size={16} name={item.name} className="shrink-0" />
<span className="flex-1">{item.name}</span>
<ChevronRight
size={12}
size={14}
className={cn(
"transition-transform",
isOpen && "rotate-90",
@@ -136,14 +136,14 @@ function DropdownMenu({
href={item.href!}
onClick={onClose}
className={cn(
"flex items-center gap-2 px-2 py-1.5 rounded-md",
"transition-colors text-xs",
"flex items-center gap-2 px-2.5 py-2 rounded-md",
"transition-colors text-[13px]",
active
? "bg-white/15 text-white"
: "text-white/60 hover:text-white hover:bg-white/10",
)}
>
<Icon size={14} name={item.name} className="shrink-0" />
<Icon size={16} name={item.name} className="shrink-0" />
<span>{item.name}</span>
</Link>
)}
@@ -262,7 +262,7 @@ export function AdminNavbar({
setScopeOverride(scopeOverride === "root" ? null : "root")
}
className={cn(
"flex items-center justify-center w-6 h-6 rounded transition-colors opacity-50 hover:opacity-100",
"flex items-center justify-center w-6 h-8 rounded transition-colors opacity-50 hover:opacity-100",
scopeOverride === "root" && "opacity-100 bg-white/15",
)}
>
@@ -273,18 +273,18 @@ export function AdminNavbar({
type="button"
onClick={() => setShowLabels((v) => !v)}
className={cn(
"flex items-center justify-center w-6 h-6 rounded transition-colors",
"flex items-center justify-center w-8 h-8 rounded transition-colors",
showLabels
? "bg-white/15 text-white"
: "text-white/40 hover:text-white/70",
)}
>
{showLabels ? <X size={13} /> : <AlignJustify size={13} />}
{showLabels ? <X size={16} /> : <AlignJustify size={16} />}
</button>
<div className="w-px h-4 bg-white/10 mx-0.5" />
<div className="w-px h-5 bg-white/10 mx-1" />
</div>
<div className="flex items-center gap-0.5 overflow-x-auto scrollbar-none flex-1 min-w-0">
<div className="flex items-center gap-1 overflow-x-auto scrollbar-none flex-1 min-w-0">
{(activeParent ? activeParent.sub! : items).map((item) => {
const Icon = item.icon;
const active = isActive(item, pathname);
@@ -293,9 +293,9 @@ export function AdminNavbar({
const buttonContent = (
<>
<Icon size={15} name={item.name} className="shrink-0" />
<Icon size={18} name={item.name} className="shrink-0" />
{showLabels && (
<span className="text-[10px] whitespace-nowrap leading-none">
<span className="text-[11px] whitespace-nowrap leading-none">
{item.name}
</span>
)}
@@ -303,7 +303,7 @@ export function AdminNavbar({
);
const buttonClass = cn(
"flex items-center gap-1 px-1.5 py-1 rounded transition-colors shrink-0",
"flex items-center gap-1.5 px-2.5 py-1.5 rounded transition-colors shrink-0",
active || isOpen
? "bg-white/15 text-white"
: "text-white/50 hover:text-white hover:bg-white/10",
+4 -4
View File
@@ -3,11 +3,11 @@ import { RandomWelcomeMessage } from "./client";
export default function AdminPage() {
return (
<div className="flex h-full w-full justify-center items-center">
<div className="flex flex-col">
<span className="opacity-80 text-4xl font-bold">ยินดีต้อนรับกลับ!</span>
<span>
<div className="flex flex-col items-center">
<span className="text-4xl font-bold no-translate">ยินดีต้อนรับกลับ!</span>
<div className="mt-1">
<RandomWelcomeMessage />
</span>
</div>
</div>
</div>
);
+1 -1
View File
@@ -1,5 +1,5 @@
import { Section } from "./page";
export function SettingsAccountSection() {
return <Section title="บัญชี">ask dreamgineer bruh</Section>;
return <Section title="บัญชี">ยังไม่พร้อมให้บริการ</Section>;
}