From d0e83f697be9411b62c1b9ee758fba49370d29a2 Mon Sep 17 00:00:00 2001 From: William Tso Date: Mon, 7 Apr 2025 23:20:48 +0800 Subject: [PATCH] take shorturl data --- app/analytics/page.tsx | 52 ++++++++++++++++++++++++++++++++ app/components/layout/Header.tsx | 2 +- app/links/page.tsx | 34 ++++++++++++++++++++- app/utils/store.ts | 29 ++++++++++++++++++ package.json | 4 ++- pnpm-lock.yaml | 35 +++++++++++++++++++++ 6 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 app/utils/store.ts diff --git a/app/analytics/page.tsx b/app/analytics/page.tsx index aaefe59..12b7861 100644 --- a/app/analytics/page.tsx +++ b/app/analytics/page.tsx @@ -11,6 +11,8 @@ import { EventsSummary, TimeSeriesData, GeoData, DeviceAnalytics as DeviceAnalyt import { TeamSelector } from '@/app/components/ui/TeamSelector'; import { ProjectSelector } from '@/app/components/ui/ProjectSelector'; import { TagSelector } from '@/app/components/ui/TagSelector'; +import { useSearchParams } from 'next/navigation'; +import { useShortUrlStore } from '@/app/utils/store'; // 事件类型定义 interface Event { @@ -114,6 +116,29 @@ const extractEventInfo = (event: Event) => { }; export default function AnalyticsPage() { + // 从 URL 获取查询参数 + const searchParams = useSearchParams(); + const shorturlParam = searchParams.get('shorturl'); + + // 使用 Zustand store + const { selectedShortUrl } = useShortUrlStore(); + + // 存储 shorturl 参数 + const [selectedShortUrlString, setSelectedShortUrlString] = useState(null); + + // 当 URL 参数变化时更新状态 + useEffect(() => { + if (shorturlParam) { + setSelectedShortUrlString(shorturlParam); + console.log('Selected shorturl from URL:', shorturlParam); + + // 已经通过 Zustand store 传递了完整数据 + if (selectedShortUrl) { + console.log('Complete shortUrl data from store:', selectedShortUrl); + } + } + }, [shorturlParam, selectedShortUrl]); + // 默认日期范围为最近7天 const today = new Date(); const [dateRange, setDateRange] = useState({ @@ -247,6 +272,33 @@ export default function AnalyticsPage() {

Analytics Dashboard

+ {/* 如果有选定的 shorturl,可以显示一个提示,显示更多详细信息 */} + {selectedShortUrl && ( +
+
+ {selectedShortUrl.title || 'Untitled'} + - + {selectedShortUrl.shortUrl} +
+ {selectedShortUrl.tags && selectedShortUrl.tags.length > 0 && ( +
+ {selectedShortUrl.tags.map((tag, index) => ( + + {tag} + + ))} +
+ )} +
+ )} + + {/* 如果只有 URL 参数但没有完整数据,则显示简单提示 */} + {selectedShortUrlString && !selectedShortUrl && ( +
+ Filtered by Short URL: + {selectedShortUrlString} +
+ )} { diff --git a/app/components/layout/Header.tsx b/app/components/layout/Header.tsx index 8c844ad..b2501e3 100644 --- a/app/components/layout/Header.tsx +++ b/app/components/layout/Header.tsx @@ -36,7 +36,7 @@ export default function Header() {
  • - Dashboard + Analytics
  • diff --git a/app/links/page.tsx b/app/links/page.tsx index 618ef5b..9ff3456 100644 --- a/app/links/page.tsx +++ b/app/links/page.tsx @@ -5,6 +5,8 @@ import { getSupabaseClient } from '../utils/supabase'; import { AuthChangeEvent } from '@supabase/supabase-js'; import { Loader2, ExternalLink, Search } from 'lucide-react'; import { TeamSelector } from '@/app/components/ui/TeamSelector'; +import { useRouter } from 'next/navigation'; +import { useShortUrlStore, ShortUrlData } from '@/app/utils/store'; // Define attribute type to avoid using 'any' interface LinkAttributes { @@ -108,7 +110,37 @@ export default function LinksPage() { const [totalLinks, setTotalLinks] = useState(0); const [totalPages, setTotalPages] = useState(0); const [searchDebounce, setSearchDebounce] = useState(null); + const router = useRouter(); + // 使用 Zustand store + const { setSelectedShortUrl } = useShortUrlStore(); + + // 处理链接记录点击 + const handleLinkClick = (shortUrl: string, link: ShortLink, metadata: any) => { + // 编码 shortUrl 以确保 URL 安全 + const encodedShortUrl = encodeURIComponent(shortUrl); + + // 创建完整的 ShortUrlData 对象 + const shortUrlData: ShortUrlData = { + id: link.id, + slug: metadata.slug, + originalUrl: metadata.originalUrl, + title: metadata.title, + shortUrl: shortUrl, + teams: metadata.teamNames, + tags: metadata.tagNames, + projects: metadata.projectNames, + createdAt: metadata.createdAt, + domain: metadata.domain + }; + + // 使用 Zustand store 保存数据 + setSelectedShortUrl(shortUrlData); + + // 导航到 analytics 页面并带上参数 + router.push(`/analytics?shorturl=${encodedShortUrl}`); + }; + // Extract link metadata from attributes const getLinkMetadata = (link: ShortLink) => { try { @@ -391,7 +423,7 @@ export default function LinksPage() { const shortUrl = `https://${metadata.domain}/${metadata.slug}`; return ( - + handleLinkClick(shortUrl, link, metadata)}>
    {metadata.title} diff --git a/app/utils/store.ts b/app/utils/store.ts new file mode 100644 index 0000000..5b714db --- /dev/null +++ b/app/utils/store.ts @@ -0,0 +1,29 @@ +import { create } from 'zustand'; + +// 定义 ShortUrl 数据类型 +export interface ShortUrlData { + id: string; + slug: string; + originalUrl: string; + title?: string; + shortUrl: string; + teams?: any[]; + projects?: any[]; + tags?: any[]; + createdAt?: string; + domain?: string; +} + +// 定义 store 类型 +interface ShortUrlStore { + selectedShortUrl: ShortUrlData | null; + setSelectedShortUrl: (shortUrl: ShortUrlData | null) => void; + clearSelectedShortUrl: () => void; +} + +// 创建 store +export const useShortUrlStore = create((set) => ({ + selectedShortUrl: null, + setSelectedShortUrl: (shortUrl) => set({ selectedShortUrl: shortUrl }), + clearSelectedShortUrl: () => set({ selectedShortUrl: null }), +})); diff --git a/package.json b/package.json index 7ec33e5..3b9f2d7 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,13 @@ "date-fns": "^4.1.0", "lucide-react": "^0.486.0", "next": "15.2.3", + "process": "^0.11.10", "react": "^19.0.0", "react-dom": "^19.0.0", "recharts": "^2.15.1", "tailwind-merge": "^3.1.0", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "zustand": "^5.0.3" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0cbcb7a..42a9428 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: next: specifier: 15.2.3 version: 15.2.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + process: + specifier: ^0.11.10 + version: 0.11.10 react: specifier: ^19.0.0 version: 19.0.0 @@ -65,6 +68,9 @@ importers: uuid: specifier: ^10.0.0 version: 10.0.0 + zustand: + specifier: ^5.0.3 + version: 5.0.3(@types/react@19.0.12)(react@19.0.0) devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -2548,6 +2554,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -3035,6 +3045,24 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -5650,6 +5678,8 @@ snapshots: prelude-ls@1.2.1: {} + process@0.11.10: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -6304,3 +6334,8 @@ snapshots: ws@8.18.1: {} yocto-queue@0.1.0: {} + + zustand@5.0.3(@types/react@19.0.12)(react@19.0.0): + optionalDependencies: + '@types/react': 19.0.12 + react: 19.0.0