diff --git a/.env.example b/.env.example index 972708f..fd5148d 100644 --- a/.env.example +++ b/.env.example @@ -42,7 +42,7 @@ SCREENSHOTONE_BASE_URL=https://shot.wr.do # GitHub api token for getting gitHub stars count GITHUB_TOKEN= -# Skip DB check and migration (only for docker). if false, will check and migrate database each time start docker compose. +# Skip DB check and migration. if false, will check and migrate database each time start docker compose. SKIP_DB_CHECK=false SKIP_DB_MIGRATION=false diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml index 683369d..0f3147d 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/docker-build-push.yml @@ -5,6 +5,7 @@ on: branches: - main - fix/docker + - i18n tags: - "v*.*.*" pull_request: diff --git a/README-zh.md b/README-zh.md index 24654eb..a088bca 100644 --- a/README-zh.md +++ b/README-zh.md @@ -109,10 +109,18 @@ pnpm db:push Follow https://localhost:3000/setup -## Environment Variables +## 环境变量 查看 [开发者文档](https://wr.do/docs/developer). +## 技术栈 + +- Next.js + React + TypeScript +- Tailwind CSS 用于样式设计 +- Prisma ORM 作为数据库工具 +- Cloudflare 作为主要的云基础设施 +- Vercel 作为推荐的部署平台 + ## 社区群组 - Discord: https://discord.gg/AHPQYuZu3m diff --git a/README.md b/README.md index 5df9986..f0f0603 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,14 @@ Follow https://localhost:3000/setup Via [Installation For Developer](https://wr.do/docs/developer). +## Technology Stack + +- Next.js + React + TypeScript +- Tailwind CSS for styling and design +- Prisma ORM as the database toolkit +- Cloudflare as the primary cloud infrastructure +- Vercel as the recommended deployment platform + ## Community Group - Discord: https://discord.gg/AHPQYuZu3m diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index e17ab4f..154292c 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -1,6 +1,7 @@ import { Suspense } from "react"; import { Metadata } from "next"; import Link from "next/link"; +import { useTranslations } from "next-intl"; import { siteConfig } from "@/config/site"; import { cn } from "@/lib/utils"; @@ -14,6 +15,7 @@ export const metadata: Metadata = { }; export default function LoginPage() { + const t = useTranslations("Auth"); return (
<> - Back + {t("Back")}
- Welcome to{" "} + {t("Welcome to")}{" "} {siteConfig.name}

- Choose your login method to continue + {t("Choose your login method to continue")}

@@ -50,19 +52,19 @@ export default function LoginPage() {

*/}

- By clicking continue, you agree to our{" "} + {t("By clicking continue, you agree to our")}{" "} - Terms of Service + {t("Terms of Service")} {" "} - and{" "} + {t("and")}{" "} - Privacy Policy + {t("Privacy Policy")} .

