feat(don): add method toggle
This commit is contained in:
@@ -18,6 +18,7 @@ import { actionLog } from "@/lib/api";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { guides } from "@/lib/db/schema";
|
||||
import { forceRefresh } from "../../../settings/api";
|
||||
|
||||
export default async function GuideEditPage({
|
||||
params,
|
||||
@@ -66,6 +67,7 @@ export default async function GuideEditPage({
|
||||
await actionLog(`Updated guide ${data.name}`, data);
|
||||
|
||||
revalidatePath("/admin/guide");
|
||||
forceRefresh("/guide");
|
||||
return { toast: "Guide updated successfully.", close: true };
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { actionLog } from "@/lib/api";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { guides } from "@/lib/db/schema";
|
||||
import { forceRefresh } from "../../../settings/api";
|
||||
|
||||
export default async function GuideCreatePage() {
|
||||
if (!(await adminCheck())) redirect("/login");
|
||||
@@ -64,6 +65,7 @@ export default async function GuideCreatePage() {
|
||||
await actionLog(`Added guide ${data.name}`, data);
|
||||
|
||||
revalidatePath("/admin/guide");
|
||||
forceRefresh("/guide");
|
||||
return { toast: "Guide added successfully.", close: true };
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { redirect } from "next/navigation";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { db } from "@/lib/db";
|
||||
import { guides } from "@/lib/db/schema";
|
||||
import { forceRefresh } from "../settings/api";
|
||||
|
||||
export async function hideGuide(id: string) {
|
||||
if (!(await adminCheck())) redirect("/login");
|
||||
@@ -15,5 +16,7 @@ export async function hideGuide(id: string) {
|
||||
hidden: not(guides.hidden),
|
||||
})
|
||||
.where(eq(guides.id, id));
|
||||
|
||||
forceRefresh("/guide");
|
||||
revalidatePath("/admin/guide");
|
||||
}
|
||||
|
||||
@@ -10,16 +10,16 @@ import { settings } from "@/lib/db/schema";
|
||||
import { adminCheck } from "@/lib/auth";
|
||||
import { sse } from "@/lib/db/sse-endpoints";
|
||||
|
||||
export async function getEnka() {
|
||||
export async function getSettongs() {
|
||||
if (!(await adminCheck())) throw "Unauthorized";
|
||||
const [{ enka } = { enka: true }] = await db.select().from(settings).limit(1);
|
||||
return enka;
|
||||
const [
|
||||
settangs = { enka: true, donatePromptpay: true, donateTruemoney: true },
|
||||
] = await db.select().from(settings).limit(1);
|
||||
return settangs;
|
||||
}
|
||||
|
||||
export async function toggleEnka(state: boolean) {
|
||||
if (!(await adminCheck())) throw "Unauthorized";
|
||||
const last = await getEnka();
|
||||
if (last === state) return;
|
||||
await db
|
||||
.insert(settings)
|
||||
.values({ enka: state })
|
||||
@@ -39,7 +39,22 @@ export async function syncAmber() {
|
||||
return res.stdout;
|
||||
}
|
||||
|
||||
export async function forceRefresh() {
|
||||
export async function forceRefresh(prefix: string | null = null) {
|
||||
if (!(await adminCheck())) throw "Unauthorized";
|
||||
sse.active.pub("refresh", null);
|
||||
sse.active.pub("refresh", prefix);
|
||||
|
||||
await actionLog("Pulled a force refresh");
|
||||
}
|
||||
|
||||
export async function toggleDonatePaymentMethod(
|
||||
method: "donatePromptpay" | "donateTruemoney",
|
||||
state: boolean,
|
||||
) {
|
||||
if (!(await adminCheck())) throw "Unauthorized";
|
||||
await db
|
||||
.insert(settings)
|
||||
.values({ [method]: state })
|
||||
.onConflictDoUpdate({ target: settings.id, set: { [method]: state } });
|
||||
|
||||
await actionLog("Changed a settings", { [method]: state });
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import {
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SettingsAccountSection } from "./account";
|
||||
import { getEnka } from "./api";
|
||||
import { getSettongs } from "./api";
|
||||
import { SettingsServicesSection } from "./services";
|
||||
|
||||
export default async function () {
|
||||
const enka = await getEnka();
|
||||
const { enka, donatePromptpay, donateTruemoney } = await getSettongs();
|
||||
|
||||
return (
|
||||
<div className="grid h-full w-full gap-8 p-2 pl-0">
|
||||
@@ -22,7 +22,11 @@ export default async function () {
|
||||
<TabsTrigger value="account">บัญชี</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="external">
|
||||
<SettingsServicesSection enka={enka} />
|
||||
<SettingsServicesSection
|
||||
enka={enka}
|
||||
donPp={donatePromptpay}
|
||||
donTmn={donateTruemoney}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="account">
|
||||
<SettingsAccountSection />
|
||||
@@ -49,3 +53,5 @@ export function Section({
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { Bomb, FolderSync } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { CdnChooser } from "@/components/chooser";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { shared } from "@/lib/comms";
|
||||
import { forceRefresh, syncAmber, toggleEnka } from "./api";
|
||||
import {
|
||||
forceRefresh,
|
||||
syncAmber,
|
||||
toggleDonatePaymentMethod,
|
||||
toggleEnka,
|
||||
} from "./api";
|
||||
import { Section } from "./page";
|
||||
import { ActionButton } from "@/components/action-button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
export function SettingsServicesSection({
|
||||
enka: enkaInitial,
|
||||
donPp: initialDonPp,
|
||||
donTmn: initialDonTmn,
|
||||
}: {
|
||||
enka: boolean;
|
||||
donPp: boolean;
|
||||
donTmn: boolean;
|
||||
}) {
|
||||
const [syncResult, setSyncResult] = useState("");
|
||||
const [enka, setEnka] = useState<boolean>();
|
||||
const [enka, setEnka] = useState<boolean>(enkaInitial);
|
||||
const [donPp, setDonPp] = useState<boolean>(initialDonPp);
|
||||
const [donTmn, setDonTmn] = useState<boolean>(initialDonTmn);
|
||||
const [pathToReload, setPathToReload] = useState("");
|
||||
const [debug] = shared.state("debug");
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof enka !== "boolean") return setEnka(enkaInitial);
|
||||
toggleEnka(enka);
|
||||
}, [enka, enkaInitial]);
|
||||
|
||||
async function sync() {
|
||||
return syncAmber().then(setSyncResult);
|
||||
}
|
||||
@@ -31,7 +39,13 @@ export function SettingsServicesSection({
|
||||
<Section title="บริการนอก">
|
||||
<div className="flex h-full flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch checked={enka} onCheckedChange={setEnka} />
|
||||
<Switch
|
||||
checked={enka}
|
||||
onCheckedChange={(v) => (
|
||||
setEnka(v),
|
||||
toggleEnka(v).then(forceRefresh.bind(null, "/artifact"))
|
||||
)}
|
||||
/>
|
||||
เปิดใช้การดึงตัวละครใน Artifact
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -44,6 +58,45 @@ export function SettingsServicesSection({
|
||||
FORCE RELOAD
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
placeholder="/path/prefix/to/reload"
|
||||
className="w-60"
|
||||
onChange={(v) => setPathToReload(v.target.value)}
|
||||
/>
|
||||
<ActionButton
|
||||
action={forceRefresh.bind(null, pathToReload)}
|
||||
variant="destructive"
|
||||
>
|
||||
<Bomb />
|
||||
Reload
|
||||
</ActionButton>
|
||||
</div>
|
||||
<span>Donate Payment Method</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={donPp}
|
||||
onCheckedChange={(v) => (
|
||||
setDonPp(v),
|
||||
toggleDonatePaymentMethod("donatePromptpay", v).then(
|
||||
forceRefresh.bind(null, "/donate"),
|
||||
)
|
||||
)}
|
||||
/>
|
||||
Promptpay
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={donTmn}
|
||||
onCheckedChange={(v) => (
|
||||
setDonTmn(v),
|
||||
toggleDonatePaymentMethod("donateTruemoney", v).then(
|
||||
forceRefresh.bind(null, "/donate"),
|
||||
)
|
||||
)}
|
||||
/>
|
||||
Truemoney
|
||||
</div>
|
||||
{debug && (
|
||||
<div className="grid w-fit gap-1 rounded-sm border border-dashed p-1">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
|
||||
+42
-26
@@ -1,5 +1,5 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { QrCodeIcon, SendIcon } from "lucide-react";
|
||||
import { CircleX, QrCodeIcon, SendIcon } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import z from "zod";
|
||||
import { th } from "zod/v4/locales";
|
||||
@@ -345,34 +345,50 @@ export default async function () {
|
||||
]}
|
||||
>
|
||||
<FormChoice value="tmn">
|
||||
<FormInput name="link" label="ลิ้งค์อั่งเปา">
|
||||
<Input placeholder="https://gift.truemoney.com/campaign/?v=..." />
|
||||
</FormInput>
|
||||
{artifactConfig.donateTruemoney ? (
|
||||
<FormInput name="link" label="ลิ้งค์อั่งเปา">
|
||||
<Input placeholder="https://gift.truemoney.com/campaign/?v=..." />
|
||||
</FormInput>
|
||||
) : (
|
||||
<div className="flex gap-2">
|
||||
<CircleX className="text-red-500" />{" "}
|
||||
ขณะนี้การโอนเงินด้วยทรูมันนี่ใช้ไม่ได้ชั่วคราว
|
||||
</div>
|
||||
)}
|
||||
</FormChoice>
|
||||
<FormChoice value="pp">
|
||||
<div className="flex w-full gap-2 pb-2">
|
||||
<Image
|
||||
src={PromptpayImage}
|
||||
alt="Promptpay QR Code"
|
||||
className="max-w-32 shrink-0 rounded"
|
||||
/>
|
||||
<div className="relative flex shrink-0 flex-col">
|
||||
<span className="text-sm font-bold">บัญชีรับโดเนท</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
ผู้รับ: นาย พัชรพล พลพันธุ์
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
บัญชี: xxx-x-x8666-x
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
เลขที่อ้างอิง: 004999056945438
|
||||
</span>
|
||||
<DownloadButton />
|
||||
{artifactConfig.donatePromptpay ? (
|
||||
<>
|
||||
<div className="flex w-full gap-2 pb-2">
|
||||
<Image
|
||||
src={PromptpayImage}
|
||||
alt="Promptpay QR Code"
|
||||
className="max-w-32 shrink-0 rounded"
|
||||
/>
|
||||
<div className="relative flex shrink-0 flex-col">
|
||||
<span className="text-sm font-bold">บัญชีรับโดเนท</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
ผู้รับ: นาย พัชรพล พลพันธุ์
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
บัญชี: xxx-x-x8666-x
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
เลขที่อ้างอิง: 004999056945438
|
||||
</span>
|
||||
<DownloadButton />
|
||||
</div>
|
||||
</div>
|
||||
<FormInput name="slip">
|
||||
<SlipUpload />
|
||||
</FormInput>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex gap-2">
|
||||
<CircleX className="text-red-500" />{" "}
|
||||
ขณะนี้การโอนเงินด้วยพร้อมเพย์ใช้ไม่ได้ชั่วคราว
|
||||
</div>
|
||||
</div>
|
||||
<FormInput name="slip">
|
||||
<SlipUpload />
|
||||
</FormInput>
|
||||
)}
|
||||
</FormChoice>
|
||||
</FormTab>
|
||||
<div className="flex justify-end">
|
||||
|
||||
+2
-1
@@ -74,7 +74,8 @@ export function VersionCheck({ headless = false }: { headless?: boolean }) {
|
||||
}
|
||||
if (newVersion !== version) ver.current = newVersion;
|
||||
},
|
||||
refresh() {
|
||||
refresh(prefix) {
|
||||
if (prefix && !window.location.pathname.startsWith(prefix)) return;
|
||||
window.location.hash = "#update";
|
||||
queueMicrotask(() => window.location.reload());
|
||||
},
|
||||
|
||||
+3
-1
@@ -74,7 +74,9 @@ export const versions = pgTable("versions", {
|
||||
|
||||
export const settings = pgTable("settings", {
|
||||
id: boolean().primaryKey().default(true),
|
||||
enka: boolean().notNull().default(false), // Turn this off when Enka is on maintenance
|
||||
enka: boolean().notNull().default(true), // Turn this off when Enka is on maintenance
|
||||
donatePromptpay: boolean().notNull().default(true),
|
||||
donateTruemoney: boolean().notNull().default(true),
|
||||
donateGoal: numeric({ mode: "number" }),
|
||||
donateGoalStarting: timestamp(),
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ export const sse = sseEndpointMap({
|
||||
active: {
|
||||
version: z.string(),
|
||||
live: z.custom<YoutubeLiveInfo>(),
|
||||
refresh: z.null(),
|
||||
refresh: z.string().nullable(),
|
||||
},
|
||||
// Admin Live Log
|
||||
log: {
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ export default {
|
||||
},
|
||||
deploymentId: process.env.NEXT_DEPLOYMENT_ID,
|
||||
// reactCompiler: true,
|
||||
allowedDevOrigins: ["astral:3000", "dev3000.astrxl.dev"],
|
||||
allowedDevOrigins: ["astral:3000", "dev3000.astrxl.dev", "nyx"],
|
||||
// basePath: "/beta"
|
||||
output: "standalone",
|
||||
} satisfies NextConfig;
|
||||
|
||||
Reference in New Issue
Block a user