chore style

This commit is contained in:
oiov
2025-03-29 17:18:05 +08:00
parent a421f2ab27
commit a549481b61
8 changed files with 393 additions and 399 deletions
@@ -233,6 +233,8 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
<TableColumnSekleton />
<TableColumnSekleton />
<TableColumnSekleton />
<TableColumnSekleton />
<TableColumnSekleton />
</>
) : data && data.list && data.list.length ? (
data.list.map((record) => (
+50 -3
View File
@@ -170,9 +170,6 @@ export function DailyPVUVChart({
.filter(Boolean)
.join(" ");
const areaData = data.map((item) => ({
id: item.country,
}));
// const pointData = data.map((item) => ({
// id: item.id,
// city: item.city,
@@ -194,6 +191,13 @@ export function DailyPVUVChart({
}
});
const areaData = Object.entries(countryClicks).map(
([country, clicks], index) => ({
id: country,
// color: getColorByClicks(clicks, index, countryClicks),
}),
);
const triggers = {
[TopoJSONMap.selectors.feature]: (d: any) =>
`${getCountryName(d.id)} · ${countryClicks[d.id] || 0}`,
@@ -441,3 +445,46 @@ export function StatsList({ data, title }: { data: Stat[]; title: string }) {
</div>
);
}
// const baseColors = [
// "#ff6b7e",
// "#a6cc74",
// "#4d8cfd",
// "#f4b83e",
// "#FF00FF",
// "#6859be",
// ];
// const hexToRgb = (hex: string) => {
// const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
// return result
// ? {
// r: parseInt(result[1], 16),
// g: parseInt(result[2], 16),
// b: parseInt(result[3], 16),
// }
// : null;
// };
// const getColorByClicks = (
// clicks: number,
// baseColorIndex: number,
// countryClicks: { [key: string]: number },
// ) => {
// const maxClicks = Math.max(...Object.values(countryClicks));
// const minClicks = Math.min(...Object.values(countryClicks));
// // 归一化点击次数
// const normalized =
// maxClicks === minClicks
// ? 0
// : (clicks - minClicks) / (maxClicks - minClicks);
// // 获取基础颜色
// const baseColor = hexToRgb(baseColors[baseColorIndex % baseColors.length]);
// // 最低60%透明度,最高100%不透明
// const alpha = 0.5 + normalized * 0.5;
// return `rgba(${baseColor!.r}, ${baseColor!.g}, ${baseColor!.b}, ${alpha})`;
// };
+14 -13
View File
@@ -66,8 +66,8 @@ export interface UrlListProps {
function TableColumnSekleton() {
return (
<TableRow className="grid grid-cols-3 items-center sm:grid-cols-9">
<TableCell className="col-span-1">
<TableRow className="grid grid-cols-3 items-center sm:grid-cols-10">
<TableCell className="col-span-1 sm:col-span-2">
<Skeleton className="h-5 w-20" />
</TableCell>
<TableCell className="col-span-1 sm:col-span-2">
@@ -223,6 +223,17 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
</div>
</CardHeader>
<CardContent>
{isShowForm && (
<UrlForm
user={{ id: user.id, name: user.name || "" }}
isShowForm={isShowForm}
setShowForm={setShowForm}
type={formType}
initData={currentEditUrl}
action={action}
onRefresh={handleRefresh}
/>
)}
<div className="mb-2 flex-row items-center gap-2 space-y-2 sm:flex sm:space-y-0">
<div className="relative w-full">
<Input
@@ -299,17 +310,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
</div>
)}
</div>
{isShowForm && (
<UrlForm
user={{ id: user.id, name: user.name || "" }}
isShowForm={isShowForm}
setShowForm={setShowForm}
type={formType}
initData={currentEditUrl}
action={action}
onRefresh={handleRefresh}
/>
)}
<Table>
<TableHeader className="bg-gray-100/50 dark:bg-primary-foreground">
<TableRow className="grid grid-cols-3 items-center sm:grid-cols-10">
+8
View File
@@ -0,0 +1,8 @@
export async function POST(req: Request) {
try {
const { email } = await req.json();
return Response.json({ email });
} catch (error) {
return Response.json({ statusText: "Server error" }, { status: 500 });
}
}
+167 -165
View File
@@ -155,144 +155,145 @@ export function RecordForm({
};
return (
<form
className="mb-4 rounded-lg border border-dashed p-4 shadow-sm animate-in fade-in-50"
onSubmit={onSubmit}
>
<div className="items-center justify-start gap-4 md:flex">
<FormSectionColumns title="Type">
<Select
onValueChange={(value: RecordType) => {
setValue("type", value);
setCurrentRecordType(value);
}}
name={"type"}
defaultValue={initData?.type || "CNAME"}
<div className="mb-4 rounded-lg border border-dashed shadow-sm animate-in fade-in-50">
<div className="rounded-t-lg bg-muted px-4 py-2 text-lg font-semibold">
{type === "add" ? "Create" : "Edit"} record
</div>
<form className="p-4" onSubmit={onSubmit}>
<div className="items-center justify-start gap-4 md:flex">
<FormSectionColumns title="Type">
<Select
onValueChange={(value: RecordType) => {
setValue("type", value);
setCurrentRecordType(value);
}}
name={"type"}
defaultValue={initData?.type || "CNAME"}
>
<SelectTrigger className="w-full shadow-inner">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
{RECORD_TYPE_ENUMS.map((type) => (
<SelectItem key={type.value} value={type.value}>
{type.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="p-1 text-[13px] text-muted-foreground">Required.</p>
</FormSectionColumns>
<FormSectionColumns title="Name">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="name">
Name (required)
</Label>
<div className="relative">
<Input
id="name"
className="flex-1 shadow-inner"
size={32}
{...register("name")}
/>
{currentRecordType === "CNAME" ||
(currentRecordType === "A" && (
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-sm text-slate-500">
.wr.do
</span>
))}
</div>
</div>
<div className="flex flex-col justify-between p-1">
{errors?.name ? (
<p className="pb-0.5 text-[13px] text-red-600">
{errors.name.message}
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
Required. Use @ for root.
</p>
)}
</div>
</FormSectionColumns>
<FormSectionColumns
title={
currentRecordType === "CNAME"
? "Content"
: currentRecordType === "A"
? "IPv4 address"
: "Content"
}
>
<SelectTrigger className="w-full shadow-inner">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
{RECORD_TYPE_ENUMS.map((type) => (
<SelectItem key={type.value} value={type.value}>
{type.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="p-1 text-[13px] text-muted-foreground">Required.</p>
</FormSectionColumns>
<FormSectionColumns title="Name">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="name">
Name (required)
</Label>
<div className="relative">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="content">
Content
</Label>
<Input
id="name"
id="content"
className="flex-1 shadow-inner"
size={32}
{...register("name")}
{...register("content")}
/>
{currentRecordType === "CNAME" ||
(currentRecordType === "A" && (
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-sm text-slate-500">
.wr.do
</span>
))}
</div>
</div>
<div className="flex flex-col justify-between p-1">
{errors?.name ? (
<p className="pb-0.5 text-[13px] text-red-600">
{errors.name.message}
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
Required. Use @ for root.
</p>
)}
</div>
</FormSectionColumns>
<FormSectionColumns
title={
currentRecordType === "CNAME"
? "Content"
: currentRecordType === "A"
? "IPv4 address"
: "Content"
}
>
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="content">
Content
</Label>
<Input
id="content"
className="flex-1 shadow-inner"
size={32}
{...register("content")}
/>
</div>
<div className="flex flex-col justify-between p-1">
{errors?.content ? (
<p className="pb-0.5 text-[13px] text-red-600">
{errors.content.message}
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
{currentRecordType === "CNAME"
? "Required. E.g. www.example.com"
: currentRecordType === "A"
? "Required. E.g. 8.8.8.8"
: "Required."}
</p>
)}
</div>
</FormSectionColumns>
</div>
<div className="flex flex-col justify-between p-1">
{errors?.content ? (
<p className="pb-0.5 text-[13px] text-red-600">
{errors.content.message}
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
{currentRecordType === "CNAME"
? "Required. E.g. www.example.com"
: currentRecordType === "A"
? "Required. E.g. 8.8.8.8"
: "Required."}
</p>
)}
</div>
</FormSectionColumns>
</div>
<div className="items-center justify-start gap-4 md:flex">
<FormSectionColumns title="TTL">
<Select
onValueChange={(value: string) => {
setValue("ttl", Number(value));
}}
name="ttl"
defaultValue={String(initData?.ttl || 1)}
>
<SelectTrigger className="w-full shadow-inner">
<SelectValue placeholder="Select a time" />
</SelectTrigger>
<SelectContent>
{TTL_ENUMS.map((ttl) => (
<SelectItem key={ttl.value} value={ttl.value}>
{ttl.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="p-1 text-[13px] text-muted-foreground">
Optional. Time To Live.
</p>
</FormSectionColumns>
<FormSectionColumns title="Comment">
<div className="flex items-center gap-2">
<Label className="sr-only" htmlFor="comment">
Comment
</Label>
<Input
id="comment"
className="flex-2 shadow-inner"
size={74}
{...register("comment")}
/>
</div>
<p className="p-1 text-[13px] text-muted-foreground">
Enter your comment here (up to 100 characters)
</p>
</FormSectionColumns>
{/* <FormSectionColumns title="Proxy">
<div className="items-center justify-start gap-4 md:flex">
<FormSectionColumns title="TTL">
<Select
onValueChange={(value: string) => {
setValue("ttl", Number(value));
}}
name="ttl"
defaultValue={String(initData?.ttl || 1)}
>
<SelectTrigger className="w-full shadow-inner">
<SelectValue placeholder="Select a time" />
</SelectTrigger>
<SelectContent>
{TTL_ENUMS.map((ttl) => (
<SelectItem key={ttl.value} value={ttl.value}>
{ttl.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="p-1 text-[13px] text-muted-foreground">
Optional. Time To Live.
</p>
</FormSectionColumns>
<FormSectionColumns title="Comment">
<div className="flex items-center gap-2">
<Label className="sr-only" htmlFor="comment">
Comment
</Label>
<Input
id="comment"
className="flex-2 shadow-inner"
size={74}
{...register("comment")}
/>
</div>
<p className="p-1 text-[13px] text-muted-foreground">
Enter your comment here (up to 100 characters)
</p>
</FormSectionColumns>
{/* <FormSectionColumns title="Proxy">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="proxy">
Proxy
@@ -301,46 +302,47 @@ export function RecordForm({
</div>
<p className="p-1 text-[13px] text-muted-foreground">Proxy status</p>
</FormSectionColumns> */}
</div>
</div>
{/* Action buttons */}
<div className="mt-3 flex justify-end gap-3">
{type === "edit" && (
{/* Action buttons */}
<div className="mt-3 flex justify-end gap-3">
{type === "edit" && (
<Button
type="button"
variant="destructive"
className="mr-auto w-[80px] px-0"
onClick={() => handleDeleteRecord()}
disabled={isDeleting}
>
{isDeleting ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<p>Delete</p>
)}
</Button>
)}
<Button
type="button"
variant="destructive"
className="mr-auto w-[80px] px-0"
onClick={() => handleDeleteRecord()}
disabled={isDeleting}
type="reset"
variant="outline"
className="w-[80px] px-0"
onClick={() => setShowForm(false)}
>
{isDeleting ? (
Cancle
</Button>
<Button
type="submit"
variant="blue"
disabled={isPending}
className="w-[80px] shrink-0 px-0"
>
{isPending ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<p>Delete</p>
<p>{type === "edit" ? "Update" : "Save"}</p>
)}
</Button>
)}
<Button
type="reset"
variant="outline"
className="w-[80px] px-0"
onClick={() => setShowForm(false)}
>
Cancle
</Button>
<Button
type="submit"
variant="blue"
disabled={isPending}
className="w-[80px] shrink-0 px-0"
>
{isPending ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<p>{type === "edit" ? "Update" : "Save"}</p>
)}
</Button>
</div>
</form>
</div>
</form>
</div>
);
}
+151 -149
View File
@@ -145,124 +145,125 @@ export function UrlForm({
};
return (
<form
className="mb-4 rounded-lg border border-dashed p-4 shadow-sm animate-in fade-in-50"
onSubmit={onSubmit}
>
<div className="items-center justify-start gap-4 md:flex">
<FormSectionColumns title="Target URL">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="target">
Target
</Label>
<Input
id="target"
className="flex-1 shadow-inner"
size={32}
{...register("target")}
/>
</div>
<div className="flex flex-col justify-between p-1">
{errors?.target ? (
<p className="pb-0.5 text-[13px] text-red-600">
{errors.target.message}
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
Required. https://your-origin-url
</p>
)}
</div>
</FormSectionColumns>
<FormSectionColumns title="Short Link">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="url">
Url
</Label>
<div className="mb-4 rounded-lg border border-dashed shadow-sm animate-in fade-in-50">
<div className="rounded-t-lg bg-muted px-4 py-2 text-lg font-semibold">
{type === "add" ? "Create" : "Edit"} short link
</div>
<form className="p-4" onSubmit={onSubmit}>
<div className="items-center justify-start gap-4 md:flex">
<FormSectionColumns title="Target URL">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="target">
Target
</Label>
<Input
id="target"
className="flex-1 shadow-inner"
size={32}
{...register("target")}
/>
</div>
<div className="flex flex-col justify-between p-1">
{errors?.target ? (
<p className="pb-0.5 text-[13px] text-red-600">
{errors.target.message}
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
Required. https://your-origin-url
</p>
)}
</div>
</FormSectionColumns>
<FormSectionColumns title="Short Link">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="url">
Url
</Label>
<div className="relative flex items-center">
<Select
onValueChange={(value: string) => {
setValue("prefix", value);
}}
name="prefix"
defaultValue={initData?.prefix || siteConfig.shortDomains[0]}
disabled={type === "edit"}
>
<SelectTrigger className="w-2/5 rounded-r-none shadow-inner">
<SelectValue placeholder="Select a domain" />
</SelectTrigger>
<SelectContent>
{siteConfig.shortDomains.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))}
</SelectContent>
</Select>
{/* <span className="pointer-events-none absolute left-20 whitespace-nowrap text-sm text-gray-400">
<div className="relative flex items-center">
<Select
onValueChange={(value: string) => {
setValue("prefix", value);
}}
name="prefix"
defaultValue={initData?.prefix || siteConfig.shortDomains[0]}
disabled={type === "edit"}
>
<SelectTrigger className="w-2/5 rounded-r-none shadow-inner">
<SelectValue placeholder="Select a domain" />
</SelectTrigger>
<SelectContent>
{siteConfig.shortDomains.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))}
</SelectContent>
</Select>
{/* <span className="pointer-events-none absolute left-20 whitespace-nowrap text-sm text-gray-400">
/s/
</span> */}
<Input
id="url"
className="w-3/5 flex-1 rounded-none pl-[8px] shadow-inner"
size={20}
{...register("url")}
disabled={type === "edit"}
/>
<Button
className="rounded-l-none"
type="button"
size="sm"
variant="outline"
disabled={type === "edit"}
onClick={() => {
setValue("url", generateUrlSuffix(6));
}}
>
<Sparkles className="h-4 w-4 text-slate-500" />
</Button>
<Input
id="url"
className="w-3/5 flex-1 rounded-none pl-[8px] shadow-inner"
size={20}
{...register("url")}
disabled={type === "edit"}
/>
<Button
className="rounded-l-none"
type="button"
size="sm"
variant="outline"
disabled={type === "edit"}
onClick={() => {
setValue("url", generateUrlSuffix(6));
}}
>
<Sparkles className="h-4 w-4 text-slate-500" />
</Button>
</div>
</div>
</div>
<div className="flex flex-col justify-between p-1">
{errors?.url ? (
<p className="pb-0.5 text-[13px] text-red-600">
{errors.url.message}
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
A random url suffix. Final url likewr.do/s/suffix
</p>
)}
</div>
</FormSectionColumns>
</div>
<div className="flex flex-col justify-between p-1">
{errors?.url ? (
<p className="pb-0.5 text-[13px] text-red-600">
{errors.url.message}
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
A random url suffix. Final url likewr.do/s/suffix
</p>
)}
</div>
</FormSectionColumns>
</div>
<div className="items-center justify-start gap-4 md:flex">
<FormSectionColumns title="Expiration">
<Select
onValueChange={(value: string) => {
setValue("expiration", value);
}}
name="expiration"
defaultValue={initData?.expiration || "-1"}
>
<SelectTrigger className="w-full shadow-inner">
<SelectValue placeholder="Select a time" />
</SelectTrigger>
<SelectContent>
{EXPIRATION_ENUMS.map((e) => (
<SelectItem key={e.value} value={e.value}>
{e.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="p-1 text-[13px] text-muted-foreground">
Expiration time, default for never.
</p>
</FormSectionColumns>
{/* <div>
<div className="items-center justify-start gap-4 md:flex">
<FormSectionColumns title="Expiration">
<Select
onValueChange={(value: string) => {
setValue("expiration", value);
}}
name="expiration"
defaultValue={initData?.expiration || "-1"}
>
<SelectTrigger className="w-full shadow-inner">
<SelectValue placeholder="Select a time" />
</SelectTrigger>
<SelectContent>
{EXPIRATION_ENUMS.map((e) => (
<SelectItem key={e.value} value={e.value}>
{e.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="p-1 text-[13px] text-muted-foreground">
Expiration time, default for never.
</p>
</FormSectionColumns>
{/* <div>
<p className="text-sm text-gray-700 dark:text-white">
Your Final URL:
</p>
@@ -270,7 +271,7 @@ export function UrlForm({
{getValues("prefix")}/s/{getValues("url")}
</p>
</div> */}
{/* <FormSectionColumns title="Visible">
{/* <FormSectionColumns title="Visible">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="visible">
Visible
@@ -287,7 +288,7 @@ export function UrlForm({
Public or private short url.
</p>
</FormSectionColumns> */}
{/* <FormSectionColumns title="Active">
{/* <FormSectionColumns title="Active">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="active">
Active
@@ -303,46 +304,47 @@ export function UrlForm({
Enable or disable short url.
</p>
</FormSectionColumns> */}
</div>
</div>
{/* Action buttons */}
<div className="mt-3 flex justify-end gap-3">
{type === "edit" && (
{/* Action buttons */}
<div className="mt-3 flex justify-end gap-3">
{type === "edit" && (
<Button
type="button"
variant="destructive"
className="mr-auto w-[80px] px-0"
onClick={() => handleDeleteUrl()}
disabled={isDeleting}
>
{isDeleting ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<p>Delete</p>
)}
</Button>
)}
<Button
type="button"
variant="destructive"
className="mr-auto w-[80px] px-0"
onClick={() => handleDeleteUrl()}
disabled={isDeleting}
type="reset"
variant="outline"
className="w-[80px] px-0"
onClick={() => setShowForm(false)}
>
{isDeleting ? (
Cancle
</Button>
<Button
type="submit"
variant="blue"
disabled={isPending}
className="w-[80px] shrink-0 px-0"
>
{isPending ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<p>Delete</p>
<p>{type === "edit" ? "Update" : "Save"}</p>
)}
</Button>
)}
<Button
type="reset"
variant="outline"
className="w-[80px] px-0"
onClick={() => setShowForm(false)}
>
Cancle
</Button>
<Button
type="submit"
variant="blue"
disabled={isPending}
className="w-[80px] shrink-0 px-0"
>
{isPending ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<p>{type === "edit" ? "Update" : "Save"}</p>
)}
</Button>
</div>
</form>
</div>
</form>
</div>
);
}
-68
View File
@@ -1,68 +0,0 @@
import {
Body,
Button,
Container,
Head,
Hr,
Html, Preview,
Section,
Tailwind,
Text
} from '@react-email/components';
import { Icons } from '../components/shared/icons';
type MagicLinkEmailProps = {
actionUrl: string
firstName: string
mailType: "login" | "register"
siteName: string
}
export const MagicLinkEmail = ({
firstName = '',
actionUrl,
mailType,
siteName
}: MagicLinkEmailProps) => (
<Html>
<Head />
<Preview>
The sales intelligence platform that helps you uncover qualified leads.
</Preview>
<Tailwind>
<Body className="bg-white font-sans">
<Container className="mx-auto py-5 pb-12">
<Icons.logo className="m-auto block size-10" />
<Text className="text-base">
Hi {firstName},
</Text>
<Text className="text-base">
Welcome to {siteName} ! Click the link below to {mailType === "login" ? "sign in to" : "activate"} your account.
</Text>
<Section className="my-5 text-center">
<Button
className="inline-block rounded-md bg-zinc-900 px-4 py-2 text-base text-white no-underline"
href={actionUrl}
>
{mailType === "login" ? "Sign in" : "Activate Account"}
</Button>
</Section>
<Text className="text-base">
This link expires in 24 hours and can only be used once.
</Text>
{mailType === "login" ? (
<Text className="text-base">
If you did not try to log into your account, you can safely ignore it.
</Text>
) : null}
<Hr className="my-4 border-t-2 border-gray-300" />
<Text className="text-sm text-gray-600">
123 Code Street, Suite 404, Devtown, CA 98765
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
export default MagicLinkEmail;
+1 -1
View File
File diff suppressed because one or more lines are too long