diff --git a/README-zh.md b/README-zh.md index 2bb14e8..19df2b9 100644 --- a/README-zh.md +++ b/README-zh.md @@ -7,9 +7,10 @@ ## 功能 +- 🔗 **短链生成**:生成附有访问者统计信息的短链接 (支持密码保护) +- 📮 **临时邮箱**:创建多个临时邮箱接收和发送邮件 - 🌐 **多租户支持**:无缝管理多个 DNS 记录 - ⚡ **即时记录创建**:快速设置 CNAME、A 等记录 -- 🔗 **短链生成**:生成附有访问者统计信息的短链接 - 📸 **截图 API**:访问截图 API - � **元数据抓取 API**:访问元数据抓取 API - �😀 **权限管理**:方便审核的管理员面板 diff --git a/README.md b/README.md index d5ae5fc..f14f2c1 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ ## Features +- 🔗 **URL Shortening:** Generate short links with visitor analytic and password - 📮 **Email Support:** Receive emails and send emails - 🌐 **Multi-Tenant Support:** Manage multiple DNS records seamlessly - ⚡ **Instant Record Creation:** Set up CNAME, A, and other records quickly -- 🔗 **URL Shortening:** Generate short links with visitor statistics attached - 📸 **Screenshot API:** Access to screenshot API. - 💯 **Meta Scraping API:** Access to meta scraping API. - 😀 **Permission Management:** A convenient admin panel for auditing diff --git a/app/password-prompt/card.tsx b/app/password-prompt/card.tsx new file mode 100644 index 0000000..23c4aaa --- /dev/null +++ b/app/password-prompt/card.tsx @@ -0,0 +1,174 @@ +"use client"; + +import { useEffect, useRef, useState, useTransition } from "react"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { Eye, EyeOff } from "lucide-react"; + +import { siteConfig } from "@/config/site"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Spotlight } from "@/components/ui/spotlight"; +import { Icons } from "@/components/shared/icons"; + +export default function PasswordPrompt() { + const router = useRouter(); + const searchParams = useSearchParams(); + const slug = searchParams.get("slug"); + const initialPassword = searchParams.get("password") || ""; + const isError = searchParams.get("error") === "1"; + const [password, setPassword] = useState(["", "", "", "", "", ""]); + const [isHidden, setIsHidden] = useState(true); + const [isPending, startTransition] = useTransition(); + const inputRefs = useRef<(HTMLInputElement | null)[]>(Array(6).fill(null)); + + useEffect(() => { + if (initialPassword) { + const paddedPassword = initialPassword + .padEnd(6, "") + .split("") + .slice(0, 6); + setPassword(paddedPassword); + handleSubmit(new Event("submit") as any); + } + }, [initialPassword]); + + const handleChange = (index: number, value: string) => { + if (value.length > 1) return; + const newPassword = [...password]; + newPassword[index] = value; + setPassword(newPassword); + + if (value && index < 5) { + inputRefs.current[index + 1]?.focus(); + } + }; + + const handleKeyDown = (index: number, e: React.KeyboardEvent) => { + if (e.key === "Backspace" && !password[index] && index > 0) { + inputRefs.current[index - 1]?.focus(); + } else if (e.key === "Enter") { + handleSubmit(e as any); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + startTransition(async () => { + e.preventDefault(); + const fullPassword = password.join(""); + if (slug && !isPending && fullPassword.length === 6) { + router.push(`/s/${slug}?password=${encodeURIComponent(fullPassword)}`); + } + }); + }; + + const toggleVisibility = () => { + setIsHidden(!isHidden); + }; + + return ( +
+
+ + +
+
+

+ Protected Link +

+ +
+

+ You are attempting to access a password-protected link.{" "} + Please contact the owner to get the password.{" "} + Learn more about this from our{" "} + + docs + + . +