diff --git a/app/(marketing)/page.tsx b/app/(marketing)/page.tsx index 441bb37..1356a4c 100644 --- a/app/(marketing)/page.tsx +++ b/app/(marketing)/page.tsx @@ -1,10 +1,12 @@ +import { getCurrentUser } from "@/lib/session"; import HeroLanding, { LandingImages } from "@/components/sections/hero-landing"; import { PricingSection } from "@/components/sections/pricing"; -export default function IndexPage() { +export default async function IndexPage() { + const user = await getCurrentUser(); return ( <> - + diff --git a/app/(protected)/admin/api-key-active-chart.tsx b/app/(protected)/admin/api-key-active-chart.tsx index dff71f3..0a70e14 100644 --- a/app/(protected)/admin/api-key-active-chart.tsx +++ b/app/(protected)/admin/api-key-active-chart.tsx @@ -1,6 +1,7 @@ "use client"; import { TrendingUp } from "lucide-react"; +import { useTranslations } from "next-intl"; import { Label, PolarGrid, @@ -29,6 +30,7 @@ export function RadialShapeChart({ total: number; totalUser: number; }) { + const t = useTranslations("Components"); const chartData = [ { browser: "safari", actived: total, fill: "var(--color-safari)" }, ]; @@ -94,7 +96,7 @@ export function RadialShapeChart({
- Cumulative proportion of activated Api Key users + {t("Activated Api Key users")}
diff --git a/app/(protected)/admin/domains/domain-list.tsx b/app/(protected)/admin/domains/domain-list.tsx index 1439dfb..88adc77 100644 --- a/app/(protected)/admin/domains/domain-list.tsx +++ b/app/(protected)/admin/domains/domain-list.tsx @@ -4,11 +4,12 @@ import { useState } from "react"; import Link from "next/link"; 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"; import { DomainFormData } from "@/lib/dto/domains"; -import { fetcher, timeAgo } from "@/lib/utils"; +import { fetcher } from "@/lib/utils"; import { useMediaQuery } from "@/hooks/use-media-query"; import { Button } from "@/components/ui/button"; import { @@ -34,6 +35,7 @@ import { FormType } from "@/components/forms/record-form"; import { EmptyPlaceholder } from "@/components/shared/empty-placeholder"; import { Icons } from "@/components/shared/icons"; import { PaginationWrapper } from "@/components/shared/pagination"; +import { TimeAgoIntl } from "@/components/shared/time-ago"; export interface DomainListProps { user: Pick; @@ -70,6 +72,7 @@ function TableColumnSekleton() { export default function DomainList({ user, action }: DomainListProps) { const { isMobile } = useMediaQuery(); + const t = useTranslations("List"); const [isShowForm, setShowForm] = useState(false); const [formType, setFormType] = useState("add"); const [currentEditDomain, setCurrentEditDomain] = @@ -130,7 +133,7 @@ export default function DomainList({ user, action }: DomainListProps) {
- Total Domains: + {t("Total Domains")}: {isLoading ? ( ) : ( @@ -161,7 +164,7 @@ export default function DomainList({ user, action }: DomainListProps) { }} > - Add Domain + {t("Add Domain")}
@@ -170,7 +173,7 @@ export default function DomainList({ user, action }: DomainListProps) {
{ setSearchParams({ @@ -197,25 +200,25 @@ export default function DomainList({ user, action }: DomainListProps) { - Domain + {t("Domain Name")} - Shorten + {t("Shorten Service")} - Email + {t("Email Service")} - Subdomain + {t("Subdomain Service")} - Active + {t("Active")} - Updated + {t("Updated")} - Actions + {t("Actions")} @@ -289,7 +292,7 @@ export default function DomainList({ user, action }: DomainListProps) { /> - {timeAgo(domain.updatedAt as Date)} + @@ -316,7 +319,9 @@ export default function DomainList({ user, action }: DomainListProps) { ) : ( - No Domains + + {t("No Domains")} + You don't have any domains yet. Start creating one. diff --git a/app/(protected)/admin/domains/page.tsx b/app/(protected)/admin/domains/page.tsx index 804ba73..a781567 100644 --- a/app/(protected)/admin/domains/page.tsx +++ b/app/(protected)/admin/domains/page.tsx @@ -19,10 +19,10 @@ export default async function DashboardPage() { return ( <> - - Total requests of {type1} - {type2 && ` and ${type2}`}. - + {message} diff --git a/app/(protected)/admin/loading.tsx b/app/(protected)/admin/loading.tsx index 86b8c85..ad24fa0 100644 --- a/app/(protected)/admin/loading.tsx +++ b/app/(protected)/admin/loading.tsx @@ -6,7 +6,7 @@ export default function AdminPanelLoading() { <>
diff --git a/app/(protected)/admin/page.tsx b/app/(protected)/admin/page.tsx index 582ea5f..338ec8f 100644 --- a/app/(protected)/admin/page.tsx +++ b/app/(protected)/admin/page.tsx @@ -1,5 +1,6 @@ import { Suspense } from "react"; import { redirect } from "next/navigation"; +import { useTranslations } from "next-intl"; import { getUserRecordCount } from "@/lib/dto/cloudflare-dns-record"; import { @@ -134,7 +135,6 @@ async function RequestStatsSection() { return hasStats ? ( <> -

Request Statistics

-

Request Logs

); @@ -213,10 +212,7 @@ export default async function AdminPage() { return ( <> - +
- + diff --git a/app/(protected)/admin/records/page.tsx b/app/(protected)/admin/records/page.tsx index 060abb5..60a0f51 100644 --- a/app/(protected)/admin/records/page.tsx +++ b/app/(protected)/admin/records/page.tsx @@ -19,10 +19,10 @@ export default async function DashboardPage() { return ( <> - + diff --git a/app/(protected)/admin/urls/page.tsx b/app/(protected)/admin/urls/page.tsx index 360ca4d..79ed5d5 100644 --- a/app/(protected)/admin/urls/page.tsx +++ b/app/(protected)/admin/urls/page.tsx @@ -19,10 +19,10 @@ export default async function DashboardPage() { return ( <> diff --git a/app/(protected)/admin/users/page.tsx b/app/(protected)/admin/users/page.tsx index fd48ebc..bbdc7f8 100644 --- a/app/(protected)/admin/users/page.tsx +++ b/app/(protected)/admin/users/page.tsx @@ -20,7 +20,7 @@ export default async function UsersPage() { <> diff --git a/app/(protected)/admin/users/user-list.tsx b/app/(protected)/admin/users/user-list.tsx index b51419d..ecb44cc 100644 --- a/app/(protected)/admin/users/user-list.tsx +++ b/app/(protected)/admin/users/user-list.tsx @@ -5,7 +5,7 @@ import { User } from "@prisma/client"; import { PenLine, RefreshCwIcon } from "lucide-react"; import useSWR, { useSWRConfig } from "swr"; -import { fetcher, timeAgo } from "@/lib/utils"; +import { fetcher } from "@/lib/utils"; import { useMediaQuery } from "@/hooks/use-media-query"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -37,6 +37,7 @@ import { UserForm } from "@/components/forms/user-form"; import { EmptyPlaceholder } from "@/components/shared/empty-placeholder"; import { Icons } from "@/components/shared/icons"; import { PaginationWrapper } from "@/components/shared/pagination"; +import { TimeAgoIntl } from "@/components/shared/time-ago"; import CountUpFn from "../../../../components/dashboard/count-up"; @@ -105,9 +106,7 @@ export default function UsersList({ user }: UrlListProps) { Total Users:{" "} - - {data && } - + {data && data.total}
@@ -203,28 +206,28 @@ export default function UserRecordsList({ user, action }: RecordListProps) { - Type + {t("Type")} - Name + {t("Name")} - Content + {t("Content")} - TTL + {t("TTL")} - Status + {t("Status")} - User + {t("User")} - Updated + {t("Updated")} - Actions + {t("Actions")} @@ -278,8 +281,11 @@ export default function UserRecordsList({ user, action }: RecordListProps) { onChangeStatu={handleChangeStatu} /> ) : ( - - Pending + + {t("Pending")} )} {record.active !== 1 && ( @@ -291,16 +297,21 @@ export default function UserRecordsList({ user, action }: RecordListProps) { {record.active === 0 && (
    -
  • The target is currently inaccessible.
  • - Please check the target and try again. + {t("The target is currently inaccessible")}.
  • - If the target is not activated within 3 - days,
    - the administrator will{" "} + {t("Please check the target and try again")} + . +
  • +
  • + {t( + "If the target is not activated within 3 days", + )} + ,
    + {t("the administrator will")}{" "} - delete this record + {t("delete this record")} .
  • @@ -309,8 +320,10 @@ export default function UserRecordsList({ user, action }: RecordListProps) { {record.active === 2 && (
    • - The record is currently pending for admin - approval. + {t( + "The record is currently pending for admin approval", + )} + .
    )} @@ -332,12 +345,14 @@ export default function UserRecordsList({ user, action }: RecordListProps) { - {timeAgo(record.modified_on as unknown as Date)} + {record.active !== 2 ? ( ) : record.active === 2 && @@ -364,7 +379,7 @@ export default function UserRecordsList({ user, action }: RecordListProps) { setShowForm(!isShowForm); }} > -

    Review

    +

    {t("Review")}

    ) : ( "--" @@ -375,7 +390,9 @@ export default function UserRecordsList({ user, action }: RecordListProps) { ) : ( - No Subdomain + + {t("No Subdomains")} + You don't have any subdomain yet. Start creating record. @@ -398,7 +415,7 @@ export default function UserRecordsList({ user, action }: RecordListProps) { diff --git a/app/(protected)/dashboard/scrape/charts.tsx b/app/(protected)/dashboard/scrape/charts.tsx index adfa3aa..0ce2d0c 100644 --- a/app/(protected)/dashboard/scrape/charts.tsx +++ b/app/(protected)/dashboard/scrape/charts.tsx @@ -1,6 +1,5 @@ import { getScrapeStatsByTypeAndUserId, - getScrapeStatsByUserId, getScrapeStatsByUserId1, } from "@/lib/dto/scrape"; @@ -21,10 +20,7 @@ export default async function DashboardScrapeCharts({ id }: { id: string }) { return ( <> {all_user_logs && all_user_logs.length > 0 && ( - <> -

    Request Statistics

    - - + )}
    {(screenshot_stats.length > 0 || meta_stats.length > 0) && ( @@ -43,7 +39,6 @@ export default async function DashboardScrapeCharts({ id }: { id: string }) { )}
    -

    Request Logs

    ); diff --git a/app/(protected)/dashboard/scrape/daily-chart.tsx b/app/(protected)/dashboard/scrape/daily-chart.tsx index 087f698..2f85bc9 100644 --- a/app/(protected)/dashboard/scrape/daily-chart.tsx +++ b/app/(protected)/dashboard/scrape/daily-chart.tsx @@ -3,9 +3,10 @@ import * as React from "react"; import Link from "next/link"; import { ScrapeMeta } from "@prisma/client"; +import { useTranslations } from "next-intl"; import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; -import { isLink, nFormatter, removeUrlSuffix, timeAgo } from "@/lib/utils"; +import { isLink, nFormatter, removeUrlSuffix } from "@/lib/utils"; import { Card, CardContent, @@ -18,7 +19,7 @@ import { ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart"; -import CountUp from "@/components/dashboard/count-up"; +import { TimeAgoIntl } from "@/components/shared/time-ago"; const chartConfig = { request: { @@ -102,18 +103,21 @@ export function DailyPVUVChart({ data }: { data: ScrapeMeta[] }) { (a, b) => new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime(), ); const latestEntry = sort_data[sort_data.length - 1]; - const latestDate = timeAgo(latestEntry.updatedAt); const latestFrom = latestEntry.type; + const t = useTranslations("Components"); + + const lastRequestInfo = t.rich("last-request-info", { + location: latestFrom, + timeAgo: () => , + }); + return (
    - Total Requests of APIs in Last 30 Days - - Last request from {latestFrom} api about{" "} - {latestDate}. - + {t("Total Requests of APIs in Last 30 Days")} + {lastRequestInfo}
    {["request", "ip"].map((key) => { @@ -125,8 +129,8 @@ export function DailyPVUVChart({ data }: { data: ScrapeMeta[] }) { className="relative z-30 flex flex-1 flex-col items-center justify-center gap-1 border-t px-6 py-2 text-left even:border-l data-[active=true]:bg-muted/50 sm:border-l sm:border-t-0 sm:px-8 sm:py-3" onClick={() => setActiveChart(chart)} > - - {chartConfig[chart].label} + + {t(chartConfig[chart].label)} {nFormatter(dataTotal[key])} diff --git a/app/(protected)/dashboard/scrape/loading.tsx b/app/(protected)/dashboard/scrape/loading.tsx index bbaba43..6a4684e 100644 --- a/app/(protected)/dashboard/scrape/loading.tsx +++ b/app/(protected)/dashboard/scrape/loading.tsx @@ -5,8 +5,8 @@ export default function DashboardRecordsLoading() { return ( <>
    diff --git a/app/(protected)/dashboard/scrape/logs.tsx b/app/(protected)/dashboard/scrape/logs.tsx index 0f0bb49..356950f 100644 --- a/app/(protected)/dashboard/scrape/logs.tsx +++ b/app/(protected)/dashboard/scrape/logs.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { RefreshCwIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; import useSWR, { useSWRConfig } from "swr"; import { nFormatter } from "@/lib/utils"; @@ -49,6 +50,8 @@ const LogsTable = ({ userId, target }) => { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); + const t = useTranslations("Components"); + const [filters, setFilters] = useState({ type: "", ip: "", @@ -141,13 +144,17 @@ const LogsTable = ({ userId, target }) => { - Date + {t("Date")} + + + {t("Type")} - Type - Link + {t("Link")} + + + {t("User")} - User diff --git a/app/(protected)/dashboard/scrape/markdown/loading.tsx b/app/(protected)/dashboard/scrape/markdown/loading.tsx index ec558f0..c2b96ac 100644 --- a/app/(protected)/dashboard/scrape/markdown/loading.tsx +++ b/app/(protected)/dashboard/scrape/markdown/loading.tsx @@ -4,7 +4,10 @@ import { DashboardHeader } from "@/components/dashboard/header"; export default function DashboardRecordsLoading() { return ( <> - + diff --git a/app/(protected)/dashboard/scrape/markdown/page.tsx b/app/(protected)/dashboard/scrape/markdown/page.tsx index 8668e5c..39286a3 100644 --- a/app/(protected)/dashboard/scrape/markdown/page.tsx +++ b/app/(protected)/dashboard/scrape/markdown/page.tsx @@ -21,10 +21,10 @@ export default async function DashboardPage() { return ( <> - + diff --git a/app/(protected)/dashboard/scrape/meta-info/page.tsx b/app/(protected)/dashboard/scrape/meta-info/page.tsx index d35806e..c57b41c 100644 --- a/app/(protected)/dashboard/scrape/meta-info/page.tsx +++ b/app/(protected)/dashboard/scrape/meta-info/page.tsx @@ -5,7 +5,6 @@ import { constructMetadata } from "@/lib/utils"; import { DashboardHeader } from "@/components/dashboard/header"; import ApiReference from "@/components/shared/api-reference"; -import DashboardScrapeCharts from "../charts"; import { MetaScraping } from "../scrapes"; export const metadata = constructMetadata({ @@ -21,10 +20,10 @@ export default async function DashboardPage() { return ( <>
    @@ -48,13 +48,13 @@ export default async function DashboardPage() {
    diff --git a/app/(protected)/dashboard/scrape/qrcode/loading.tsx b/app/(protected)/dashboard/scrape/qrcode/loading.tsx index ec558f0..d61de3b 100644 --- a/app/(protected)/dashboard/scrape/qrcode/loading.tsx +++ b/app/(protected)/dashboard/scrape/qrcode/loading.tsx @@ -4,7 +4,10 @@ import { DashboardHeader } from "@/components/dashboard/header"; export default function DashboardRecordsLoading() { return ( <> - + diff --git a/app/(protected)/dashboard/scrape/qrcode/page.tsx b/app/(protected)/dashboard/scrape/qrcode/page.tsx index 97474f3..19da1d9 100644 --- a/app/(protected)/dashboard/scrape/qrcode/page.tsx +++ b/app/(protected)/dashboard/scrape/qrcode/page.tsx @@ -6,7 +6,7 @@ import { DashboardHeader } from "@/components/dashboard/header"; import ApiReference from "@/components/shared/api-reference"; import QRCodeEditor from "@/components/shared/qr"; -import { CodeLight, QrCodeScraping } from "../scrapes"; +import { CodeLight } from "../scrapes"; export const metadata = constructMetadata({ title: "Url to QR Code API - WR.DO", @@ -21,10 +21,10 @@ export default async function DashboardPage() { return ( <> - Playground + {t("Playground")} - Automate your website screenshots and turn them into stunning - visuals for your applications. + {t( + "Automate your website screenshots and turn them into stunning visuals for your applications", + )} + . @@ -126,9 +130,9 @@ export function ScreenshotScraping({ variant="blue" onClick={handleScrapingScreenshot} disabled={isShoting} - className="rounded-l-none" + className="w-28 rounded-l-none" > - {isShoting ? "Scraping..." : "Send"} + {isShoting ? t("Scraping") : t("Start")}
    @@ -164,6 +168,7 @@ export function MetaScraping({ }: { user: { id: string; apiKey: string }; }) { + const t = useTranslations("Scrape"); const { theme } = useTheme(); const [currentLink, setCurrentLink] = useState("wr.do"); const [protocol, setProtocol] = useState("https://"); @@ -203,8 +208,10 @@ export function MetaScraping({ - Playground - Scrape the meta data of a website. + {t("Playground")} + + {t("Scrape the meta data of a website")}. +
    @@ -239,9 +246,9 @@ export function MetaScraping({ variant="blue" onClick={handleScrapingMeta} disabled={isScraping} - className="rounded-l-none" + className="w-28 rounded-l-none" > - {isScraping ? "Scraping..." : "Send"} + {isScraping ? t("Scraping") : t("Start")}
    @@ -264,6 +271,7 @@ export function MarkdownScraping({ }: { user: { id: string; apiKey: string }; }) { + const t = useTranslations("Scrape"); const { theme } = useTheme(); const [currentLink, setCurrentLink] = useState("wr.do"); const [protocol, setProtocol] = useState("https://"); @@ -334,9 +342,9 @@ export function MarkdownScraping({ variant="blue" onClick={handleScrapingMeta} disabled={isScraping} - className="rounded-l-none" + className="w-28 rounded-l-none" > - {isScraping ? "Scraping..." : "Send"} + {isScraping ? t("Scraping") : t("Start")}
    @@ -359,6 +367,7 @@ export function TextScraping({ }: { user: { id: string; apiKey: string }; }) { + const t = useTranslations("Scrape"); const { theme } = useTheme(); const [currentLink, setCurrentLink] = useState("wr.do"); const [protocol, setProtocol] = useState("https://"); @@ -394,7 +403,7 @@ export function TextScraping({ - Text + {t("Text")}
    @@ -429,9 +438,9 @@ export function TextScraping({ variant="blue" onClick={handleScrapingMeta} disabled={isScraping} - className="rounded-l-none" + className="w-28 rounded-l-none" > - {isScraping ? "Scraping..." : "Send"} + {isScraping ? t("Scraping") : t("Start")}
    @@ -454,6 +463,7 @@ export function QrCodeScraping({ }: { user: { id: string; apiKey: string }; }) { + const t = useTranslations("Scrape"); const { theme } = useTheme(); const [protocol, setProtocol] = useState("https://"); @@ -487,11 +497,7 @@ export function QrCodeScraping({ - Playground - - Automate your website screenshots and turn them into stunning - visuals for your applications. - + {t("Playground")}
    diff --git a/app/(protected)/dashboard/scrape/screenshot/loading.tsx b/app/(protected)/dashboard/scrape/screenshot/loading.tsx index ec558f0..3dcbec4 100644 --- a/app/(protected)/dashboard/scrape/screenshot/loading.tsx +++ b/app/(protected)/dashboard/scrape/screenshot/loading.tsx @@ -4,7 +4,10 @@ import { DashboardHeader } from "@/components/dashboard/header"; export default function DashboardRecordsLoading() { return ( <> - + diff --git a/app/(protected)/dashboard/scrape/screenshot/page.tsx b/app/(protected)/dashboard/scrape/screenshot/page.tsx index 9f3eb4c..eae6066 100644 --- a/app/(protected)/dashboard/scrape/screenshot/page.tsx +++ b/app/(protected)/dashboard/scrape/screenshot/page.tsx @@ -22,10 +22,10 @@ export default async function DashboardPage() { return ( <>
    diff --git a/app/(protected)/dashboard/settings/page.tsx b/app/(protected)/dashboard/settings/page.tsx index 7d63c18..6f46dd7 100644 --- a/app/(protected)/dashboard/settings/page.tsx +++ b/app/(protected)/dashboard/settings/page.tsx @@ -22,7 +22,7 @@ export default async function SettingsPage() { <>
    diff --git a/app/(protected)/dashboard/urls/globe/realtime-chart.tsx b/app/(protected)/dashboard/urls/globe/realtime-chart.tsx index c1fabd0..9d41f53 100644 --- a/app/(protected)/dashboard/urls/globe/realtime-chart.tsx +++ b/app/(protected)/dashboard/urls/globe/realtime-chart.tsx @@ -1,13 +1,7 @@ "use client"; -import { - Bar, - BarChart, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, -} from "recharts"; +import { useTranslations } from "next-intl"; +import { Bar, BarChart, Tooltip, XAxis, YAxis } from "recharts"; import { cn } from "@/lib/utils"; import StatusDot from "@/components/dashboard/status-dot"; @@ -29,6 +23,7 @@ export const RealtimeChart = ({ chartData, totalClicks, }: RealtimeChartProps) => { + const t = useTranslations("Components"); const getTickInterval = (dataLength: number) => { if (dataLength <= 6) return 0; if (dataLength <= 12) return 1; @@ -45,7 +40,7 @@ export const RealtimeChart = ({
    -

    Realtime Visits

    +

    {t("Realtime Visits")}

    {totalClicks}

    diff --git a/app/(protected)/dashboard/urls/live-logs.tsx b/app/(protected)/dashboard/urls/live-logs.tsx index 606ab5f..7d8004c 100644 --- a/app/(protected)/dashboard/urls/live-logs.tsx +++ b/app/(protected)/dashboard/urls/live-logs.tsx @@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { RefreshCwIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useTheme } from "next-themes"; import useSWR, { useSWRConfig } from "swr"; @@ -54,6 +55,8 @@ export default function LiveLog({ admin = false }: { admin?: boolean }) { const [limitDiplay, setLimitDisplay] = useState(100); const newLogsRef = useRef>(new Set()); // Track new log keys + const t = useTranslations("Components"); + const { data: newLogs, error, @@ -151,10 +154,10 @@ export default function LiveLog({ admin = false }: { admin?: boolean }) {
    - Live Log + {t("Live Logs")} - Real-time logs of short link visits. + {t("Real-time logs of short link visits")}.
    @@ -166,7 +169,8 @@ export default function LiveLog({ admin = false }: { admin?: boolean }) { isLive ? "border-dashed border-blue-600 text-blue-500" : "" }`} > - {isLive ? "Stop" : "Live"} + {" "} + {isLive ? t("Stop") : t("Live")} @@ -578,7 +580,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) { }} > - Edit URL + {t("Edit URL")} @@ -612,7 +614,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) { > )} - {timeAgo(short.updatedAt as Date)} + {pathname === "/dashboard" && ( -

    Short URLs

    +

    {t("Short URLs")}

    )} setCurrentView("List")} value="List"> @@ -715,7 +717,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) { }} > - Add URL + {t("Add URL")}
    diff --git a/app/(protected)/setup/guide.tsx b/app/(protected)/setup/guide.tsx index 872caa1..ab79990 100644 --- a/app/(protected)/setup/guide.tsx +++ b/app/(protected)/setup/guide.tsx @@ -1,9 +1,10 @@ "use client"; -import { useEffect, useState, useTransition } from "react"; +import { useState, useTransition } from "react"; import { useRouter } from "next/navigation"; import { AnimatePresence, motion } from "framer-motion"; import { ChevronLeft, ChevronRight } from "lucide-react"; +import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { siteConfig } from "@/config/site"; @@ -27,26 +28,22 @@ export default function StepGuide({ const [direction, setDirection] = useState(0); const [completedSteps, setCompletedSteps] = useState([]); + const t = useTranslations("Common"); + const steps = [ { id: 1, - title: "Set up an administrator", - description: - "Begin by entering your website URL or selecting an example site to reimagine your website with modern themes.", + title: t("Set up an administrator"), component: () => , }, { id: 2, - title: "Add the first domain", - description: - "Check out your reimagined site and click to Migrate & Download.", + title: t("Add the first domain"), component: () => , }, { id: 3, - title: "Congrats on completing setup 🎉", - description: - "Navigate to your GitHub dashboard where you'll manage your repository and project files.", + title: t("Congrats on completing setup 🎉"), component: () => , }, ]; @@ -92,7 +89,7 @@ export default function StepGuide({
    -

    Admin Setup Guide

    +

    {t("Admin Setup Guide")}

    {currentStep} @@ -161,7 +158,7 @@ export default function StepGuide({ )} > - Previous + {t("Previous")} @@ -182,6 +179,7 @@ export default function StepGuide({ function SetAdminRole({ id, email }: { id: string; email: string }) { const [isPending, startTransition] = useTransition(); const [isAdmin, setIsAdmin] = useState(false); + const t = useTranslations("Common"); const handleSetAdmin = async () => { startTransition(async () => { const res = await fetch("/api/setup"); @@ -194,7 +192,7 @@ function SetAdminRole({ id, email }: { id: string; email: string }) { const ReadyBadge = ( - Ready + {t("Ready")} ); @@ -202,14 +200,14 @@ function SetAdminRole({ id, email }: { id: string; email: string }) {
    - Allow Sign Up: + {t("Allow Sign Up")}: {siteConfig.openSignup ? ReadyBadge : }
    - Set {email} as ADMIN: + {t("Set {email} as ADMIN", { email })}: {isAdmin ? ( ReadyBadge @@ -223,30 +221,36 @@ function SetAdminRole({ id, email }: { id: string; email: string }) { {isPending && ( )} - Active Now + {t("Active Now")} )}

    - 📢 Only by becoming an administrator can one access the admin panel - and add domain names. + •{" "} + {t( + "Only by becoming an administrator can one access the admin panel and add domain names", + )} + .

    - 📢 Administrators can set all user permissions, allocate quotas, view - and edit all resources (short links, subdomains, email), etc. + •{" "} + {t( + "Administrators can set all user permissions, allocate quotas, view and edit all resources (short links, subdomains, email), etc", + )} + .

    - 📢 Via{" "} + •{t("Via")}{" "} - quick start + {t("quick start")} {" "} - docs to get more information. + {t("docs to get more information")}.

    @@ -256,6 +260,7 @@ function SetAdminRole({ id, email }: { id: string; email: string }) { function AddDomain({ onNextStep }: { onNextStep: () => void }) { const [isPending, startTransition] = useTransition(); const [domain, setDomain] = useState(""); + const t = useTranslations("Common"); const handleCreateDomain = async () => { if (!domain) { toast.warning("Domain name cannot be empty"); @@ -292,10 +297,10 @@ function AddDomain({ onNextStep }: { onNextStep: () => void }) { }; return (
    - +
    void }) { />

    - Please enter a valid domain name (must be hosted on Cloudflare). + {t( + "Please enter a valid domain name (must be hosted on Cloudflare)", + )} + .

    @@ -318,7 +326,7 @@ function AddDomain({ onNextStep }: { onNextStep: () => void }) { size={"sm"} onClick={onNextStep} > - Or add later + {t("Or add later")}
    diff --git a/app/(protected)/setup/loading.tsx b/app/(protected)/setup/loading.tsx index 2c0fcc0..2e995a7 100644 --- a/app/(protected)/setup/loading.tsx +++ b/app/(protected)/setup/loading.tsx @@ -4,10 +4,7 @@ import { DashboardHeader } from "@/components/dashboard/header"; export default function DashboardLoading() { return ( <> - + diff --git a/app/api/email/inbox/route.ts b/app/api/email/inbox/route.ts index ede75cf..fdaa749 100644 --- a/app/api/email/inbox/route.ts +++ b/app/api/email/inbox/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; -import { getEmailsByEmailAddress } from "@/lib/dto/email"; +import { deleteEmailsByIds, getEmailsByEmailAddress } from "@/lib/dto/email"; import { checkUserStatus } from "@/lib/dto/user"; import { getCurrentUser } from "@/lib/session"; @@ -35,3 +35,22 @@ export async function GET(req: NextRequest) { ); } } + +export async function DELETE(req: NextRequest) { + try { + const user = checkUserStatus(await getCurrentUser()); + if (user instanceof Response) return user; + + const { ids } = await req.json(); + if (!ids) { + return Response.json("ids is required", { status: 400 }); + } + + await deleteEmailsByIds(ids); + + return Response.json("success", { status: 200 }); + } catch (error) { + console.error("[Error]", error); + return Response.json(error.message || "Server error", { status: 500 }); + } +} diff --git a/app/api/record/admin/apply/route.ts b/app/api/record/admin/apply/route.ts index fd8d83f..a23c3c8 100644 --- a/app/api/record/admin/apply/route.ts +++ b/app/api/record/admin/apply/route.ts @@ -20,7 +20,7 @@ export async function POST(req: Request) { const { record: reviewRecord, userId, id } = await req.json(); const record = { ...reviewRecord, - comment: "Created by wr.do (review mode)", + // comment: "Created by wr.do (review mode)", id, }; diff --git a/app/layout.tsx b/app/layout.tsx index 4b023c0..2411e06 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,8 @@ import "@/styles/globals.css"; import { fontHeading, fontSans, fontSatoshi } from "@/assets/fonts"; import { SessionProvider } from "next-auth/react"; +import { NextIntlClientProvider } from "next-intl"; +import { getLocale, getMessages } from "next-intl/server"; import { ThemeProvider } from "next-themes"; import { ViewTransitions } from "next-view-transitions"; @@ -18,10 +20,12 @@ interface RootLayoutProps { export const metadata = constructMetadata(); -export default function RootLayout({ children }: RootLayoutProps) { +export default async function RootLayout({ children }: RootLayoutProps) { + const locale = await getLocale(); + const messages = await getMessages(); return ( - +