Improved user interface translations and clarity in Simplified
This commit is contained in:
@@ -5,6 +5,7 @@ import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { User } from "@prisma/client";
|
||||
import { PenLine, RefreshCwIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
|
||||
@@ -101,6 +102,7 @@ function TableColumnSekleton() {
|
||||
export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
const pathname = usePathname();
|
||||
const { isMobile } = useMediaQuery();
|
||||
const t = useTranslations("List");
|
||||
const [currentView, setCurrentView] = useState<string>("List");
|
||||
const [isShowForm, setShowForm] = useState(false);
|
||||
const [formType, setFormType] = useState<FormType>("add");
|
||||
@@ -197,7 +199,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
className="h-8 text-xs md:text-xs"
|
||||
placeholder="Search by slug..."
|
||||
placeholder={t("Search by slug") + "..."}
|
||||
value={searchParams.slug}
|
||||
onChange={(e) => {
|
||||
setSearchParams({
|
||||
@@ -220,7 +222,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
className="h-8 text-xs md:text-xs"
|
||||
placeholder="Search by target..."
|
||||
placeholder={t("Search by target") + "..."}
|
||||
value={searchParams.target}
|
||||
onChange={(e) => {
|
||||
setSearchParams({
|
||||
@@ -244,7 +246,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
className="h-8 text-xs md:text-xs"
|
||||
placeholder="Search by user name..."
|
||||
placeholder={t("Search by username") + "..."}
|
||||
value={searchParams.userName}
|
||||
onChange={(e) => {
|
||||
setSearchParams({
|
||||
@@ -299,28 +301,28 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
<TableHeader className="bg-gray-100/50 dark:bg-primary-foreground">
|
||||
<TableRow className="grid grid-cols-3 items-center sm:grid-cols-11">
|
||||
<TableHead className="col-span-1 flex items-center font-bold sm:col-span-2">
|
||||
Slug
|
||||
{t("Slug")}
|
||||
</TableHead>
|
||||
<TableHead className="col-span-1 flex items-center font-bold sm:col-span-2">
|
||||
Target
|
||||
{t("Target")}
|
||||
</TableHead>
|
||||
<TableHead className="col-span-1 hidden items-center font-bold sm:flex">
|
||||
User
|
||||
{t("User")}
|
||||
</TableHead>
|
||||
<TableHead className="col-span-1 hidden items-center font-bold sm:flex">
|
||||
Enabled
|
||||
{t("Enabled")}
|
||||
</TableHead>
|
||||
<TableHead className="col-span-1 hidden items-center font-bold sm:flex">
|
||||
Expiration
|
||||
{t("Expiration")}
|
||||
</TableHead>
|
||||
<TableHead className="col-span-1 hidden items-center font-bold sm:flex">
|
||||
Clicks
|
||||
{t("Clicks")}
|
||||
</TableHead>
|
||||
<TableHead className="col-span-1 hidden items-center font-bold sm:flex">
|
||||
Updated
|
||||
{t("Updated")}
|
||||
</TableHead>
|
||||
<TableHead className="col-span-1 flex items-center font-bold sm:col-span-2">
|
||||
Actions
|
||||
{t("Actions")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -408,7 +410,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
setShowForm(!isShowForm);
|
||||
}}
|
||||
>
|
||||
<p className="hidden sm:block">Edit</p>
|
||||
<p className="hidden sm:block">{t("Edit")}</p>
|
||||
<PenLine className="mx-0.5 size-4 sm:ml-1 sm:size-3" />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -662,7 +664,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
{/* Tabs */}
|
||||
<div className="mb-4 flex items-center justify-between gap-2">
|
||||
{pathname === "/dashboard" && (
|
||||
<h2 className="mr-3 text-lg font-semibold">Short URLs</h2>
|
||||
<h2 className="mr-3 text-lg font-semibold">{t("Short URLs")}</h2>
|
||||
)}
|
||||
<TabsList>
|
||||
<TabsTrigger onClick={() => setCurrentView("List")} value="List">
|
||||
@@ -715,7 +717,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
}}
|
||||
>
|
||||
<Icons.add className="size-4" />
|
||||
<span className="hidden sm:inline">Add URL</span>
|
||||
<span className="hidden sm:inline">{t("Add URL")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Dispatch, SetStateAction, useTransition } from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { User } from "@prisma/client";
|
||||
import { Sparkles } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import useSWR from "swr";
|
||||
@@ -51,6 +52,7 @@ export function UrlForm({
|
||||
}: RecordFormProps) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [isDeleting, startDeleteTransition] = useTransition();
|
||||
const t = useTranslations("List");
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
@@ -166,14 +168,14 @@ export function UrlForm({
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded-t-lg bg-muted px-4 py-2 text-lg font-semibold">
|
||||
{type === "add" ? "Create" : "Edit"} short link
|
||||
{type === "add" ? t("Create short link") : t("Edit short link")}
|
||||
</div>
|
||||
<form className="p-4" onSubmit={onSubmit}>
|
||||
<div className="items-center justify-start gap-4 md:flex">
|
||||
<FormSectionColumns title="Target URL" required>
|
||||
<FormSectionColumns title={t("Target URL")} required>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Label className="sr-only" htmlFor="target">
|
||||
Target
|
||||
{t("Target")}
|
||||
</Label>
|
||||
<Input
|
||||
id="target"
|
||||
@@ -189,12 +191,12 @@ export function UrlForm({
|
||||
</p>
|
||||
) : (
|
||||
<p className="pb-0.5 text-[13px] text-muted-foreground">
|
||||
Required. https://your-origin-url
|
||||
{t("Required")}. https://your-origin-url
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</FormSectionColumns>
|
||||
<FormSectionColumns title="Short Link" required>
|
||||
<FormSectionColumns title={t("Short Link")} required>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Label className="sr-only" htmlFor="url">
|
||||
Url
|
||||
@@ -258,7 +260,8 @@ export function UrlForm({
|
||||
</p>
|
||||
) : (
|
||||
<p className="pb-0.5 text-[13px] text-muted-foreground">
|
||||
A random url suffix. Final url like「wr.do/s/suffix」
|
||||
{t("A random url suffix")}. {t("Final url like")}
|
||||
「wr.do/s/suffix」
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -266,10 +269,10 @@ export function UrlForm({
|
||||
</div>
|
||||
|
||||
<div className="items-center justify-start gap-4 md:flex">
|
||||
<FormSectionColumns title="Password (Optional)">
|
||||
<FormSectionColumns title={`${t("Password")} (${t("Optional")})`}>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<Label className="sr-only" htmlFor="password">
|
||||
Password
|
||||
{t("Password")}
|
||||
</Label>
|
||||
<Input
|
||||
id="password"
|
||||
@@ -277,7 +280,7 @@ export function UrlForm({
|
||||
size={32}
|
||||
maxLength={6}
|
||||
type="password"
|
||||
placeholder="Enter 6 character password"
|
||||
placeholder={t("Enter 6 character password")}
|
||||
{...register("password")}
|
||||
/>
|
||||
</div>
|
||||
@@ -288,12 +291,12 @@ export function UrlForm({
|
||||
</p>
|
||||
) : (
|
||||
<p className="pb-0.5 text-[13px] text-muted-foreground">
|
||||
Optional. If you want to protect your link.
|
||||
{t("Optional")}. {t("If you want to protect your link")}.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</FormSectionColumns>
|
||||
<FormSectionColumns title="Expiration" required>
|
||||
<FormSectionColumns title={t("Expiration")} required>
|
||||
<Select
|
||||
onValueChange={(value: string) => {
|
||||
setValue("expiration", value);
|
||||
@@ -313,7 +316,7 @@ export function UrlForm({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="p-1 text-[13px] text-muted-foreground">
|
||||
Expiration time, default for never.
|
||||
{t("Expiration time, default for never")}.
|
||||
</p>
|
||||
</FormSectionColumns>
|
||||
</div>
|
||||
@@ -331,7 +334,7 @@ export function UrlForm({
|
||||
{isDeleting ? (
|
||||
<Icons.spinner className="size-4 animate-spin" />
|
||||
) : (
|
||||
<p>Delete</p>
|
||||
<p>{t("Delete")}</p>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
@@ -341,7 +344,7 @@ export function UrlForm({
|
||||
className="w-[80px] px-0"
|
||||
onClick={() => setShowForm(false)}
|
||||
>
|
||||
Cancle
|
||||
{t("Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -352,7 +355,7 @@ export function UrlForm({
|
||||
{isPending ? (
|
||||
<Icons.spinner className="size-4 animate-spin" />
|
||||
) : (
|
||||
<p>{type === "edit" ? "Update" : "Save"}</p>
|
||||
<p>{type === "edit" ? t("Update") : t("Save")}</p>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,9 @@ import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { SidebarNavItem } from "@/types";
|
||||
import { Menu, PanelLeftClose, PanelRightClose } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Link } from "next-view-transitions";
|
||||
import { name, version } from "package.json";
|
||||
import pkg from "package.json";
|
||||
|
||||
import { siteConfig } from "@/config/site";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -28,6 +29,7 @@ interface DashboardSidebarProps {
|
||||
}
|
||||
|
||||
export function DashboardSidebar({ links }: DashboardSidebarProps) {
|
||||
const t = useTranslations("System");
|
||||
const path = usePathname();
|
||||
|
||||
const { isTablet } = useMediaQuery();
|
||||
@@ -98,7 +100,7 @@ export function DashboardSidebar({ links }: DashboardSidebarProps) {
|
||||
>
|
||||
{isSidebarExpanded ? (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{section.title}
|
||||
{t(section.title)}
|
||||
</p>
|
||||
) : (
|
||||
<div className="h-4" />
|
||||
@@ -122,7 +124,7 @@ export function DashboardSidebar({ links }: DashboardSidebarProps) {
|
||||
)}
|
||||
>
|
||||
<Icon className="size-5" />
|
||||
{item.title}
|
||||
{t(item.title)}
|
||||
{item.badge && (
|
||||
<Badge className="ml-auto flex size-5 shrink-0 items-center justify-center rounded-full">
|
||||
{item.badge}
|
||||
@@ -150,7 +152,7 @@ export function DashboardSidebar({ links }: DashboardSidebarProps) {
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
{item.title}
|
||||
{t(item.title)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -172,9 +174,9 @@ export function DashboardSidebar({ links }: DashboardSidebarProps) {
|
||||
rel="noreferrer"
|
||||
className="font-medium underline-offset-2 hover:underline"
|
||||
>
|
||||
{name}
|
||||
{pkg.name}
|
||||
</Link>
|
||||
v{version}
|
||||
v{pkg.version}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -189,6 +191,7 @@ export function MobileSheetSidebar({ links }: DashboardSidebarProps) {
|
||||
const path = usePathname();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { isSm, isMobile } = useMediaQuery();
|
||||
const t = useTranslations("System");
|
||||
|
||||
if (isSm || isMobile) {
|
||||
return (
|
||||
@@ -229,7 +232,7 @@ export function MobileSheetSidebar({ links }: DashboardSidebarProps) {
|
||||
className="flex flex-col gap-0.5"
|
||||
>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{section.title}
|
||||
{t(section.title)}
|
||||
</p>
|
||||
|
||||
{section.items.map((item) => {
|
||||
@@ -253,7 +256,7 @@ export function MobileSheetSidebar({ links }: DashboardSidebarProps) {
|
||||
)}
|
||||
>
|
||||
<Icon className="size-5" />
|
||||
{item.title}
|
||||
{t(item.title)}
|
||||
{item.badge && (
|
||||
<Badge className="ml-auto flex size-5 shrink-0 items-center justify-center rounded-full">
|
||||
{item.badge}
|
||||
@@ -276,9 +279,9 @@ export function MobileSheetSidebar({ links }: DashboardSidebarProps) {
|
||||
rel="noreferrer"
|
||||
className="font-medium underline-offset-2 hover:underline"
|
||||
>
|
||||
{name}
|
||||
{pkg.name}
|
||||
</Link>
|
||||
v{version}
|
||||
v{pkg.version}
|
||||
</p>
|
||||
|
||||
{/* <div className="mt-auto">
|
||||
|
||||
@@ -5,6 +5,7 @@ import Link from "next/link";
|
||||
import { useSelectedLayoutSegment } from "next/navigation";
|
||||
import { Menu, X } from "lucide-react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { docsConfig } from "@/config/docs";
|
||||
import { marketingConfig } from "@/config/marketing";
|
||||
@@ -16,6 +17,7 @@ import { Icons } from "@/components/shared/icons";
|
||||
import { ModeToggle } from "./mode-toggle";
|
||||
|
||||
export function NavMobile() {
|
||||
const t = useTranslations("System");
|
||||
const { data: session } = useSession();
|
||||
const [open, setOpen] = useState(false);
|
||||
const selectedLayout = useSelectedLayoutSegment();
|
||||
@@ -69,7 +71,7 @@ export function NavMobile() {
|
||||
onClick={() => setOpen(false)}
|
||||
className="flex w-full font-medium capitalize"
|
||||
>
|
||||
{title}
|
||||
{t(title)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
@@ -83,7 +85,7 @@ export function NavMobile() {
|
||||
onClick={() => setOpen(false)}
|
||||
className="flex w-full font-medium capitalize"
|
||||
>
|
||||
Admin
|
||||
{t("Admin")}
|
||||
</Link>
|
||||
</li>
|
||||
) : null}
|
||||
@@ -94,7 +96,7 @@ export function NavMobile() {
|
||||
onClick={() => setOpen(false)}
|
||||
className="flex w-full font-medium capitalize"
|
||||
>
|
||||
Dashboard
|
||||
{t("Dashboard")}
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
@@ -106,7 +108,7 @@ export function NavMobile() {
|
||||
onClick={() => setOpen(false)}
|
||||
className="flex w-full font-medium capitalize"
|
||||
>
|
||||
Sign in
|
||||
{t("Sign in")}
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import { useTheme } from "next-themes"
|
||||
import * as React from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Icons } from "@/components/shared/icons"
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Icons } from "@/components/shared/icons";
|
||||
|
||||
export function ModeToggle() {
|
||||
const { setTheme } = useTheme()
|
||||
const { setTheme } = useTheme();
|
||||
const t = useTranslations("System");
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -21,23 +23,23 @@ export function ModeToggle() {
|
||||
<Button variant="ghost" size="sm" className="size-8 px-0">
|
||||
<Icons.sun className="rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Icons.moon className="absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
<span className="sr-only">{t("Toggle theme")}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||
<Icons.sun className="mr-2 size-4" />
|
||||
<span>Light</span>
|
||||
<span>{t("Light")}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||
<Icons.moon className="mr-2 size-4" />
|
||||
<span>Dark</span>
|
||||
<span>{t("Dark")}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||
<Icons.laptop className="mr-2 size-4" />
|
||||
<span>System</span>
|
||||
<span>{t("System")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useSelectedLayoutSegment } from "next/navigation";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { docsConfig } from "@/config/docs";
|
||||
import { marketingConfig } from "@/config/marketing";
|
||||
@@ -10,13 +10,11 @@ import { siteConfig } from "@/config/site";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useScroll } from "@/hooks/use-scroll";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { DocsSearch } from "@/components/docs/search";
|
||||
import { Icons } from "@/components/shared/icons";
|
||||
import MaxWidthWrapper from "@/components/shared/max-width-wrapper";
|
||||
|
||||
import { ModeToggle } from "./mode-toggle";
|
||||
import { UserAccountNav } from "./user-account-nav";
|
||||
|
||||
interface NavBarProps {
|
||||
scroll?: boolean;
|
||||
@@ -25,7 +23,6 @@ interface NavBarProps {
|
||||
|
||||
export function NavBar({ scroll = false }: NavBarProps) {
|
||||
const scrolled = useScroll(50);
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
const selectedLayout = useSelectedLayoutSegment();
|
||||
const documentation = selectedLayout === "docs";
|
||||
@@ -37,6 +34,8 @@ export function NavBar({ scroll = false }: NavBarProps) {
|
||||
const links =
|
||||
(selectedLayout && configMap[selectedLayout]) || marketingConfig.mainNav;
|
||||
|
||||
const t = useTranslations("System");
|
||||
|
||||
return (
|
||||
<header
|
||||
className={`sticky top-0 z-40 flex w-full justify-center bg-background/60 backdrop-blur-xl transition-all ${
|
||||
@@ -75,7 +74,7 @@ export function NavBar({ scroll = false }: NavBarProps) {
|
||||
item.disabled && "cursor-not-allowed opacity-80",
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
{t(item.title)}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
@@ -108,7 +107,7 @@ export function NavBar({ scroll = false }: NavBarProps) {
|
||||
className="hidden text-sm font-medium text-foreground/60 transition-colors hover:text-foreground/80 md:block"
|
||||
>
|
||||
<Button className="" variant="outline" size="sm" rounded="lg">
|
||||
Dashboard
|
||||
{t("Dashboard")}
|
||||
</Button>
|
||||
</Link>
|
||||
<div className="hidden md:flex">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from "react";
|
||||
import Link from "next/link";
|
||||
import { name, version } from "package.json";
|
||||
|
||||
import { footerLinks, siteConfig } from "@/config/site";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -56,16 +57,17 @@ export function SiteFooter({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||
|
||||
<div className="border-t py-4">
|
||||
<div className="container flex max-w-6xl items-center justify-between">
|
||||
<p className="font-mono text-sm text-muted-foreground/70">
|
||||
© 2024{" "}
|
||||
<p className="mx-3 mt-auto flex items-center gap-1 pb-3 pt-6 font-mono text-xs text-muted-foreground/90">
|
||||
© 2024
|
||||
<Link
|
||||
href={siteConfig.links.github}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium text-primary underline underline-offset-2"
|
||||
className="font-medium underline-offset-2 hover:underline"
|
||||
>
|
||||
oiov
|
||||
{name}
|
||||
</Link>
|
||||
v{version}
|
||||
. <br className="block sm:hidden" /> Built with{" "}
|
||||
<Link
|
||||
href="https://www.cloudflare.com?ref=wrdo"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { LayoutDashboard, Lock, LogOut, Settings } from "lucide-react";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Drawer } from "vaul";
|
||||
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
@@ -28,6 +29,7 @@ export function UserAccountNav() {
|
||||
};
|
||||
|
||||
const { isMobile } = useMediaQuery();
|
||||
const t = useTranslations("System");
|
||||
|
||||
if (!user)
|
||||
return (
|
||||
@@ -85,7 +87,7 @@ export function UserAccountNav() {
|
||||
className="flex w-full items-center gap-3 px-2.5 py-2"
|
||||
>
|
||||
<Lock className="size-4" />
|
||||
<p className="text-sm">Admin</p>
|
||||
<p className="text-sm">{t("Admin")}</p>
|
||||
</Link>
|
||||
</li>
|
||||
) : null}
|
||||
@@ -97,7 +99,7 @@ export function UserAccountNav() {
|
||||
className="flex w-full items-center gap-3 px-2.5 py-2"
|
||||
>
|
||||
<LayoutDashboard className="size-4" />
|
||||
<p className="text-sm">Dashboard</p>
|
||||
<p className="text-sm">{t("Dashboard")}</p>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
@@ -108,7 +110,7 @@ export function UserAccountNav() {
|
||||
className="flex w-full items-center gap-3 px-2.5 py-2"
|
||||
>
|
||||
<Settings className="size-4" />
|
||||
<p className="text-sm">Settings</p>
|
||||
<p className="text-sm">{t("Settings")}</p>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
@@ -123,7 +125,7 @@ export function UserAccountNav() {
|
||||
>
|
||||
<div className="flex w-full items-center gap-3 px-2.5 py-2">
|
||||
<LogOut className="size-4" />
|
||||
<p className="text-sm">Log out </p>
|
||||
<p className="text-sm">{t("Log out")}</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -170,7 +172,7 @@ export function UserAccountNav() {
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/admin" className="flex items-center space-x-2.5">
|
||||
<Lock className="size-4" />
|
||||
<p className="text-sm">Admin</p>
|
||||
<p className="text-sm">{t("Admin")}</p>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
@@ -178,7 +180,7 @@ export function UserAccountNav() {
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/dashboard" className="flex items-center space-x-2.5">
|
||||
<LayoutDashboard className="size-4" />
|
||||
<p className="text-sm">Dashboard</p>
|
||||
<p className="text-sm">{t("Dashboard")}</p>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -188,7 +190,7 @@ export function UserAccountNav() {
|
||||
className="flex items-center space-x-2.5"
|
||||
>
|
||||
<Settings className="size-4" />
|
||||
<p className="text-sm">Settings</p>
|
||||
<p className="text-sm">{t("Settings")}</p>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
@@ -203,7 +205,7 @@ export function UserAccountNav() {
|
||||
>
|
||||
<div className="flex items-center space-x-2.5">
|
||||
<LogOut className="size-4" />
|
||||
<p className="text-sm">Log out </p>
|
||||
<p className="text-sm">{t("Log out")}</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function HeroLanding({
|
||||
}: {
|
||||
userId: string | undefined;
|
||||
}) {
|
||||
const t = useTranslations("Common");
|
||||
const t = useTranslations("Landing");
|
||||
return (
|
||||
<section className="custom-bg relative space-y-6 py-12 sm:py-20 lg:py-24">
|
||||
<div className="container flex max-w-screen-lg flex-col items-center gap-5 text-center">
|
||||
@@ -30,23 +30,24 @@ export default function HeroLanding({
|
||||
"px-4",
|
||||
)}
|
||||
>
|
||||
<span className="mr-1">🚀</span>Deploy with
|
||||
<span className="mr-1">🚀</span>
|
||||
{t("deployWithVercel")}
|
||||
<span className="font-bold" style={{ fontFamily: "Bahamas Bold" }}>
|
||||
Vercel
|
||||
</span>
|
||||
now!
|
||||
|
||||
{t("now")}
|
||||
</Link>
|
||||
|
||||
<h1 className="text-balance font-satoshi text-[40px] font-black leading-[1.15] tracking-tight sm:text-5xl md:text-6xl md:leading-[1.15]">
|
||||
One platform powers{" "}
|
||||
{t("onePlatformPowers")}
|
||||
<span className="bg-gradient-to-r from-violet-600 via-blue-600 to-cyan-500 bg-clip-text text-transparent">
|
||||
endless solutions
|
||||
{t("endlessSolutions")}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p className="max-w-2xl text-balance text-muted-foreground sm:text-lg">
|
||||
Link shortening, domain hosting, email manager and <br />
|
||||
open api, everything you need to build better.
|
||||
{t("platformDescription")}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
@@ -63,7 +64,7 @@ export default function HeroLanding({
|
||||
"gap-2 bg-primary-foreground px-4 text-[15px] font-semibold text-primary hover:bg-slate-100",
|
||||
)}
|
||||
>
|
||||
<span>Documents</span>
|
||||
<span>{t("documents")}</span>
|
||||
<Icons.bookOpen className="size-4" />
|
||||
</Link>
|
||||
<Link
|
||||
@@ -74,7 +75,7 @@ export default function HeroLanding({
|
||||
"px-4 text-[15px] font-semibold",
|
||||
)}
|
||||
>
|
||||
<span>{userId ? t("Dashboard") : "Sign in for free"}</span>
|
||||
<span>{userId ? t("Dashboard") : t("signInForFree")}</span>
|
||||
{/* <Icons.arrowRight className="size-4" /> */}
|
||||
</Link>
|
||||
</div>
|
||||
@@ -86,13 +87,14 @@ export default function HeroLanding({
|
||||
}
|
||||
|
||||
export function LandingImages() {
|
||||
const t = useTranslations("Landing");
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto mt-10 w-full max-w-6xl px-6">
|
||||
<div className="my-14 flex flex-col items-center justify-around gap-10 md:flex-row-reverse">
|
||||
<Image
|
||||
className="size-[260px] rounded-lg transition-all hover:opacity-90 hover:shadow-xl"
|
||||
alt={"example"}
|
||||
alt={t("exampleImageAlt")}
|
||||
src="/_static/landing/link.svg"
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxMAAAsTAQCanBgAAACCSURBVBhXZYzBCgIxDEQnTdPau+hveBB/XtiLn+NJQdoNS2Orq6zuO0zgZRhSVbvegeAJGx7hvUeMAUSEzu1RUesEKuNkIgyrFaoFzB4i8i1+cDEwXHOuRc65lbVpe38XuPm+YMdIKa3WOj9F60vWcj0IOg8Xy7ngdDxgv9vO+h/gCZNAKuSRdQ2rAAAAAElFTkSuQmCC"
|
||||
@@ -101,21 +103,16 @@ export function LandingImages() {
|
||||
/>
|
||||
<div className="grids grids-dark px-2 py-4">
|
||||
<h3 className="mb-6 text-xl font-bold md:text-3xl">
|
||||
URL Shortening
|
||||
{t("urlShorteningTitle")}
|
||||
</h3>
|
||||
<p className="text-lg">
|
||||
📖 Instantly transform long, unwieldy URLs into short, memorable
|
||||
links that are easy to share. Enjoy built-in analytics to track
|
||||
clicks, monitor performance, and gain insights into your
|
||||
audience—all in real time.
|
||||
</p>
|
||||
<p className="text-lg">{t("urlShorteningDescription")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-around gap-10 md:flex-row">
|
||||
<Image
|
||||
className="size-[260px] rounded-lg transition-all hover:opacity-90 hover:shadow-xl"
|
||||
alt={"example"}
|
||||
alt={t("exampleImageAlt")}
|
||||
src="/_static/landing/hosting.svg"
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxMAAAsTAQCanBgAAACCSURBVBhXZYzBCgIxDEQnTdPau+hveBB/XtiLn+NJQdoNS2Orq6zuO0zgZRhSVbvegeAJGx7hvUeMAUSEzu1RUesEKuNkIgyrFaoFzB4i8i1+cDEwXHOuRc65lbVpe38XuPm+YMdIKa3WOj9F60vWcj0IOg8Xy7ngdDxgv9vO+h/gCZNAKuSRdQ2rAAAAAElFTkSuQmCC"
|
||||
@@ -124,21 +121,16 @@ export function LandingImages() {
|
||||
/>
|
||||
<div className="grids grids-dark px-2 py-4">
|
||||
<h3 className="mb-6 text-xl font-bold md:text-3xl">
|
||||
Free Subdomain Hosting
|
||||
{t("freeSubdomainHostingTitle")}
|
||||
</h3>
|
||||
<p className="text-lg">
|
||||
🎉 Kickstart your online presence with free, fully customizable
|
||||
subdomains. Whether you're launching a personal project or
|
||||
testing a business idea, get started quickly with no cost and
|
||||
reliable hosting you can trust.
|
||||
</p>
|
||||
<p className="text-lg">{t("freeSubdomainHostingDescription")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-14 flex flex-col items-center justify-around gap-10 md:flex-row-reverse">
|
||||
<Image
|
||||
className="size-[260px] rounded-lg transition-all hover:opacity-90 hover:shadow-xl"
|
||||
alt={"example"}
|
||||
alt={t("exampleImageAlt")}
|
||||
src="/_static/landing/email.svg"
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxMAAAsTAQCanBgAAACCSURBVBhXZYzBCgIxDEQnTdPau+hveBB/XtiLn+NJQdoNS2Orq6zuO0zgZRhSVbvegeAJGx7hvUeMAUSEzu1RUesEKuNkIgyrFaoFzB4i8i1+cDEwXHOuRc65lbVpe38XuPm+YMdIKa3WOj9F60vWcj0IOg8Xy7ngdDxgv9vO+h/gCZNAKuSRdQ2rAAAAAElFTkSuQmCC"
|
||||
@@ -147,13 +139,17 @@ export function LandingImages() {
|
||||
/>
|
||||
<div className="grids grids-dark px-2 py-4">
|
||||
<h3 className="mb-6 text-xl font-bold md:text-3xl">
|
||||
Email Receivers & Senders
|
||||
{t("emailReceiversSendersTitle")}
|
||||
</h3>
|
||||
<p className="text-lg">
|
||||
📧 Seamlessly receive and send emails from any email provider with
|
||||
top-notch security. Stay connected and manage your communications
|
||||
effortlessly, knowing your data is protected with robust
|
||||
encryption and privacy features.
|
||||
{t("emailReceiversSendersDescription")}{" "}
|
||||
<a
|
||||
className="underline"
|
||||
href="/dashboard/settings"
|
||||
target="_blank"
|
||||
>
|
||||
{t("applyYourApiKey")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,7 +157,7 @@ export function LandingImages() {
|
||||
<div className="flex flex-col items-center justify-around gap-10 md:flex-row">
|
||||
<Image
|
||||
className="size-[260px] rounded-lg transition-all hover:opacity-90 hover:shadow-xl"
|
||||
alt={"example"}
|
||||
alt={t("exampleImageAlt")}
|
||||
src="/_static/landing/domain.svg"
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxMAAAsTAQCanBgAAACCSURBVBhXZYzBCgIxDEQnTdPau+hveBB/XtiLn+NJQdoNS2Orq6zuO0zgZRhSVbvegeAJGx7hvUeMAUSEzu1RUesEKuNkIgyrFaoFzB4i8i1+cDEwXHOuRc65lbVpe38XuPm+YMdIKa3WOj9F60vWcj0IOg8Xy7ngdDxgv9vO+h/gCZNAKuSRdQ2rAAAAAElFTkSuQmCC"
|
||||
@@ -170,21 +166,16 @@ export function LandingImages() {
|
||||
/>
|
||||
<div className="grids grids-dark px-2 py-4">
|
||||
<h3 className="mb-6 text-xl font-bold md:text-3xl">
|
||||
Multiple Domains
|
||||
{t("multipleDomainsTitle")}
|
||||
</h3>
|
||||
<p className="text-lg">
|
||||
🤩 Empower your business with the flexibility of multiple domains,
|
||||
such as wr.do, uv.do, and more. Establish a strong digital
|
||||
footprint, create branded links, or manage diverse projects—all
|
||||
under one unified platform.
|
||||
</p>
|
||||
<p className="text-lg">{t("multipleDomainsDescription")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-around gap-10 md:flex-row-reverse">
|
||||
<Image
|
||||
className="size-[260px] rounded-lg transition-all hover:opacity-90 hover:shadow-xl"
|
||||
alt={"example"}
|
||||
alt={t("exampleImageAlt")}
|
||||
src="/_static/landing/screenshot.svg"
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxMAAAsTAQCanBgAAACCSURBVBhXZYzBCgIxDEQnTdPau+hveBB/XtiLn+NJQdoNS2Orq6zuO0zgZRhSVbvegeAJGx7hvUeMAUSEzu1RUesEKuNkIgyrFaoFzB4i8i1+cDEwXHOuRc65lbVpe38XuPm+YMdIKa3WOj9F60vWcj0IOg8Xy7ngdDxgv9vO+h/gCZNAKuSRdQ2rAAAAAElFTkSuQmCC"
|
||||
@@ -193,19 +184,16 @@ export function LandingImages() {
|
||||
/>
|
||||
<div className="grids grids-dark px-2 py-4">
|
||||
<h3 className="mb-6 text-xl font-bold md:text-3xl">
|
||||
Website Screenshot API
|
||||
{t("websiteScreenshotApiTitle")}
|
||||
</h3>
|
||||
<p className="text-lg">
|
||||
📷 Capture high-quality screenshots of any webpage instantly with
|
||||
our powerful Screenshot API. Integrate seamlessly into your
|
||||
applications, access third-party services, and unlock advanced
|
||||
features by applying your unique API key.
|
||||
{t("websiteScreenshotApiDescription")}{" "}
|
||||
<a
|
||||
className="underline"
|
||||
href="/dashboard/settings"
|
||||
target="_blank"
|
||||
>
|
||||
Apply your api key-->
|
||||
{t("applyYourApiKey")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -214,7 +202,7 @@ export function LandingImages() {
|
||||
<div className="my-14 flex flex-col items-center justify-around gap-10 md:flex-row">
|
||||
<Image
|
||||
className="size-[260px] rounded-lg transition-all hover:opacity-90 hover:shadow-xl"
|
||||
alt={"example"}
|
||||
alt={t("exampleImageAlt")}
|
||||
src="/_static/landing/info.svg"
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxMAAAsTAQCanBgAAACCSURBVBhXZYzBCgIxDEQnTdPau+hveBB/XtiLn+NJQdoNS2Orq6zuO0zgZRhSVbvegeAJGx7hvUeMAUSEzu1RUesEKuNkIgyrFaoFzB4i8i1+cDEwXHOuRc65lbVpe38XuPm+YMdIKa3WOj9F60vWcj0IOg8Xy7ngdDxgv9vO+h/gCZNAKuSRdQ2rAAAAAElFTkSuQmCC"
|
||||
@@ -223,19 +211,16 @@ export function LandingImages() {
|
||||
/>
|
||||
<div className="grids grids-dark px-2 py-4">
|
||||
<h3 className="mb-6 text-xl font-bold md:text-3xl">
|
||||
Meta Information API
|
||||
{t("metaInformationApiTitle")}
|
||||
</h3>
|
||||
<p className="text-lg">
|
||||
🍥 Extract rich, structured web data effortlessly with our smart
|
||||
Meta Information API. Perfect for developers, businesses, or
|
||||
researchers, this tool offers seamless integration, third-party
|
||||
service access, and enhanced functionality.
|
||||
{t("metaInformationApiDescription")}{" "}
|
||||
<a
|
||||
className="underline"
|
||||
href="/dashboard/settings"
|
||||
target="_blank"
|
||||
>
|
||||
Apply your api key-->
|
||||
{t("applyYourApiKey")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { CSSProperties, ReactNode } from "react";
|
||||
import Link from "next/link";
|
||||
import { motion } from "framer-motion";
|
||||
import { X } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { TeamPlanQuota } from "@/config/team";
|
||||
import { cn, nFormatter } from "@/lib/utils";
|
||||
@@ -66,6 +67,7 @@ const getBenefits = (plan) => [
|
||||
];
|
||||
|
||||
export const PricingSection = () => {
|
||||
const t = useTranslations("Landing");
|
||||
return (
|
||||
<section
|
||||
id="pricing"
|
||||
@@ -75,49 +77,48 @@ export const PricingSection = () => {
|
||||
<div className="relative z-10 mx-auto max-w-5xl px-4 py-20 md:px-8">
|
||||
<div className="mb-12 space-y-3">
|
||||
<h2 className="text-center text-xl font-semibold leading-tight sm:text-3xl sm:leading-tight md:text-4xl md:leading-tight">
|
||||
Pricing
|
||||
{t("pricingTitle")}
|
||||
</h2>
|
||||
<p className="text-center text-base text-zinc-600 dark:text-zinc-400 md:text-lg">
|
||||
Use it for free for yourself, upgrade when your team needs advanced
|
||||
control.
|
||||
{t("pricingDescription")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<PriceCard
|
||||
tier="Free"
|
||||
price="$0/mo"
|
||||
bestFor="For hobbyists and individuals looking to manage their links"
|
||||
tier={t("freeTier")}
|
||||
price={t("freePrice")}
|
||||
bestFor={t("freeBestFor")}
|
||||
CTA={
|
||||
<Link href={"/dashboard"}>
|
||||
<Button className="w-full" variant={"default"}>
|
||||
Get started free
|
||||
{t("getStartedFree")}
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
benefits={getBenefits(TeamPlanQuota.free)}
|
||||
/>
|
||||
{/* <PriceCard
|
||||
tier="Premium"
|
||||
price="$5/mo"
|
||||
bestFor="Best for 5-50 users"
|
||||
tier={t("premiumTier")}
|
||||
price={t("premiumPrice")}
|
||||
bestFor={t("premiumBestFor")}
|
||||
CTA={
|
||||
<Link href={"/pricing"}>
|
||||
<Button className="w-full bg-zinc-800 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 dark:bg-zinc-50 dark:text-zinc-950 dark:hover:bg-zinc-200 dark:hover:text-zinc-900">
|
||||
Get free trial
|
||||
{t("getFreeTrial")}
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
benefits={getBenefits(TeamPlanQuota.premium)}
|
||||
/> */}
|
||||
<PriceCard
|
||||
tier="Enterprise"
|
||||
price="Contact us"
|
||||
bestFor="For large organizations with custom needs"
|
||||
tier={t("enterpriseTier")}
|
||||
price={t("enterprisePrice")}
|
||||
bestFor={t("enterpriseBestFor")}
|
||||
CTA={
|
||||
<Link href={"mailto:support@wr.do"}>
|
||||
<Button className="w-full" variant="outline">
|
||||
Contact us
|
||||
{t("contactUs")}
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -44,6 +45,8 @@ export function PaginationWrapper({
|
||||
// Calculate total pages based on pageSize
|
||||
const totalPages = Math.ceil(total / pageSize);
|
||||
|
||||
const t = useTranslations("List");
|
||||
|
||||
const shouldShowPage = (page: number) => {
|
||||
return (
|
||||
page === 1 || page === totalPages || Math.abs(page - currentPage) <= 2
|
||||
@@ -144,7 +147,7 @@ export function PaginationWrapper({
|
||||
>
|
||||
{setPageSize && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground">Show</span>
|
||||
<span className="text-muted-foreground">{t("Show")}</span>
|
||||
<Select
|
||||
value={pageSize.toString()}
|
||||
onValueChange={handlePageSizeChange}
|
||||
@@ -172,7 +175,7 @@ export function PaginationWrapper({
|
||||
"hidden sm:inline-block",
|
||||
)}
|
||||
>
|
||||
per page
|
||||
{t("per page")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+20
-18
@@ -10,6 +10,7 @@ import {
|
||||
} from "react";
|
||||
import Link from "next/link";
|
||||
import { debounce } from "lodash";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -45,6 +46,7 @@ export default function QRCodeEditor({
|
||||
user: { id: string; apiKey: string; team: string };
|
||||
url: string;
|
||||
}) {
|
||||
const t = useTranslations("List");
|
||||
const [params, setParams] = useState({
|
||||
key: user.apiKey,
|
||||
url,
|
||||
@@ -145,13 +147,13 @@ export default function QRCodeEditor({
|
||||
|
||||
return (
|
||||
<div className="relative w-full max-w-lg rounded-lg bg-white p-4 shadow-lg dark:bg-neutral-900">
|
||||
<h2 className="mb-4 text-lg font-semibold">QR Code Design</h2>
|
||||
<h2 className="mb-4 text-lg font-semibold">{t("QR Code Design")}</h2>
|
||||
|
||||
{/* QR Code Preview */}
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<h3 className="text-sm font-semibold text-neutral-600 dark:text-neutral-300">
|
||||
Preview
|
||||
{t("Preview")}
|
||||
</h3>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="ml-auto px-2 py-2 hover:bg-accent hover:text-accent-foreground">
|
||||
@@ -166,7 +168,7 @@ export default function QRCodeEditor({
|
||||
>
|
||||
<div className="flex items-center gap-2 text-neutral-500">
|
||||
<Icons.media className="size-4" />
|
||||
<span className="font-semibold">Download SVG</span>
|
||||
<span className="font-semibold">{t("Download SVG")}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
@@ -181,7 +183,7 @@ export default function QRCodeEditor({
|
||||
>
|
||||
<div className="flex items-center gap-2 text-neutral-500">
|
||||
<Icons.media className="size-4" />
|
||||
<span className="font-semibold">Download PNG</span>
|
||||
<span className="font-semibold">{t("Download PNG")}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
@@ -196,7 +198,7 @@ export default function QRCodeEditor({
|
||||
>
|
||||
<div className="flex items-center gap-2 text-neutral-500">
|
||||
<Icons.media className="size-4" />
|
||||
<span className="font-semibold">Download JPG</span>
|
||||
<span className="font-semibold">{t("Download JPG")}</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -234,7 +236,7 @@ export default function QRCodeEditor({
|
||||
|
||||
<div className="group mb-3 flex items-center justify-between">
|
||||
<h3 className="text-nowrap text-sm font-semibold text-neutral-600 transition-all group-hover:ml-1 group-hover:font-bold dark:text-neutral-300">
|
||||
Url
|
||||
{t("Url")}
|
||||
</h3>
|
||||
<Input
|
||||
className="ml-auto w-3/5"
|
||||
@@ -248,7 +250,7 @@ export default function QRCodeEditor({
|
||||
<div className="mb-3">
|
||||
<div className="group mb-3 flex items-center justify-between">
|
||||
<h3 className="text-nowrap text-sm font-semibold text-neutral-600 transition-all group-hover:ml-1 group-hover:font-bold dark:text-neutral-300">
|
||||
Logo
|
||||
{t("Logo")}
|
||||
</h3>
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
@@ -256,13 +258,13 @@ export default function QRCodeEditor({
|
||||
<Icons.help className="ml-1 size-4 text-neutral-400" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-64 text-left">
|
||||
Display your logo in the center of the QR code.{" "}
|
||||
{t("Display your logo in the center of the QR code")}.{" "}
|
||||
<Link
|
||||
className="border-b text-neutral-500"
|
||||
href="/docs/open-api/qrcode"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
{t("Learn more")}
|
||||
</Link>
|
||||
.
|
||||
</TooltipContent>
|
||||
@@ -277,7 +279,7 @@ export default function QRCodeEditor({
|
||||
<details className="group">
|
||||
<summary className="flex w-full cursor-pointer items-center justify-between">
|
||||
<h3 className="text-nowrap text-sm font-semibold text-neutral-600 transition-all group-hover:ml-1 group-hover:font-bold dark:text-neutral-300">
|
||||
Custom Logo
|
||||
{t("Custom Logo")}
|
||||
</h3>
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
@@ -291,13 +293,13 @@ export default function QRCodeEditor({
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-64 text-left">
|
||||
Customize your QR code logo.{" "}
|
||||
{t("Customize your QR code logo")}.{" "}
|
||||
<Link
|
||||
className="border-b text-neutral-500"
|
||||
href="/docs/open-api/qrcode"
|
||||
target="_blank"
|
||||
>
|
||||
Learn more
|
||||
{t("Learn more")}
|
||||
</Link>
|
||||
.
|
||||
</TooltipContent>
|
||||
@@ -319,7 +321,7 @@ export default function QRCodeEditor({
|
||||
<details className="group mb-3">
|
||||
<summary className="flex w-full cursor-pointer items-center justify-between">
|
||||
<h3 className="text-nowrap text-sm font-semibold text-neutral-600 transition-all group-hover:ml-1 group-hover:font-bold dark:text-neutral-300">
|
||||
Front Color
|
||||
{t("Front Color")}
|
||||
</h3>
|
||||
<Icons.chevronDown className="ml-auto size-4" />
|
||||
</summary>
|
||||
@@ -373,7 +375,7 @@ export default function QRCodeEditor({
|
||||
<details className="group" open={true}>
|
||||
<summary className="flex w-full cursor-pointer items-center justify-between">
|
||||
<h3 className="text-nowrap text-sm font-semibold text-neutral-600 transition-all group-hover:ml-1 group-hover:font-bold dark:text-neutral-300">
|
||||
Background Color
|
||||
{t("Background Color")}
|
||||
</h3>
|
||||
<Icons.chevronDown className="ml-auto size-4" />
|
||||
</summary>
|
||||
@@ -428,20 +430,20 @@ export default function QRCodeEditor({
|
||||
{!user.apiKey && (
|
||||
<div className="absolute left-0 top-0 z-20 flex size-full flex-col items-center justify-center gap-2 bg-neutral-100/20 px-4 backdrop-blur">
|
||||
<p className="text-center text-sm">
|
||||
Please create a <strong>api key</strong> before use this feature.{" "}
|
||||
<br /> Learn more about{" "}
|
||||
{t("Please create a api key before use this feature")}. <br />{" "}
|
||||
{t("Learn more about")}{" "}
|
||||
<Link
|
||||
className="py-1 text-blue-600 hover:text-blue-400 hover:underline dark:hover:text-primary-foreground"
|
||||
href={"/docs/open-api#api-key"}
|
||||
target="_blank"
|
||||
>
|
||||
api key
|
||||
Api key
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
|
||||
<Link href={"/dashboard/settings"}>
|
||||
<Button>Create Api Key</Button>
|
||||
<Button>{t("Create Api Key")}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+120
-2
@@ -1,6 +1,124 @@
|
||||
{
|
||||
"Common": {
|
||||
"Common": {},
|
||||
"List": {
|
||||
"Short URLs": "Short URLs",
|
||||
"Manage Short URLs": "Manage Short URLs",
|
||||
"Add URL": "Add URL",
|
||||
"Slug": "Slug",
|
||||
"Target": "Target",
|
||||
"User": "User",
|
||||
"Enabled": "Enabled",
|
||||
"Expiration": "Expiration",
|
||||
"Clicks": "Clicks",
|
||||
"Updated": "Updated",
|
||||
"Actions": "Actions",
|
||||
"Edit": "Edit",
|
||||
"Search by slug": "Search by slug",
|
||||
"Search by target": "Search by target",
|
||||
"Search by username": "Search by username",
|
||||
"Create short link": "Create short link",
|
||||
"Edit short link": "Edit short link",
|
||||
"Delete": "Delete",
|
||||
"Target URL": "Target URL",
|
||||
"Required": "Required",
|
||||
"Short Link": "Short Link",
|
||||
"A random url suffix": "A random url suffix",
|
||||
"Final url like": "Final url like",
|
||||
"Password": "Password",
|
||||
"Optional": "Optional",
|
||||
"If you want to protect your link": "If you want to protect your link",
|
||||
"Enter 6 character password": "Enter 6 character password",
|
||||
"Expiration time, default for never": "Expiration time, default for never",
|
||||
"Save": "Save",
|
||||
"Cancel": "Cancel",
|
||||
"Update": "Update",
|
||||
"QR Code Design": "QR Code Design",
|
||||
"Preview": "Preview",
|
||||
"Download SVG": "Download SVG",
|
||||
"Download PNG": "Download PNG",
|
||||
"Download JPG": "Download JPG",
|
||||
"Url": "Url",
|
||||
"Logo": "Logo",
|
||||
"Custom Logo": "Custom Logo",
|
||||
"Front Color": "Front Color",
|
||||
"Background Color": "Background Color",
|
||||
"Display your logo in the center of the QR code": "Display your logo in the center of the QR code",
|
||||
"Learn more": "Learn more",
|
||||
"Customize your QR code logo": "Customize your QR code logo",
|
||||
"Please create a api key before use this feature": "Please create a api key before use this feature",
|
||||
"Learn more about": "Learn more about",
|
||||
"Create Api Key": "Create Api Key"
|
||||
},
|
||||
"Landing": {
|
||||
"settings": "Settings",
|
||||
"Dashboard": "Dashboard"
|
||||
"Dashboard": "Dashboard",
|
||||
"deployWithVercel": "Deploy with",
|
||||
"now": "now!",
|
||||
"onePlatformPowers": "One platform powers",
|
||||
"endlessSolutions": "endless solutions",
|
||||
"platformDescription": "Link shortening, domain hosting, email manager and open api, everything you need to build better.",
|
||||
"documents": "Documents",
|
||||
"signInForFree": "Sign in for free",
|
||||
"exampleImageAlt": "example",
|
||||
"urlShorteningTitle": "URL Shortening",
|
||||
"urlShorteningDescription": "📖 Instantly transform long, unwieldy URLs into short, memorable links that are easy to share. Enjoy built-in analytics to track clicks, monitor performance, and gain insights into your audience—all in real time.",
|
||||
"freeSubdomainHostingTitle": "Free Subdomain Hosting",
|
||||
"freeSubdomainHostingDescription": "🎉 Kickstart your online presence with free, fully customizable subdomains. Whether you're launching a personal project or testing a business idea, get started quickly with no cost and reliable hosting you can trust.",
|
||||
"emailReceiversSendersTitle": "Email Receivers & Senders",
|
||||
"emailReceiversSendersDescription": "📧 Seamlessly receive and send emails from any email provider with top-notch security. Stay connected and manage your communications effortlessly, knowing your data is protected with robust encryption and privacy features.",
|
||||
"multipleDomainsTitle": "Multiple Domains",
|
||||
"multipleDomainsDescription": "🤩 Empower your business with the flexibility of multiple domains, such as wr.do, uv.do, and more. Establish a strong digital footprint, create branded links, or manage diverse projects—all under one unified platform.",
|
||||
"websiteScreenshotApiTitle": "Website Screenshot API",
|
||||
"websiteScreenshotApiDescription": "📷 Capture high-quality screenshots of any webpage instantly with our powerful Screenshot API. Integrate seamlessly into your applications, access third-party services, and unlock advanced features by applying your unique API key.",
|
||||
"metaInformationApiTitle": "Meta Information API",
|
||||
"metaInformationApiDescription": "🍥 Extract rich, structured web data effortlessly with our smart Meta Information API. Perfect for developers, businesses, or researchers, this tool offers seamless integration, third-party service access, and enhanced functionality.",
|
||||
"applyYourApiKey": "Apply your api key",
|
||||
"pricingTitle": "Pricing",
|
||||
"pricingDescription": "Use it for free for yourself, upgrade when your team needs advanced control.",
|
||||
"freeTier": "Free",
|
||||
"freePrice": "$0/mo",
|
||||
"freeBestFor": "For hobbyists and individuals looking to manage their links",
|
||||
"getStartedFree": "Get started free",
|
||||
"premiumTier": "Premium",
|
||||
"premiumPrice": "$5/mo",
|
||||
"premiumBestFor": "Best for 5-50 users",
|
||||
"getFreeTrial": "Get free trial",
|
||||
"enterpriseTier": "Enterprise",
|
||||
"enterprisePrice": "Contact us",
|
||||
"enterpriseBestFor": "For large organizations with custom needs",
|
||||
"contactUs": "Contact us"
|
||||
},
|
||||
"System": {
|
||||
"MENU": "Menu",
|
||||
"Dashboard": "Dashboard",
|
||||
"Short Urls": "Short Urls",
|
||||
"Emails": "Emails",
|
||||
"DNS Records": "DNS Records",
|
||||
"WRoom": "WRoom",
|
||||
"OPEN API": "OPEN API",
|
||||
"Overview": "Overview",
|
||||
"Screenshot": "Screenshot",
|
||||
"QR Code": "QR Code",
|
||||
"Meta Info": "Meta Info",
|
||||
"Markdown": "Markdown",
|
||||
"ADMIN": "ADMIN",
|
||||
"Admin Panel": "Admin Panel",
|
||||
"Domains": "Domains",
|
||||
"Users": "Users",
|
||||
"URLs": "URLs",
|
||||
"Records": "Records",
|
||||
"OPTIONS": "OPTIONS",
|
||||
"Settings": "Settings",
|
||||
"Documentation": "Documentation",
|
||||
"Feedback": "Feedback",
|
||||
"Support": "Support",
|
||||
"Docs": "Docs",
|
||||
"Toggle theme": "Toggle theme",
|
||||
"Light": "Light",
|
||||
"Dark": "Dark",
|
||||
"System": "System",
|
||||
"Admin": "Admin",
|
||||
"Sign in": "Sign in",
|
||||
"Log out": "Log out"
|
||||
}
|
||||
}
|
||||
|
||||
+122
-2
@@ -1,6 +1,126 @@
|
||||
{
|
||||
"Common": {
|
||||
"Common": {},
|
||||
"List": {
|
||||
"Short URLs": "短链列表",
|
||||
"Manage Short URLs": "管理短链",
|
||||
"Add URL": "创建短链",
|
||||
"Slug": "短链",
|
||||
"Target": "目标",
|
||||
"User": "用户",
|
||||
"Enabled": "启用",
|
||||
"Expiration": "有效期",
|
||||
"Clicks": "点击量",
|
||||
"Updated": "更新",
|
||||
"Actions": "操作",
|
||||
"Edit": "编辑",
|
||||
"Search by slug": "搜索短链",
|
||||
"Search by target": "搜索目标链接",
|
||||
"Search by username": "搜索用户名",
|
||||
"Create short link": "创建短链",
|
||||
"Edit short link": "编辑短链",
|
||||
"Delete": "删除",
|
||||
"Target URL": "目标链接",
|
||||
"Required": "必填",
|
||||
"Short Link": "短链",
|
||||
"A random url suffix": "短链后缀",
|
||||
"Final url like": "最终链接为",
|
||||
"Password": "访问密码",
|
||||
"Optional": "可选",
|
||||
"If you want to protect your link": "如果您想保护您的链接",
|
||||
"Enter 6 character password": "请输入6位密码",
|
||||
"Expiration time, default for never": "过期时间,默认为永不过期",
|
||||
"Save": "保存",
|
||||
"Cancel": "取消",
|
||||
"Update": "更新",
|
||||
"QR Code Design": "自定义二维码",
|
||||
"Preview": "预览",
|
||||
"Download SVG": "下载 SVG 图片",
|
||||
"Download PNG": "下载 PNG 图片",
|
||||
"Download JPG": "下载 JPG 图片",
|
||||
"Url": "链接",
|
||||
"Logo": "图标",
|
||||
"Custom Logo": "自定义图标",
|
||||
"Front Color": "前景色",
|
||||
"Background Color": "背景色",
|
||||
"Display your logo in the center of the QR code": "在二维码中央显示您的图标",
|
||||
"Learn more": "了解更多",
|
||||
"Customize your QR code logo": "自定义二维码图标",
|
||||
"Please create a api key before use this feature": "请在使用此功能之前创建一个 API 密钥",
|
||||
"Learn more about": "了解更多关于",
|
||||
"Create Api Key": "创建 API 密钥",
|
||||
"Show": "显示",
|
||||
"per page": "条/页"
|
||||
},
|
||||
"Landing": {
|
||||
"settings": "设置",
|
||||
"Dashboard": "用户面板"
|
||||
"Dashboard": "管理面板",
|
||||
"deployWithVercel": "使用",
|
||||
"now": "部署私有版本",
|
||||
"onePlatformPowers": "一站式域名",
|
||||
"endlessSolutions": "服务平台",
|
||||
"platformDescription": " 短链生成、子域名托管、无限邮箱服务,开放API接口,一站式域名管理解决方案。",
|
||||
"documents": "参考文档",
|
||||
"signInForFree": "免费登录",
|
||||
"exampleImageAlt": "示例",
|
||||
"urlShorteningTitle": "短链生成器",
|
||||
"urlShorteningDescription": "📖 立即将冗长、复杂的URL转换为短小易记的链接,便于分享。享受内置的分析功能,实时跟踪点击量、监控性能并深入了解您的受众。支持设置密码保护、自定义二维码生成,支持调用API生成。",
|
||||
"freeSubdomainHostingTitle": "子域名托管",
|
||||
"freeSubdomainHostingDescription": "🎉 管理多 Cloudflare 账户下的多个域名的 DNS 记录,支持创建多种 DNS 记录类型(CNAME、A、TXT 等)。",
|
||||
"emailReceiversSendersTitle": "电子邮件接收与发送",
|
||||
"emailReceiversSendersDescription": "📧 创建无限个自定义前缀邮箱,接收无限制邮件,支持调用 API 创建邮箱、调用 API 获取收件箱邮件,支持过滤未读邮件列表。",
|
||||
"multipleDomainsTitle": "多域名支持",
|
||||
"multipleDomainsDescription": "🤩 通过多个域名的灵活性增强您的业务,例如wr.do、uv.do等。建立强大的数字足迹,创建品牌链接,或在一个统一平台下管理多样化项目。",
|
||||
"websiteScreenshotApiTitle": "网站截图API",
|
||||
"websiteScreenshotApiDescription": "📷 使用我们强大的截图API即时捕获任何网页的高质量截图。无缝集成到您的应用程序中,访问第三方服务,并通过申请您的唯一API密钥解锁功能。",
|
||||
"metaInformationApiTitle": "元信息API",
|
||||
"metaInformationApiDescription": "🍥 使用我们智能的元信息API轻松提取丰富、结构化的网页数据。适合开发者、商家或研究人员,此工具提供无缝集成、第三方服务访问和增强功能。",
|
||||
"applyYourApiKey": "申请您的API密钥",
|
||||
"pricingTitle": "定价",
|
||||
"pricingDescription": "免费为个人使用,当您的团队需要高级控制时可升级。",
|
||||
"freeTier": "免费体验",
|
||||
"freePrice": "$0/mo",
|
||||
"freeBestFor": "适合爱好者和需要管理链接的个人",
|
||||
"getStartedFree": "免费开始",
|
||||
"premiumTier": "高级",
|
||||
"premiumPrice": "$5/mo",
|
||||
"premiumBestFor": "最适合5-50人的团队",
|
||||
"getFreeTrial": "获取免费试用",
|
||||
"enterpriseTier": "企业计划",
|
||||
"enterprisePrice": "联系我们",
|
||||
"enterpriseBestFor": "适合有定制需求的大型组织",
|
||||
"contactUs": "联系我们"
|
||||
},
|
||||
"System": {
|
||||
"MENU": "菜单",
|
||||
"Dashboard": "用户面板",
|
||||
"Short Urls": "短链接",
|
||||
"Emails": "邮件",
|
||||
"DNS Records": "DNS 记录",
|
||||
"WRoom": "WRoom",
|
||||
"OPEN API": "开放API",
|
||||
"Overview": "概览面板",
|
||||
"Screenshot": "截图API",
|
||||
"QR Code": "二维码API",
|
||||
"Meta Info": "元数据API",
|
||||
"Markdown": "Markdown",
|
||||
"ADMIN": "管理员",
|
||||
"Admin Panel": "概览面板",
|
||||
"Domains": "域名管理",
|
||||
"Users": "用户管理",
|
||||
"URLs": "短链管理",
|
||||
"Records": "DNS 记录管理",
|
||||
"OPTIONS": "选项",
|
||||
"Settings": "用户设置",
|
||||
"Documentation": "使用文档",
|
||||
"Feedback": "反馈",
|
||||
"Support": "联系站长",
|
||||
"Docs": "文档",
|
||||
"Toggle theme": "主题切换",
|
||||
"Light": "浅色",
|
||||
"Dark": "深色",
|
||||
"System": "跟随系统",
|
||||
"Admin": "管理面板",
|
||||
"Sign in": "登录",
|
||||
"Log out": "退出登录"
|
||||
}
|
||||
}
|
||||
|
||||
+101
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user