feat : buzz feedback

This commit is contained in:
2026-08-30 23:48:03 +07:00 Unverified
parent b92348f589
commit fd841057c1
32 changed files with 3932 additions and 265 deletions
+4
View File
@@ -42,6 +42,10 @@ function createAuth() {
transaction: true,
}),
baseURL: required("BETTER_AUTH_URL"),
trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS
?.split(",")
.map((origin) => origin.trim())
.filter(Boolean),
secret: required("BETTER_AUTH_SECRET"),
emailAndPassword: {
enabled: true,
+47
View File
@@ -3,6 +3,13 @@ import "server-only";
import { cacheLife, cacheTag } from "next/cache";
import { getPublicPage, listPublicCharacters } from "@/lib/content/queries";
import {
getPublicArtifactExport,
getPublicGuide,
getPublicTeamExport,
getPublicWeaponExport,
listGuideCards,
} from "@/lib/guides/queries";
import {
characterCacheTag,
dataSourceCacheTag,
@@ -40,3 +47,43 @@ export async function getCachedPublicPage(
}
return result;
}
export async function getCachedPublicGuideCards() {
"use cache: remote";
cacheLife({ stale: 30, revalidate: 60, expire: 60 * 60 });
cacheTag(DIRECTORY_CACHE_TAG);
return listGuideCards();
}
export async function getCachedPublicGuide(slug: string) {
"use cache: remote";
cacheLife({ stale: 15, revalidate: 60, expire: 60 * 60 });
const result = await getPublicGuide(slug);
cacheTag(DIRECTORY_CACHE_TAG);
if (result) cacheTag(pageCacheTag(result.id));
return result;
}
export async function getCachedPublicTeamExport(teamId: string) {
"use cache: remote";
cacheLife({ stale: 15, revalidate: 60, expire: 60 * 60 });
const result = await getPublicTeamExport(teamId);
if (result) cacheTag(pageCacheTag(result.guideId));
return result;
}
export async function getCachedPublicWeaponExport(guideId: string) {
"use cache: remote";
cacheLife({ stale: 15, revalidate: 60, expire: 60 * 60 });
const result = await getPublicWeaponExport(guideId);
cacheTag(pageCacheTag(guideId));
return result;
}
export async function getCachedPublicArtifactExport(guideId: string) {
"use cache: remote";
cacheLife({ stale: 15, revalidate: 60, expire: 60 * 60 });
const result = await getPublicArtifactExport(guideId);
cacheTag(pageCacheTag(guideId));
return result;
}
+3
View File
@@ -0,0 +1,3 @@
export function artifactTierCount(secondName: string | null): 2 | 4 {
return secondName ? 2 : 4;
}
+1
View File
@@ -175,6 +175,7 @@ export async function getPublicTeamExport(teamId: string) {
return {
...row.team,
guideId: row.guide.id,
guideName: row.guide.name,
members: members.map(({ member, character }) => {
const weapon = weapons.find((item) => item.key === member.weaponKey);
+1
View File
@@ -113,6 +113,7 @@ export const extraSectionSchema = z.strictObject({
title: z.string().trim().min(1).max(160),
rows: z.array(z.strictObject({
mediaId: z.string().uuid().nullable(),
ratio: z.enum(["1:1", "16:9"]),
heading: z.string().trim().min(1).max(200),
body: z.string().trim().max(2_000),
percent: percent.nullable(),
+7 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { normalizeTalentPriorities, reorderTalentPriorities, talentRelationSymbol } from "@/lib/guides/talents";
import { normalizeTalentPriorities, reorderTalentPriorities, talentRelationSymbol, talentTypeLabel } from "@/lib/guides/talents";
describe("talent priorities", () => {
it("normalizes legacy talents to strict comparisons", () => {
@@ -32,4 +32,10 @@ describe("talent priorities", () => {
expect(talentRelationSymbol("gte")).toBe("≥");
expect(talentRelationSymbol()).toBe(">");
});
it("labels each talent type in Thai", () => {
expect(talentTypeLabel("NA")).toBe("ตีปกติ");
expect(talentTypeLabel("E")).toBe("สกิลธาตุ");
expect(talentTypeLabel("Q")).toBe("ท่าไม้ตาย");
});
});
+4
View File
@@ -41,3 +41,7 @@ export function reorderTalentPriorities(
export function talentRelationSymbol(relation?: TalentRelation): ">" | "≥" {
return relation === "gte" ? "≥" : ">";
}
export function talentTypeLabel(type: TalentPriority["type"]): string {
return type === "E" ? "สกิลธาตุ" : type === "Q" ? "ท่าไม้ตาย" : "ตีปกติ";
}
+2
View File
@@ -1,7 +1,9 @@
export function navigationIconPath(slug: string): string | null {
if (slug === "overview") return "/icon/nav/Overview.webp";
if (slug === "weapon") return "/icon/nav/Weapon.png";
if (slug === "artifact") return "/icon/nav/Artifact.webp";
if (slug === "constellations") return "/icon/nav/Constellations.webp";
if (slug === "team") return "/icon/nav/Team-calc.webp";
if (slug === "extra" || slug.startsWith("extra-")) return "/icon/nav/Extra.webp";
return null;
}