add statu switcher

This commit is contained in:
oiov
2024-11-06 17:34:13 +08:00
parent ff2c7753d1
commit 60e52cf11a
6 changed files with 151 additions and 13 deletions
@@ -1,9 +1,10 @@
"use client";
import { useState } from "react";
import { useRef, useState } from "react";
import Link from "next/link";
import { User } from "@prisma/client";
import { PenLine, RefreshCwIcon } from "lucide-react";
import { toast } from "sonner";
import useSWR, { useSWRConfig } from "swr";
import { UserRecordFormData } from "@/lib/dto/cloudflare-dns-record";
@@ -19,6 +20,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import {
Table,
TableBody,
@@ -37,6 +39,7 @@ import CountUpFn from "@/components/dashboard/count-up";
import StatusDot from "@/components/dashboard/status-dot";
import { FormType, RecordForm } from "@/components/forms/record-form";
import { EmptyPlaceholder } from "@/components/shared/empty-placeholder";
import { Icons } from "@/components/shared/icons";
import { LinkPreviewer } from "@/components/shared/link-previewer";
import { PaginationWrapper } from "@/components/shared/pagination";
@@ -94,6 +97,38 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
mutate(`${action}?page=${currentPage}&size=${pageSize}`, undefined);
};
const handleChangeStatu = async (
checked: boolean,
record: UserRecordFormData,
setChecked: (value: boolean) => void,
) => {
const originalState = record.active === 1;
setChecked(checked); // 立即更新 UI
const res = await fetch(`/api/record/update`, {
method: "PUT",
body: JSON.stringify({
zone_id: record.zone_id,
record_id: record.record_id,
active: checked ? 1 : 0,
target: record.name,
}),
});
if (res.ok) {
const data = await res.json();
if (data === "Target is accessible!") {
toast.success(data);
} else {
setChecked(originalState);
toast.warning(data);
}
} else {
setChecked(originalState);
toast.error("Failed to update status");
}
};
return (
<>
<Card className="xl:col-span-2">
@@ -186,7 +221,7 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
Status
</TableHead>
<TableHead className="col-span-1 hidden items-center justify-center font-bold sm:flex">
Update
Updated
</TableHead>
<TableHead className="col-span-1 flex items-center justify-center font-bold">
Actions
@@ -238,8 +273,36 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
?.label
}
</TableCell>
<TableCell className="col-span-1 hidden justify-center sm:flex">
<StatusDot status={record.active} />
<TableCell className="col-span-1 hidden items-center justify-center gap-1 sm:flex">
<SwitchWrapper
record={record}
onChangeStatu={handleChangeStatu}
/>
{!record.active && (
<TooltipProvider>
<Tooltip delayDuration={200}>
<TooltipTrigger className="truncate">
<Icons.help className="size-4 cursor-pointer text-yellow-500 opacity-90" />
</TooltipTrigger>
<TooltipContent>
<ul className="list-disc px-3">
{/* 无序列表的dot */}
<li>The target is currently inaccessible.</li>
<li>Please check the target and try again.</li>
<li>
If the target is not activated within 3 days,{" "}
<br />
the administrator will{" "}
<strong className="text-red-500">
delete this record
</strong>
.
</li>
</ul>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</TableCell>
<TableCell className="col-span-1 hidden justify-center sm:flex">
{timeAgo(record.modified_on as unknown as Date)}
@@ -285,3 +348,24 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
</>
);
}
const SwitchWrapper = ({
record,
onChangeStatu,
}: {
record: UserRecordFormData;
onChangeStatu: (
checked: boolean,
record: UserRecordFormData,
setChecked: (value: boolean) => void,
) => Promise<void>;
}) => {
const [checked, setChecked] = useState(record.active === 1);
return (
<Switch
checked={checked}
onCheckedChange={(value) => onChangeStatu(value, record, setChecked)}
/>
);
};
+1 -1
View File
@@ -89,7 +89,7 @@ export async function POST(req: Request) {
tags: data.result.tags?.join("") ?? "",
created_on: data.result.created_on,
modified_on: data.result.modified_on,
active: 1,
active: 0,
});
if (res.status !== "success") {
return Response.json(res.status, {
+56 -5
View File
@@ -1,9 +1,13 @@
import { env } from "@/env.mjs";
import { updateDNSRecord } from "@/lib/cloudflare";
import { updateUserRecord } from "@/lib/dto/cloudflare-dns-record";
import {
updateUserRecord,
updateUserRecordState,
} from "@/lib/dto/cloudflare-dns-record";
import { checkUserStatus } from "@/lib/dto/user";
import { getCurrentUser } from "@/lib/session";
// update record
export async function POST(req: Request) {
try {
const user = checkUserStatus(await getCurrentUser());
@@ -11,10 +15,10 @@ export async function POST(req: Request) {
const { CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL } = env;
if (!CLOUDFLARE_ZONE_ID || !CLOUDFLARE_API_KEY || !CLOUDFLARE_EMAIL) {
return Response.json("API key、zone iD and email are required", {
status: 400,
statusText: "API key、zone iD and email are required",
});
return Response.json(
{ statusText: "API key andzone id are required." },
{ status: 401 },
);
}
const { record, recordId } = await req.json();
@@ -63,3 +67,50 @@ export async function POST(req: Request) {
});
}
}
// update record state
export async function PUT(req: Request) {
try {
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
const { CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL } = env;
if (!CLOUDFLARE_ZONE_ID || !CLOUDFLARE_API_KEY || !CLOUDFLARE_EMAIL) {
return Response.json(
{ statusText: "API key and zone id are required." },
{ status: 401 },
);
}
const { zone_id, record_id, target, active } = await req.json();
let isTargetAccessible = false;
try {
const target_res = await fetch(`https://${target}`);
isTargetAccessible = target_res.status === 200;
} catch (fetchError) {
isTargetAccessible = false;
// console.log(`Failed to access target: ${fetchError}`);
}
const res = await updateUserRecordState(
user.id,
record_id,
zone_id,
isTargetAccessible ? 1 : 0,
);
if (!res) {
return Response.json(
{ statusText: "An error occurred." },
{ status: 502 },
);
}
return Response.json(
isTargetAccessible ? "Target is accessible!" : "Target is unaccessible!",
);
} catch (error) {
console.error(error);
return Response.json({ statusText: "Server error" }, { status: 500 });
}
}
+1 -1
View File
@@ -95,7 +95,7 @@ export function RecordForm({
description: response.statusText,
});
} else {
const res = await response.json();
// const res = await response.json();
toast.success(`Created successfully!`);
setShowForm(false);
onRefresh();
+3
View File
@@ -2,11 +2,13 @@ import {
AlertTriangle,
ArrowRight,
ArrowUpRight,
BadgeHelp,
BookOpen,
Bug,
Check,
ChevronLeft,
ChevronRight,
CircleHelp,
Copy,
File,
FileText,
@@ -129,6 +131,7 @@ export const Icons = {
link: Link,
mail: Mail,
bug: Bug,
// help: CircleHelp,
outLink: ({ ...props }: LucideProps) => (
<svg
width="0.95em"
+2 -2
View File
@@ -89,7 +89,7 @@ export async function getUserRecords(
role === "USER"
? {
userId,
active,
// active,
}
: {};
const [total, list] = await prisma.$transaction([
@@ -217,7 +217,7 @@ export async function updateUserRecordState(
) {
return await prisma.userRecord.update({
where: {
userId,
// userId,
record_id,
zone_id,
},