docs:create .env.exmaple style(admin):change welcome back text, add tooltip for some button change, incrase navbar size
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
# Backend
|
||||||
|
DATABASE_URL=YOUR_DATABASE_URL
|
||||||
|
REDIS_URL=YOUR_REDIS_URL
|
||||||
|
BETTER_AUTH_SECRET=YOUR_BETTER_AUTH_SECRET
|
||||||
|
SENTRY_AUTH_TOKEN=YOURT_SENTRY_AUTH_TOKEN
|
||||||
|
|
||||||
|
# Channel target
|
||||||
|
YOUTUBE_CHANNEL_ID=YOUR_YOUTUBE_CHANNEL_ID
|
||||||
|
YOUTUBE_API_KEY=YOUR_YOUTUBE_API_KEY
|
||||||
|
|
||||||
|
# Base url
|
||||||
|
BASE_URL=YOUR_BASE_URL # example: https://buzz.sudloh.com
|
||||||
|
|
||||||
|
# Discord bot (Rubgram)
|
||||||
|
DISCORD_BOT_TOKEN=YOUR_DISCORD_BOT_TOKEN
|
||||||
|
DISCORD_GUILD_ID=YOUR_DISCORD_GUILD_ID
|
||||||
|
DISCORD_CLIENT_ID=YOUR_DISCORD_CLIENT_ID
|
||||||
|
DISCORD_CLIENT_SECRET=YOUR_DISCORD_CLIENT_SECRET
|
||||||
|
DISCORD_REDIRECT_URI=https://YOUR_BASE_URL/rubgram/callback
|
||||||
|
|
||||||
|
# Discord webhook (Rubgram)
|
||||||
|
DISCORD_WEBHOOK_URL=YOUR_DISCORD_WEBHOOK_URL # For Rubgram calling
|
||||||
|
WEBHOOK_RUBGRAM_SUBMIT=YOUR_WEBHOOK_RUBGRAM_SUBMIT # For Rubgram submit
|
||||||
|
|
||||||
|
# Payment (Rubgram) https://slipok.com/
|
||||||
|
SLIPOK_API_URL=YOUR_SLIPOK_API_URL
|
||||||
|
SLIPOK_API_KEY=YOUR_SLIPOK_API_KEY
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
ENVIRONMENT=production
|
||||||
+2
-1
@@ -31,7 +31,8 @@ yarn-error.log*
|
|||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env
|
||||||
|
.env.local
|
||||||
!dev/.env.development
|
!dev/.env.development
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Buzz Events
|
# Buzz Events
|
||||||
This is a Next.js project running multiple events for a YouTuber, Buzz. It have been branched into 5 different parts:
|
This is a Next.js project running multiple events for a YouTuber, Buzz. It have been branched into 5 different parts:
|
||||||
- Artifact: Monthly watcher-submitted ID review
|
- Artifact: Versionly watcher-submitted ID review.
|
||||||
- Tierlist: Versionly tierlist for best characters for Abyss & Stygian
|
- Rubgram: A paid event that basically lets Buzz play endgame content for you.
|
||||||
- Rubgram: A paid event that basically lets Buzz play Abyss/Imaginarium Theater/Stygian for you.
|
- Tierlist: Versionly tierlist rating characters for Spiral Abyss & Stygian Onslaught.
|
||||||
- `WIP` Admin: Admin panel where you can manage versions, characters, tierlists, external services, etc.
|
- `WIP` Admin: Admin panel where you can manage versions, characters, tierlists, external services, etc.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
@@ -17,6 +17,10 @@ This is a Next.js project running multiple events for a YouTuber, Buzz. It have
|
|||||||
- Authentication: DIscord OAuth2
|
- Authentication: DIscord OAuth2
|
||||||
- Client side cross components communication with debug menu
|
- Client side cross components communication with debug menu
|
||||||
|
|
||||||
|
## External APIs
|
||||||
|
- Character&Version data: [Project Amber](https://gi.yatta.moe/en)
|
||||||
|
- Character Card: [Enka.network](https://enka.network/)
|
||||||
|
|
||||||
## Developers
|
## Developers
|
||||||
Lead Developer: `@dmgnr`
|
Lead Developer: `@dmgnr`
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Avatar from "@/components/avatar";
|
|||||||
import SearchBox, { type Filters, type ParsedQuery } from "@/components/search";
|
import SearchBox, { type Filters, type ParsedQuery } from "@/components/search";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { type characters, element } from "@/lib/db/schema";
|
import { type characters, element } from "@/lib/db/schema";
|
||||||
|
import { SimpleTooltip } from "@/components/tooltip";
|
||||||
|
|
||||||
/** Filters and their value providers */
|
/** Filters and their value providers */
|
||||||
const filters: Filters = {
|
const filters: Filters = {
|
||||||
@@ -46,11 +47,13 @@ export function CharManager({
|
|||||||
className="bg-input sticky top-2"
|
className="bg-input sticky top-2"
|
||||||
onQueryChange={setQuery}
|
onQueryChange={setQuery}
|
||||||
/>
|
/>
|
||||||
<Button className="h-10 w-10" asChild>
|
<SimpleTooltip text="เพิ่มตัวละคร" side="right">
|
||||||
<Link href="/admin/char/create">
|
<Button className="h-10 w-10" asChild>
|
||||||
<UserPlus />
|
<Link href="/admin/char/create">
|
||||||
</Link>
|
<UserPlus />
|
||||||
</Button>
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</SimpleTooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-4 justify-center">
|
<div className="flex flex-wrap gap-4 justify-center">
|
||||||
{filteredChars.map((char) => (
|
{filteredChars.map((char) => (
|
||||||
|
|||||||
+82
-29
@@ -4,45 +4,98 @@ import Link from "next/link";
|
|||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { SidebarMenuButton } from "@/components/ui/sidebar";
|
import { SidebarMenuButton } from "@/components/ui/sidebar";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export function VersionCreateDialogForm() {
|
export function VersionCreateDialogForm() {
|
||||||
return <form>Todo</form>;
|
return <form>Todo</form>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RandomWelcomeMessage({ list }: { list?: string[] }) {
|
export function RandomWelcomeMessage() {
|
||||||
const messages = list || [
|
const items = [
|
||||||
"What are we cooking today?",
|
{
|
||||||
"I'm NOT an EMERGENCY FOOD!!",
|
"header": "ความลับแห่งแรงจันทราผัน",
|
||||||
"Why are you here?",
|
"message": "แรงจันทราผัน (Kuuvahki) ใน Nod-Krai มีคุณสมบัติเหมือนแรงแม่เหล็ก ซึ่งเป็นกุญแจสำคัญในการแก้ปริศนาโบราณ"
|
||||||
"The emergency food is ready sir.",
|
},
|
||||||
"Who left the artifact stats like that?",
|
{
|
||||||
"Don't forget to hydrate, boss.",
|
"header": "การล่าอันบ้าคลั่ง: Wild Hunt",
|
||||||
"Another day, another reroll.",
|
"message": "เมื่อเผชิญหน้ากับกองทัพ Wild Hunt การลด HP ให้เหลือ 0 เป็นเพียงจุดเริ่มต้นเท่านั้น คุณต้องลดค่า Max HP ของพวกมันให้เหลือ 0 ในขณะที่พวกมันติดสถานะ Grief-Stricken เพื่อกำจัดพวกมันอย่างถาวร "
|
||||||
"Still reviewing artifacts? Respect.",
|
},
|
||||||
"Let's make these builds shine.",
|
{
|
||||||
"You came back… impressive.",
|
"header": "ประกายจันทร์: Ascendant Gleam",
|
||||||
"Processing luck… 0.3% found.",
|
"message": "การจัดทีมที่มีตัวละครจาก Nod-Krai อย่างน้อย 2 ตัว จะเปิดใช้งาน Ascendant Gleam เพื่อให้ใช้ความสามรถตัวละครได้อย่างเต็มที่"
|
||||||
"Someone's gotta keep this event alive.",
|
},
|
||||||
"New submissions incoming!",
|
{
|
||||||
"Welcome back, brave reviewer.",
|
"header": "มรดกแห่งดวงจันทร์ทั้งสาม",
|
||||||
"Did you bring Mora this time?",
|
"message": "Nod-Krai เคยถูกเรียกว่า Nephilheim และถูกเชื่อว่าเป็นเศษเสี้ยวของดวงจันทร์ดวงที่สี่ซึ่งแตกสลายก่อนจะก่อตัวเสร็จสิ้น ทำให้ดินแดนแห่งนี้มีความเชื่อมโยงกับพลังจันทราอย่างลึกซึ้ง"
|
||||||
"Your approval power is unmatched.",
|
},
|
||||||
"Remember: critique kindly, roast fairly.",
|
{
|
||||||
"Time to bless—or curse—some artifacts.",
|
"header": "คำสาบานของผู้เฝ้าประภาคาร",
|
||||||
"Don't worry, no 5★ drama yet.",
|
"message": "กลุ่ม Lightkeepers ยึดถือคติ 'ใช้เลือดและกระดูกเป็นเชื้อเพลิง' พวกเขาเฝ้าประภาคารที่ Piramida มานานกว่า 500 ปีเพื่อไม่ให้ความมืดจาก Abyss ล่มสลายดินแดนของมนุษย์"
|
||||||
"Admin mode: activated.",
|
},
|
||||||
];
|
{
|
||||||
|
"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(() => {
|
||||||
useEffect(
|
setIndex(Math.floor(Math.random() * items.length));
|
||||||
() => setMessage(messages[Math.floor(Math.random() * messages.length)]),
|
}, [items.length]);
|
||||||
[messages.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({
|
export function SidebarLink({
|
||||||
|
|||||||
+15
-15
@@ -105,17 +105,17 @@ function DropdownMenu({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => setOpenSub(isOpen ? null : item.name)}
|
onClick={() => setOpenSub(isOpen ? null : item.name)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 w-full px-2 py-1.5 rounded-md text-left",
|
"flex items-center gap-2 w-full px-2.5 py-2 rounded-md text-left",
|
||||||
"transition-colors text-xs",
|
"transition-colors text-[13px]",
|
||||||
active
|
active
|
||||||
? "bg-white/15 text-white"
|
? "bg-white/15 text-white"
|
||||||
: "text-white/60 hover:text-white hover:bg-white/10",
|
: "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>
|
<span className="flex-1">{item.name}</span>
|
||||||
<ChevronRight
|
<ChevronRight
|
||||||
size={12}
|
size={14}
|
||||||
className={cn(
|
className={cn(
|
||||||
"transition-transform",
|
"transition-transform",
|
||||||
isOpen && "rotate-90",
|
isOpen && "rotate-90",
|
||||||
@@ -136,14 +136,14 @@ function DropdownMenu({
|
|||||||
href={item.href!}
|
href={item.href!}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 px-2 py-1.5 rounded-md",
|
"flex items-center gap-2 px-2.5 py-2 rounded-md",
|
||||||
"transition-colors text-xs",
|
"transition-colors text-[13px]",
|
||||||
active
|
active
|
||||||
? "bg-white/15 text-white"
|
? "bg-white/15 text-white"
|
||||||
: "text-white/60 hover:text-white hover:bg-white/10",
|
: "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>
|
<span>{item.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
@@ -262,7 +262,7 @@ export function AdminNavbar({
|
|||||||
setScopeOverride(scopeOverride === "root" ? null : "root")
|
setScopeOverride(scopeOverride === "root" ? null : "root")
|
||||||
}
|
}
|
||||||
className={cn(
|
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",
|
scopeOverride === "root" && "opacity-100 bg-white/15",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -273,18 +273,18 @@ export function AdminNavbar({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowLabels((v) => !v)}
|
onClick={() => setShowLabels((v) => !v)}
|
||||||
className={cn(
|
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
|
showLabels
|
||||||
? "bg-white/15 text-white"
|
? "bg-white/15 text-white"
|
||||||
: "text-white/40 hover:text-white/70",
|
: "text-white/40 hover:text-white/70",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{showLabels ? <X size={13} /> : <AlignJustify size={13} />}
|
{showLabels ? <X size={16} /> : <AlignJustify size={16} />}
|
||||||
</button>
|
</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>
|
||||||
|
|
||||||
<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) => {
|
{(activeParent ? activeParent.sub! : items).map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
const active = isActive(item, pathname);
|
const active = isActive(item, pathname);
|
||||||
@@ -293,9 +293,9 @@ export function AdminNavbar({
|
|||||||
|
|
||||||
const buttonContent = (
|
const buttonContent = (
|
||||||
<>
|
<>
|
||||||
<Icon size={15} name={item.name} className="shrink-0" />
|
<Icon size={18} name={item.name} className="shrink-0" />
|
||||||
{showLabels && (
|
{showLabels && (
|
||||||
<span className="text-[10px] whitespace-nowrap leading-none">
|
<span className="text-[11px] whitespace-nowrap leading-none">
|
||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -303,7 +303,7 @@ export function AdminNavbar({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const buttonClass = cn(
|
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
|
active || isOpen
|
||||||
? "bg-white/15 text-white"
|
? "bg-white/15 text-white"
|
||||||
: "text-white/50 hover:text-white hover:bg-white/10",
|
: "text-white/50 hover:text-white hover:bg-white/10",
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { RandomWelcomeMessage } from "./client";
|
|||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full justify-center items-center">
|
<div className="flex h-full w-full justify-center items-center">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col items-center">
|
||||||
<span className="opacity-80 text-4xl font-bold">ยินดีต้อนรับกลับ!</span>
|
<span className="text-4xl font-bold no-translate">ยินดีต้อนรับกลับ!</span>
|
||||||
<span>
|
<div className="mt-1">
|
||||||
<RandomWelcomeMessage />
|
<RandomWelcomeMessage />
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Section } from "./page";
|
import { Section } from "./page";
|
||||||
|
|
||||||
export function SettingsAccountSection() {
|
export function SettingsAccountSection() {
|
||||||
return <Section title="บัญชี">ask dreamgineer bruh</Section>;
|
return <Section title="บัญชี">ยังไม่พร้อมให้บริการ</Section>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user