+
+ +
+
+ {password.map((char, index) => ( + handleChange(index, e.target.value)} + onKeyDown={(e) => handleKeyDown(index, e)} + ref={(el) => (inputRefs.current[index] = el as any)} + maxLength={1} + autoFocus={index === 0} + className="h-12 w-12 rounded-md border border-gray-300 text-center text-lg font-medium text-neutral-100 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + ))} +
+ + {isError && ( +

+ Incorrect password. Please try again. +

+ )} + +
+ + + +
+
+
+
+ +
+ Powered by{" "} + + {siteConfig.name} + +
+
+ ); +} diff --git a/app/password-prompt/layout.tsx b/app/password-prompt/layout.tsx index 9e90bd7..036e95c 100644 --- a/app/password-prompt/layout.tsx +++ b/app/password-prompt/layout.tsx @@ -5,29 +5,9 @@ interface ProtectedLayoutProps { } export default async function Password({ children }: ProtectedLayoutProps) { - // const filteredLinks = sidebarLinks.map((section) => ({ - // ...section, - // items: section.items.filter( - // ({ authorizeOnly }) => !authorizeOnly || authorizeOnly === user.role, - // ), - // })); - return (
- {/*
- - - -
- -
- - - -
-
*/} -
{children} diff --git a/app/password-prompt/page.tsx b/app/password-prompt/page.tsx index 3e928ac..a6fb46d 100644 --- a/app/password-prompt/page.tsx +++ b/app/password-prompt/page.tsx @@ -1,163 +1,12 @@ -"use client"; +import { constructMetadata } from "@/lib/utils"; -import { useEffect, useRef, useState, useTransition } from "react"; -import Link from "next/link"; -import { useRouter, useSearchParams } from "next/navigation"; -import { Eye, EyeOff } from "lucide-react"; +import PasswordPrompt from "./card"; -import { siteConfig } from "@/config/site"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Icons } from "@/components/shared/icons"; +export const metadata = constructMetadata({ + title: "Password Required", + description: "Short link with password", +}); -export default function PasswordPrompt() { - const router = useRouter(); - const searchParams = useSearchParams(); - const slug = searchParams.get("slug"); - const initialPassword = searchParams.get("password") || ""; - const isError = searchParams.get("error") === "1"; - const [password, setPassword] = useState(["", "", "", "", "", ""]); - const [isHidden, setIsHidden] = useState(true); - const [isPending, startTransition] = useTransition(); - const inputRefs = useRef<(HTMLInputElement | null)[]>(Array(6).fill(null)); - - useEffect(() => { - if (initialPassword) { - const paddedPassword = initialPassword - .padEnd(6, "") - .split("") - .slice(0, 6); - setPassword(paddedPassword); - handleSubmit(new Event("submit") as any); - } - }, [initialPassword]); - - const handleChange = (index: number, value: string) => { - if (value.length > 1) return; - const newPassword = [...password]; - newPassword[index] = value; - setPassword(newPassword); - - if (value && index < 5) { - inputRefs.current[index + 1]?.focus(); - } - }; - - const handleKeyDown = (index: number, e: React.KeyboardEvent) => { - if (e.key === "Backspace" && !password[index] && index > 0) { - inputRefs.current[index - 1]?.focus(); - } else if (e.key === "Enter") { - handleSubmit(e as any); - } - }; - - const handleSubmit = (e: React.FormEvent) => { - startTransition(async () => { - e.preventDefault(); - const fullPassword = password.join(""); - if (slug && !isPending && fullPassword.length === 6) { - router.push(`/s/${slug}?password=${encodeURIComponent(fullPassword)}`); - } - }); - }; - - const toggleVisibility = () => { - setIsHidden(!isHidden); - }; - - return ( -
-
-
-

- Protected Link -

- -
-

- You are attempting to access a password-protected link.{" "} - Please contact the owner to get the password.{" "} - Learn more about this from our{" "} - - docs - - . -

-
- -
-
- {password.map((char, index) => ( - handleChange(index, e.target.value)} - onKeyDown={(e) => handleKeyDown(index, e)} - ref={(el) => (inputRefs.current[index] = el as any)} - maxLength={1} - autoFocus={index === 0} - className="h-12 w-12 rounded-md border border-gray-300 text-center text-lg font-medium focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - ))} -
- - {isError && ( -

- Incorrect password. Please try again. -

- )} - -
- - - -
-
-
-
- -
- Powered by{" "} - - {siteConfig.name} - -
-
- ); +export default async function Page() { + return ; } diff --git a/components/shared/icons.tsx b/components/shared/icons.tsx index 1228740..4ef465a 100644 --- a/components/shared/icons.tsx +++ b/components/shared/icons.tsx @@ -84,6 +84,55 @@ export const Icons = { calendar: Calendar, lock: LockKeyhole, unLock: LockKeyholeOpen, + pwdKey: ({ ...props }: LucideProps) => ( + + + + + + + + + + ), fileText: FileText, dashboard: LayoutPanelLeft, download: Download, diff --git a/components/ui/spotlight.tsx b/components/ui/spotlight.tsx new file mode 100644 index 0000000..ed679f2 --- /dev/null +++ b/components/ui/spotlight.tsx @@ -0,0 +1,57 @@ +import React from "react"; + +import { cn } from "@/lib/utils"; + +type SpotlightProps = { + className?: string; + fill?: string; +}; + +export const Spotlight = ({ className, fill }: SpotlightProps) => { + return ( + + + + + + + + + + + + + ); +}; diff --git a/public/sw.js.map b/public/sw.js.map index 307b22b..923a29a 100644 --- a/public/sw.js.map +++ b/public/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/a639c8cdc1e1a975199dbc8b8563536c/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/931da158ed9d626b8d65d3a56844db34/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file diff --git a/tailwind.config.ts b/tailwind.config.ts index 3d5dd2e..a0be851 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -178,6 +178,16 @@ const config = { transform: "translateX(0px)", }, }, + spotlight: { + "0%": { + opacity: "0", + transform: "translate(-72%, -62%) scale(0.5)", + }, + "100%": { + opacity: "1", + transform: "translate(-50%,-40%) scale(1)", + }, + }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", @@ -196,6 +206,8 @@ const config = { "fade-in-right": "fade-in-right 0.4s", "fade-out-left": "fade-out-left 0.4s", "fade-out-right": "fade-out-right 0.4s", + + spotlight: "spotlight 2s ease .75s 1 forwards", }, }, },