chore: better url list
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -41,3 +41,5 @@ next-env.d.ts
|
||||
|
||||
.vscode
|
||||
.contentlayer
|
||||
|
||||
public/sw.js.map
|
||||
@@ -81,7 +81,7 @@ export default function DomainList({ user, action }: DomainListProps) {
|
||||
const [currentEditDomain, setCurrentEditDomain] =
|
||||
useState<DomainFormData | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [pageSize, setPageSize] = useState(15);
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
slug: "",
|
||||
target: "",
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function PlanList({ user, action }: PlanListProps) {
|
||||
const [currentEditPlan, setCurrentEditPlan] =
|
||||
useState<PlanQuotaFormData | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [pageSize, setPageSize] = useState(15);
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
slug: "",
|
||||
target: "",
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
|
||||
export default function DashboardUrlsLoading() {
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader
|
||||
heading="Manage Short URLs"
|
||||
text="List and manage short urls"
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4 lg:grid-cols-4">
|
||||
<Skeleton className="h-[102px] w-full rounded-lg" />
|
||||
<Skeleton className="h-[102px] w-full rounded-lg" />
|
||||
|
||||
@@ -2,7 +2,6 @@ import { redirect } from "next/navigation";
|
||||
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { constructMetadata } from "@/lib/utils";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
|
||||
import UserUrlsList from "../../dashboard/urls/url-list";
|
||||
|
||||
@@ -18,12 +17,6 @@ export default async function DashboardPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader
|
||||
heading="Manage Short URLs"
|
||||
text="List and manage short urls"
|
||||
link="/docs/short-urls"
|
||||
linkText="short urls"
|
||||
/>
|
||||
<UserUrlsList
|
||||
user={{
|
||||
id: user.id,
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function UsersList({ user }: UrlListProps) {
|
||||
const [isShowForm, setShowForm] = useState(false);
|
||||
const [currentEditUser, setcurrentEditUser] = useState<User | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [pageSize, setPageSize] = useState(15);
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
email: "",
|
||||
userName: "",
|
||||
|
||||
@@ -88,7 +88,7 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
|
||||
const [currentEditRecord, setCurrentEditRecord] =
|
||||
useState<UserRecordFormData | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [pageSize, setPageSize] = useState(15);
|
||||
const isAdmin = action.includes("/admin");
|
||||
|
||||
const t = useTranslations("List");
|
||||
@@ -154,7 +154,7 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
|
||||
<div className="grid gap-2">
|
||||
<CardTitle>{t("Subdomain List")}</CardTitle>
|
||||
<CardDescription className="hidden text-balance sm:block">
|
||||
{t("Before using please read the")}{" "}
|
||||
{t("Before using please read the")}
|
||||
<Link
|
||||
target="_blank"
|
||||
className="font-semibold text-yellow-600 after:content-['↗'] hover:underline"
|
||||
@@ -162,14 +162,14 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
|
||||
>
|
||||
{t("legitimacy review")}
|
||||
</Link>
|
||||
. {t("See")}{" "}
|
||||
. {t("See")}
|
||||
<Link
|
||||
target="_blank"
|
||||
className="text-blue-500 hover:underline"
|
||||
href="/docs/examples/vercel"
|
||||
>
|
||||
{t("examples")}
|
||||
</Link>{" "}
|
||||
</Link>
|
||||
{t("for more usage")}.
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
@@ -157,7 +157,10 @@ export const UrlExporter: React.FC<{
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="flex items-center gap-2" variant={"outline"}>
|
||||
<Button
|
||||
className="flex items-center gap-2 text-nowrap"
|
||||
variant={"outline"}
|
||||
>
|
||||
{t("Export")}
|
||||
<Icons.chevronDown className="size-4" />
|
||||
</Button>
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
|
||||
export default function DashboardUrlsLoading() {
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader
|
||||
heading="Manage Short URLs"
|
||||
text="List and manage short urls"
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4 lg:grid-cols-4">
|
||||
<Skeleton className="h-[102px] w-full rounded-lg" />
|
||||
<Skeleton className="h-[102px] w-full rounded-lg" />
|
||||
|
||||
@@ -2,7 +2,6 @@ import { redirect } from "next/navigation";
|
||||
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { constructMetadata } from "@/lib/utils";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
|
||||
import UserUrlsList from "./url-list";
|
||||
|
||||
@@ -18,12 +17,6 @@ export default async function DashboardPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader
|
||||
heading="Manage Short URLs"
|
||||
text="List and manage short urls"
|
||||
link="/docs/short-urls"
|
||||
linkText="short urls"
|
||||
/>
|
||||
<UserUrlsList
|
||||
user={{
|
||||
id: user.id,
|
||||
|
||||
@@ -30,6 +30,13 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Modal } from "@/components/ui/modal";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
@@ -113,7 +120,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
null,
|
||||
);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [pageSize, setPageSize] = useState(15);
|
||||
const [isShowStats, setShowStats] = useState(false);
|
||||
const [isShowQrcode, setShowQrcode] = useState(false);
|
||||
const [selectedUrl, setSelectedUrl] = useState<ShortUrlFormData | null>(null);
|
||||
@@ -197,80 +204,101 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
</EmptyPlaceholder>
|
||||
);
|
||||
|
||||
const rendeSeachInputs = () => (
|
||||
<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
|
||||
className="h-8 text-xs md:text-xs"
|
||||
placeholder={t("Search by slug") + "..."}
|
||||
value={searchParams.slug}
|
||||
onChange={(e) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
slug: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{searchParams.slug && (
|
||||
<Button
|
||||
className="absolute right-2 top-1/2 h-6 -translate-y-1/2 rounded-full px-1 text-gray-500 hover:text-gray-700"
|
||||
onClick={() => setSearchParams({ ...searchParams, slug: "" })}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<Icons.close className="size-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
const renderSearchInputs = () => {
|
||||
const [searchType, setSearchType] = useState<
|
||||
"slug" | "target" | "userName"
|
||||
>("slug");
|
||||
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
className="h-8 text-xs md:text-xs"
|
||||
placeholder={t("Search by target") + "..."}
|
||||
value={searchParams.target}
|
||||
onChange={(e) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
target: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{searchParams.target && (
|
||||
<Button
|
||||
className="absolute right-2 top-1/2 h-6 -translate-y-1/2 rounded-full px-1 text-gray-500 hover:text-gray-700"
|
||||
onClick={() => setSearchParams({ ...searchParams, target: "" })}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<Icons.close className="size-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
const getCurrentSearchValue = () => {
|
||||
switch (searchType) {
|
||||
case "slug":
|
||||
return searchParams.slug;
|
||||
case "target":
|
||||
return searchParams.target;
|
||||
case "userName":
|
||||
return searchParams.userName;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
{user.role === "ADMIN" && (
|
||||
<div className="relative w-full">
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
slug: searchType === "slug" ? value : "",
|
||||
target: searchType === "target" ? value : "",
|
||||
userName: searchType === "userName" ? value : "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearSearch = () => {
|
||||
handleSearchChange("");
|
||||
};
|
||||
|
||||
const getPlaceholder = () => {
|
||||
switch (searchType) {
|
||||
case "slug":
|
||||
return t("Search by slug") + "...";
|
||||
case "target":
|
||||
return t("Search by target") + "...";
|
||||
case "userName":
|
||||
return t("Search by username") + "...";
|
||||
default:
|
||||
return t("Search") + "...";
|
||||
}
|
||||
};
|
||||
|
||||
const searchOptions = [
|
||||
{ value: "slug", label: t("Link Slug") },
|
||||
{ value: "target", label: t("Link Target") },
|
||||
...(user.role === "ADMIN"
|
||||
? [{ value: "userName", label: t("Username") }]
|
||||
: []),
|
||||
];
|
||||
|
||||
const currentSearchValue = getCurrentSearchValue();
|
||||
|
||||
return (
|
||||
<div className="ml-auto flex items-center">
|
||||
<Select
|
||||
value={searchType}
|
||||
onValueChange={(value: typeof searchType) => setSearchType(value)}
|
||||
>
|
||||
<SelectTrigger className="h-10 w-[85px] rounded-r-none bg-muted text-sm">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{searchOptions.map((option) => (
|
||||
<SelectItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className="text-sm"
|
||||
>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
className="h-8 text-xs md:text-xs"
|
||||
placeholder={t("Search by username") + "..."}
|
||||
value={searchParams.userName}
|
||||
onChange={(e) => {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
userName: e.target.value,
|
||||
});
|
||||
}}
|
||||
className="h-10 rounded-l-none border-l-0 pr-8 text-sm"
|
||||
placeholder={getPlaceholder()}
|
||||
value={currentSearchValue}
|
||||
onChange={(e) => handleSearchChange(e.target.value)}
|
||||
/>
|
||||
{searchParams.userName && (
|
||||
{currentSearchValue && (
|
||||
<Button
|
||||
className="absolute right-2 top-1/2 h-6 -translate-y-1/2 rounded-full px-1 text-gray-500 hover:text-gray-700"
|
||||
onClick={() => setSearchParams({ ...searchParams, userName: "" })}
|
||||
variant={"ghost"}
|
||||
onClick={handleClearSearch}
|
||||
variant="ghost"
|
||||
>
|
||||
<Icons.close className="size-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const rendeClicks = (short: ShortUrlFormData) => (
|
||||
<>
|
||||
@@ -650,13 +678,16 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
className={cn("rounded-lg", pathname === "/dashboard" && "border p-6")}
|
||||
className={cn(
|
||||
"space-y-3 rounded-lg",
|
||||
pathname === "/dashboard" && "border p-6",
|
||||
)}
|
||||
value={currentView}
|
||||
>
|
||||
{/* Tabs */}
|
||||
<div className="mb-4 flex items-center justify-between gap-2">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
{pathname === "/dashboard" && (
|
||||
<h2 className="mr-3 text-lg font-semibold">{t("Short URLs")}</h2>
|
||||
<h2 className="mr-auto text-lg font-semibold">{t("Short URLs")}</h2>
|
||||
)}
|
||||
<TabsList>
|
||||
<TabsTrigger onClick={() => setCurrentView("List")} value="List">
|
||||
@@ -678,8 +709,8 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
{/* <p>Total: {data?.total || 0}</p> */}
|
||||
<div className="ml-auto flex items-center justify-end gap-3">
|
||||
<div className="flex items-center justify-end gap-3">
|
||||
{renderSearchInputs()}
|
||||
<UrlExporter data={data?.list || []} />
|
||||
<Button
|
||||
variant={"outline"}
|
||||
@@ -710,14 +741,12 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabsContent className="space-y-3" value="List">
|
||||
{pathname !== "/dashboard" && <UrlStatus action={action} />}
|
||||
{rendeSeachInputs()}
|
||||
{pathname !== "/dashboard" && <UrlStatus action={action} />}
|
||||
|
||||
<TabsContent className="mt-0 space-y-3" value="List">
|
||||
{rendeList()}
|
||||
</TabsContent>
|
||||
<TabsContent className="space-y-3" value="Grid">
|
||||
{pathname !== "/dashboard" && <UrlStatus action={action} />}
|
||||
{rendeSeachInputs()}
|
||||
<TabsContent className="mt-0 space-y-3" value="Grid">
|
||||
{rendeGrid()}
|
||||
</TabsContent>
|
||||
{selectedUrl?.id && (
|
||||
|
||||
@@ -14,5 +14,5 @@ export default async function SentEmailPage() {
|
||||
|
||||
if (!user?.id) redirect("/login");
|
||||
|
||||
return <SendsEmailList />;
|
||||
return <SendsEmailList user={user as any} />;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function EmailList({
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [isAutoRefresh, setIsAutoRefresh] = useState(false);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [pageSize, setPageSize] = useState(15);
|
||||
const [selectedEmails, setSelectedEmails] = useState<string[]>([]);
|
||||
const [showMutiCheckBox, setShowMutiCheckBox] = useState(false);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState, useTransition } from "react";
|
||||
import { useEffect, useState, useTransition } from "react";
|
||||
import { User, UserEmail } from "@prisma/client";
|
||||
import randomName from "@scaleway/random-name";
|
||||
import {
|
||||
@@ -45,7 +45,6 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "../ui/tooltip";
|
||||
import { SendEmailModal } from "./SendEmailModal";
|
||||
import SendsEmailList from "./SendsEmailList";
|
||||
|
||||
interface EmailSidebarProps {
|
||||
user: User;
|
||||
@@ -83,7 +82,7 @@ export default function EmailSidebar({
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [onlyUnread, setOnlyUnread] = useState(false);
|
||||
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [pageSize, setPageSize] = useState(15);
|
||||
|
||||
const { data, isLoading, error, mutate } = useSWR<{
|
||||
list: UserEmailList[];
|
||||
@@ -338,6 +337,7 @@ export default function EmailSidebar({
|
||||
"bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700":
|
||||
onlyUnread,
|
||||
},
|
||||
{ "col-span-2": user.role !== "ADMIN" },
|
||||
)}
|
||||
onClick={() => {
|
||||
setOnlyUnread(!onlyUnread);
|
||||
@@ -363,28 +363,30 @@ export default function EmailSidebar({
|
||||
</div>
|
||||
|
||||
{/* Admin Mode */}
|
||||
<div
|
||||
onClick={() => setAdminModel(!isAdminModel)}
|
||||
className={cn(
|
||||
"flex cursor-pointer flex-col items-center gap-1 rounded-md bg-neutral-100 px-1 pb-1 pt-2 transition-colors hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700",
|
||||
{
|
||||
"bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700":
|
||||
isAdminModel,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<Icons.lock className="size-3" />
|
||||
<p className="line-clamp-1 text-start font-medium">
|
||||
{t("Admin Mode")}
|
||||
</p>
|
||||
{user.role === "ADMIN" && (
|
||||
<div
|
||||
onClick={() => setAdminModel(!isAdminModel)}
|
||||
className={cn(
|
||||
"flex cursor-pointer flex-col items-center gap-1 rounded-md bg-neutral-100 px-1 pb-1 pt-2 transition-colors hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700",
|
||||
{
|
||||
"bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700":
|
||||
isAdminModel,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<Icons.lock className="size-3" />
|
||||
<p className="line-clamp-1 text-start font-medium">
|
||||
{t("Admin Mode")}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
className="scale-90"
|
||||
checked={isAdminModel}
|
||||
onCheckedChange={(v) => setAdminModel(v)}
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
className="scale-90"
|
||||
checked={isAdminModel}
|
||||
onCheckedChange={(v) => setAdminModel(v)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { UserSendEmail } from "@prisma/client";
|
||||
import { User, UserSendEmail } from "@prisma/client";
|
||||
import { useTranslations } from "next-intl";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { cn, fetcher, formatDate, htmlToText, nFormatter } from "@/lib/utils";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
@@ -19,9 +18,9 @@ import {
|
||||
} from "../ui/collapsible";
|
||||
import { Switch } from "../ui/switch";
|
||||
|
||||
export default function SendsEmailList({}: {}) {
|
||||
export default function SendsEmailList({ user }: { user: User }) {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [pageSize, setPageSize] = useState(15);
|
||||
const [isAdminModel, setAdminModel] = useState(false);
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
@@ -72,28 +71,30 @@ export default function SendsEmailList({}: {}) {
|
||||
</div>
|
||||
|
||||
{/* Admin Mode */}
|
||||
<div
|
||||
onClick={() => setAdminModel(!isAdminModel)}
|
||||
className={cn(
|
||||
"flex cursor-pointer flex-col items-center gap-1 rounded-md bg-neutral-100 px-1 pb-1 pt-2 transition-colors hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700",
|
||||
{
|
||||
"bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700":
|
||||
isAdminModel,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<Icons.lock className="size-3" />
|
||||
<p className="line-clamp-1 text-start font-medium">
|
||||
{t("Admin Mode")}
|
||||
</p>
|
||||
{user.role === "ADMIN" && (
|
||||
<div
|
||||
onClick={() => setAdminModel(!isAdminModel)}
|
||||
className={cn(
|
||||
"flex cursor-pointer flex-col items-center gap-1 rounded-md bg-neutral-100 px-1 pb-1 pt-2 transition-colors hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700",
|
||||
{
|
||||
"bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-gray-700":
|
||||
isAdminModel,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<Icons.lock className="size-3" />
|
||||
<p className="line-clamp-1 text-start font-medium">
|
||||
{t("Admin Mode")}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
className="scale-90"
|
||||
checked={isAdminModel}
|
||||
onCheckedChange={(v) => setAdminModel(v)}
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
className="scale-90"
|
||||
checked={isAdminModel}
|
||||
onCheckedChange={(v) => setAdminModel(v)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-4 flex items-center justify-between gap-4">
|
||||
<Input
|
||||
|
||||
@@ -40,7 +40,7 @@ export function PaginationWrapper({
|
||||
size?: "small" | "medium" | "large";
|
||||
}) {
|
||||
// Page size options
|
||||
const pageSizeOptions = [10, 20, 50, 100];
|
||||
const pageSizeOptions = [10, 15, 20, 50, 100];
|
||||
|
||||
// Calculate total pages based on pageSize
|
||||
const totalPages = Math.ceil(total / pageSize);
|
||||
|
||||
@@ -233,7 +233,10 @@
|
||||
"Email Service Configs": "Email Service Configs",
|
||||
"Email Provider": "Email Provider",
|
||||
"Resend API Key": "Resend API Key",
|
||||
"Brevo API Key": "Brevo API Key"
|
||||
"Brevo API Key": "Brevo API Key",
|
||||
"Link Slug": "Slug",
|
||||
"Link Target": "Target",
|
||||
"Username": "Username"
|
||||
},
|
||||
"Components": {
|
||||
"Dashboard": "Dashboard",
|
||||
|
||||
@@ -233,7 +233,10 @@
|
||||
"Email Service Configs": "邮件服务商",
|
||||
"Email Provider": "邮件提供商",
|
||||
"Resend API Key": "Resend 密钥",
|
||||
"Brevo API Key": "Brevo 密钥"
|
||||
"Brevo API Key": "Brevo 密钥",
|
||||
"Link Slug": "后缀",
|
||||
"Link Target": "目标链接",
|
||||
"Username": "用户名"
|
||||
},
|
||||
"Components": {
|
||||
"Dashboard": "用户面板",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user