This commit is contained in:
oiov
2024-07-28 11:14:59 +08:00
parent b58be42e22
commit 84b95f3b87
8 changed files with 183 additions and 105 deletions
+11 -2
View File
@@ -41,12 +41,21 @@ export default function LoginPage() {
<UserAuthForm />
</Suspense>
<p className="px-8 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our{" "}
<Link
href="/register"
href="/terms"
className="hover:text-brand underline underline-offset-4"
>
Don&apos;t have an account? Sign Up
Terms of Service
</Link>{" "}
and{" "}
<Link
href="/privacy"
className="hover:text-brand underline underline-offset-4"
>
Privacy Policy
</Link>
.
</p>
</div>
</div>
+45 -15
View File
@@ -4,8 +4,8 @@ import { useState } from "react";
import Link from "next/link";
import { UserRecordFormData } from "@/actions/cloudflare-dns-record";
import { User } from "@prisma/client";
import { ArrowUpRight } from "lucide-react";
import useSWR from "swr";
import { ArrowUpRight, DotSquareIcon, RefreshCwIcon } from "lucide-react";
import useSWR, { useSWRConfig } from "swr";
import { TTL_ENUMS } from "@/lib/cloudflare";
import { fetcher } from "@/lib/utils";
@@ -66,6 +66,7 @@ export default function UserRecordsList({ user }: RecordListProps) {
const [currentEditRecord, setCurrentEditRecord] =
useState<UserRecordFormData | null>(null);
const { mutate } = useSWRConfig();
const { data, error, isLoading } = useSWR<UserRecordFormData[]>(
"/api/record",
fetcher,
@@ -74,6 +75,10 @@ export default function UserRecordsList({ user }: RecordListProps) {
},
);
const handleRefresh = () => {
mutate("/api/record", undefined);
};
return (
<>
<Card className="xl:col-span-2">
@@ -84,18 +89,31 @@ export default function UserRecordsList({ user }: RecordListProps) {
All Dns Records
</CardDescription>
</div>
<Button
className="ml-auto w-[120px] shrink-0 gap-1"
variant="default"
onClick={() => {
setCurrentEditRecord(null);
setShowForm(false);
setFormType("add");
setShowForm(!isShowForm);
}}
>
Add record
</Button>
<div className="ml-auto flex items-center justify-end gap-3">
<Button
variant={"outline"}
onClick={() => handleRefresh()}
disabled={isLoading}
>
{isLoading ? (
<RefreshCwIcon className="h-4 w-4 animate-spin" />
) : (
<RefreshCwIcon className="h-4 w-4" />
)}
</Button>
<Button
className="w-[120px] shrink-0 gap-1"
variant="default"
onClick={() => {
setCurrentEditRecord(null);
setShowForm(false);
setFormType("add");
setShowForm(!isShowForm);
}}
>
Add record
</Button>
</div>
</CardHeader>
<CardContent>
{isShowForm && (
@@ -105,6 +123,7 @@ export default function UserRecordsList({ user }: RecordListProps) {
setShowForm={setShowForm}
type={formType}
initData={currentEditRecord}
onRefresh={handleRefresh}
/>
)}
<Table>
@@ -179,7 +198,18 @@ export default function UserRecordsList({ user }: RecordListProps) {
<EmptyPlaceholder.Description>
You don&apos;t have any record yet. Start creating record.
</EmptyPlaceholder.Description>
<Button>Add Record</Button>
<Button
className="w-[120px] shrink-0 gap-1"
variant="default"
onClick={() => {
setCurrentEditRecord(null);
setShowForm(false);
setFormType("add");
setShowForm(!isShowForm);
}}
>
Add record
</Button>
</EmptyPlaceholder>
)}
</TableBody>
+7 -11
View File
@@ -7,11 +7,14 @@ import {
import { env } from "@/env.mjs";
import { createDNSRecord } from "@/lib/cloudflare";
import { getCurrentUser } from "@/lib/session";
import { checkUserStatus } from "@/lib/user";
import { generateSecret } from "@/lib/utils";
export async function POST(req: Request) {
try {
const user = await getCurrentUser();
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
const { records } = await req.json();
const {
CLOUDFLARE_ZONE_ID,
@@ -20,13 +23,6 @@ export async function POST(req: Request) {
NEXT_PUBLIC_FREE_RECORD_QUOTA,
} = env;
if (!user?.id) {
return Response.json("Unauthorized", {
status: 401,
statusText: "Unauthorized",
});
}
if (!CLOUDFLARE_ZONE_ID || !CLOUDFLARE_API_KEY || !CLOUDFLARE_EMAIL) {
return Response.json("API key、zone iD and email are required", {
status: 400,
@@ -106,9 +102,9 @@ export async function POST(req: Request) {
}
} catch (error) {
console.error(error);
return Response.json(error, {
status: 500,
statusText: "Server error",
return Response.json(error?.statusText || error, {
status: error?.status || 500,
statusText: error?.statusText || "Server error",
});
}
}
+22 -12
View File
@@ -1,28 +1,38 @@
import { deleteUserRecord } from "@/actions/cloudflare-dns-record";
import { env } from "@/env.mjs";
import { deleteDNSRecord } from "@/lib/cloudflare";
import { getCurrentUser } from "@/lib/session";
import { checkUserStatus } from "@/lib/user";
export async function POST(req: Request) {
try {
const user = await getCurrentUser();
if (!user?.id) {
return Response.json("Unauthorized", {
status: 401,
statusText: "Unauthorized",
});
}
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
const { record_id, zone_id, active } = await req.json();
const { CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL } = env;
await deleteUserRecord(user.id, record_id, zone_id, active);
// await
// Delete cf dns record first.
const res = await deleteDNSRecord(
CLOUDFLARE_ZONE_ID,
CLOUDFLARE_API_KEY,
CLOUDFLARE_EMAIL,
record_id,
);
if (res && res.result?.id) {
// Then delete user record.
await deleteUserRecord(user.id, record_id, zone_id, active);
return Response.json({
status: 200,
statusText: "success",
});
}
} catch (error) {
console.error(error);
return Response.json(error, {
status: 500,
statusText: "Server error",
return Response.json(error?.statusText || error, {
status: error.status || 500,
statusText: error.statusText || "Server error",
});
}
}
+6 -11
View File
@@ -2,26 +2,21 @@ import { getUserRecords } from "@/actions/cloudflare-dns-record";
import { env } from "@/env.mjs";
import { getCurrentUser } from "@/lib/session";
import { checkUserStatus } from "@/lib/user";
export async function GET(req: Request) {
try {
const user = await getCurrentUser();
if (!user?.id) {
return Response.json("Unauthorized", {
status: 401,
statusText: "Unauthorized",
});
}
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
// const { CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL } = env;
const user_records = await getUserRecords(user.id, 1);
return Response.json(user_records);
} catch (error) {
return Response.json(error, {
status: 500,
statusText: "Server error",
return Response.json(error?.statusText || error, {
status: error.status || 500,
statusText: error.statusText || "Server error",
});
}
}
+2
View File
@@ -1,8 +1,10 @@
import { env } from "@/env.mjs";
import { getCurrentUser } from "@/lib/session";
import { checkUserStatus } from "@/lib/user";
export async function POST(req: Request) {
try {
const user = checkUserStatus(await getCurrentUser());
} catch (error) {
console.error(error);
return Response.json(error, {
+73 -53
View File
@@ -38,6 +38,7 @@ export interface RecordFormProps {
setShowForm: Dispatch<SetStateAction<boolean>>;
type: FormType;
initData?: UserRecordFormData | null;
onRefresh: () => void;
}
export function RecordForm({
@@ -46,8 +47,10 @@ export function RecordForm({
setShowForm,
type,
initData,
onRefresh,
}: RecordFormProps) {
const [isPending, startTransition] = useTransition();
const [isDeleting, startDeleteTransition] = useTransition();
const {
handleSubmit,
@@ -66,68 +69,79 @@ export function RecordForm({
});
const onSubmit = handleSubmit((data) => {
startTransition(async () => {
if (type === "add") {
handleCreateRecord(data);
} else if (type === "edit") {
handleUpdateRecord(data);
}
});
if (type === "add") {
handleCreateRecord(data);
} else if (type === "edit") {
handleUpdateRecord(data);
}
});
const handleCreateRecord = async (data: CreateDNSRecord) => {
const response = await fetch("/api/record/add", {
method: "POST",
body: JSON.stringify({
records: [data],
}),
});
if (!response.ok || response.status !== 200) {
toast.error("Created Failed!", {
description: response.statusText,
startTransition(async () => {
const response = await fetch("/api/record/add", {
method: "POST",
body: JSON.stringify({
records: [data],
}),
});
} else {
const res = await response.json();
toast.success(`Created successfully!`);
setShowForm(false);
}
if (!response.ok || response.status !== 200) {
toast.error("Created Failed!", {
description: response.statusText,
});
} else {
const res = await response.json();
toast.success(`Created successfully!`);
setShowForm(false);
onRefresh();
}
});
};
const handleUpdateRecord = async (data: CreateDNSRecord) => {
const response = await fetch("/api/record/update", {
method: "POST",
body: JSON.stringify({
records: [data],
}),
startTransition(async () => {
if (type === "edit") {
const response = await fetch("/api/record/update", {
method: "POST",
body: JSON.stringify({
records: [data],
}),
});
if (!response.ok || response.status !== 200) {
toast.error("Update Failed", {
description: response.statusText,
});
} else {
const res = await response.json();
toast.success(`Update successfully!`);
setShowForm(false);
onRefresh();
}
}
});
if (!response.ok || response.status !== 200) {
toast.error("Update Failed", {
description: response.statusText,
});
} else {
const res = await response.json();
toast.success(`Update successfully!`);
setShowForm(false);
}
};
const handleDeleteRecord = async () => {
const response = await fetch("/api/record/delete", {
method: "POST",
body: JSON.stringify({
record_id: initData?.record_id,
zone_id: initData?.zone_id,
active: initData?.active,
}),
});
if (!response.ok || response.status !== 200) {
toast.error("Delete Failed", {
description: response.statusText,
if (type === "edit") {
startTransition(async () => {
const response = await fetch("/api/record/delete", {
method: "POST",
body: JSON.stringify({
record_id: initData?.record_id,
zone_id: initData?.zone_id,
active: initData?.active,
}),
});
if (!response.ok || response.status !== 200) {
toast.error("Delete Failed", {
description: response.statusText,
});
} else {
await response.json();
toast.success(`Success`);
setShowForm(false);
onRefresh();
}
});
} else {
await response.json();
toast.success(`Delete successfully!`);
setShowForm(false);
}
};
@@ -261,16 +275,22 @@ export function RecordForm({
<div className="flex justify-end gap-3">
{type === "edit" && (
<Button
variant={"destructive"}
type="button"
variant="destructive"
className="mr-auto w-[80px] px-0"
onClick={() => handleDeleteRecord()}
disabled={isDeleting}
>
Delete
{isDeleting ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<p>Delete</p>
)}
</Button>
)}
<Button
type="reset"
variant={"outline"}
variant="outline"
className="w-[80px] px-0"
onClick={() => setShowForm(false)}
>
+17 -1
View File
@@ -26,4 +26,20 @@ export const getUserById = async (id: string) => {
} catch {
return null;
}
};
};
export function checkUserStatus(user: any) {
if (!user?.id) {
throw new Response("Unauthorized", {
status: 401,
statusText: "Unauthorized",
});
}
if (user.active === 0) {
throw new Response("Forbidden", {
status: 403,
statusText: "Forbidden",
});
}
return user;
}