Skip to content

Commit 4221408

Browse files
committed
add api client
1 parent 3de1190 commit 4221408

File tree

8 files changed

+175
-24
lines changed

8 files changed

+175
-24
lines changed

src/Footer/Component.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import React from 'react'
44

55
import type { Footer } from '@/payload-types'
66

7-
import { findFeedBySlug } from '@/queries/feeds'
8-
97
import { ThemeSelector } from '@/providers/Theme/ThemeSelector'
108
import { CMSLink } from '@/components/Link'
119
import { Logo } from '@/components/Logo/Logo'
@@ -14,18 +12,11 @@ export async function Footer() {
1412
const footerData: Footer = await getCachedGlobal('footer', 1)()
1513
const navItems = footerData?.navItems || []
1614

17-
let emojis: string[] = []
18-
const emojiData = await findFeedBySlug('logo-emojis')
19-
if (emojiData) {
20-
const json = emojiData.json || []
21-
emojis = Array.isArray(json) ? json.filter((v): v is string => typeof v === 'string') : []
22-
}
23-
2415
return (
2516
<footer className="mt-auto border-t border-border bg-black dark:bg-card text-white">
2617
<div className="container py-8 gap-8 flex flex-col md:flex-row md:justify-between">
2718
<Link className="flex items-center" href="/">
28-
<Logo emojis={emojis} />
19+
<Logo />
2920
</Link>
3021

3122
<div className="flex flex-col-reverse items-start md:flex-row gap-4 md:items-center">

src/Header/Component.client.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ import { HeaderNav } from './Nav'
1111

1212
interface HeaderClientProps {
1313
data: Header
14-
emojis: string[]
1514
}
1615

17-
export const HeaderClient: React.FC<HeaderClientProps> = ({ data, emojis }) => {
16+
export const HeaderClient: React.FC<HeaderClientProps> = ({ data }) => {
1817
/* Storing the value in a useState to avoid hydration errors */
1918
const [theme, setTheme] = useState<string | null>(null)
2019
const { headerTheme, setHeaderTheme } = useHeaderTheme()
@@ -34,7 +33,7 @@ export const HeaderClient: React.FC<HeaderClientProps> = ({ data, emojis }) => {
3433
<header className="container relative z-20 " {...(theme ? { 'data-theme': theme } : {})}>
3534
<div className="py-8 flex justify-between">
3635
<Link className="flex items-center" href="/">
37-
<Logo emojis={emojis} />
36+
<Logo />
3837
</Link>
3938
<HeaderNav data={data} />
4039
</div>

src/Header/Component.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,5 @@ import { findFeedBySlug } from '@/queries/feeds'
99
export async function Header() {
1010
const headerData: Header = await getCachedGlobal('header', 1)()
1111

12-
let emojis: string[] = []
13-
const emojiData = await findFeedBySlug('logo-emojis')
14-
if (emojiData) {
15-
const json = emojiData.json || []
16-
emojis = Array.isArray(json) ? json.filter((v): v is string => typeof v === 'string') : []
17-
}
18-
19-
return <HeaderClient data={headerData} emojis={emojis} />
12+
return <HeaderClient data={headerData} />
2013
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { request } from './request'
2+
3+
export const feedsAPI = {
4+
async getEmojis(): Promise<string[]> {
5+
const res = await request<string[]>('/api/feeds/for/logo-emojis?json')
6+
7+
if (!res.ok) {
8+
throw new Error(res.error?.message || 'Failed to fetch emojis')
9+
}
10+
11+
return res.data
12+
},
13+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// lib/api-client/request.ts
2+
export type ApiError = {
3+
status: number
4+
message: string
5+
}
6+
7+
export type ApiResponse<T> =
8+
| {
9+
ok: true
10+
data: T
11+
}
12+
| {
13+
ok: false
14+
error: ApiError
15+
}
16+
17+
type RequestOptions = {
18+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
19+
body?: unknown
20+
headers?: HeadersInit
21+
credentials?: RequestCredentials
22+
}
23+
24+
export async function request<T>(
25+
url: string,
26+
options: RequestOptions = {},
27+
): Promise<ApiResponse<T>> {
28+
try {
29+
const res = await fetch(url, {
30+
method: options.method ?? 'GET',
31+
headers: {
32+
'Content-Type': 'application/json',
33+
...options.headers,
34+
},
35+
credentials: options.credentials ?? 'include',
36+
body: options.body ? JSON.stringify(options.body) : undefined,
37+
})
38+
39+
// HTTP 错误(404 / 500)
40+
if (!res.ok) {
41+
const message = await safeReadMessage(res)
42+
return {
43+
ok: false,
44+
error: {
45+
status: res.status,
46+
message,
47+
},
48+
}
49+
}
50+
51+
// 204 No Content
52+
if (res.status === 204) {
53+
return {
54+
ok: true,
55+
data: undefined as T,
56+
}
57+
}
58+
59+
const data = (await res.json()) as T
60+
61+
return {
62+
ok: true,
63+
data,
64+
}
65+
} catch (err) {
66+
// 网络错误 / CORS / JSON 解析错误
67+
return {
68+
ok: false,
69+
error: {
70+
status: 0,
71+
message: err instanceof Error ? err.message : 'Unknown network error',
72+
},
73+
}
74+
}
75+
}
76+
77+
async function safeReadMessage(res: Response): Promise<string> {
78+
try {
79+
const json = await res.json()
80+
if (json?.message) return json.message
81+
} catch {}
82+
83+
return res.statusText || 'Request failed'
84+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// lib/hooks/useAsync.ts
2+
'use client'
3+
4+
import { useEffect, useRef, useState } from 'react'
5+
6+
export function useAsync<T>(fn: () => Promise<T>, deps: React.DependencyList = []) {
7+
const [data, setData] = useState<T | null>(null)
8+
const [error, setError] = useState<Error | null>(null)
9+
const [loading, setLoading] = useState(false)
10+
11+
const fnRef = useRef(fn)
12+
fnRef.current = fn
13+
14+
useEffect(() => {
15+
let cancelled = false
16+
17+
setLoading(true)
18+
setError(null)
19+
20+
fnRef
21+
.current()
22+
.then((res) => {
23+
if (!cancelled) setData(res)
24+
})
25+
.catch((err) => {
26+
if (!cancelled) {
27+
setError(err instanceof Error ? err : new Error(String(err)))
28+
}
29+
})
30+
.finally(() => {
31+
if (!cancelled) setLoading(false)
32+
})
33+
34+
return () => {
35+
cancelled = true
36+
}
37+
}, deps)
38+
39+
return { data, error, loading }
40+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// lib/hooks/useEmojis.ts
2+
'use client'
3+
4+
import { feedsAPI } from '../api-client/feeds'
5+
import { useAsync } from './useAsync'
6+
7+
type UseEmojisReturn = {
8+
data: string[] | null
9+
loading: boolean
10+
error: Error | null
11+
}
12+
13+
export function useEmojis(): UseEmojisReturn {
14+
return useAsync<string[]>(() => feedsAPI.getEmojis(), [])
15+
}

src/components/Logo/Logo.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,33 @@
22
import React, { useEffect, useRef, useState } from 'react'
33
import clsx from 'clsx'
44

5+
import { useEmojis } from '@/app/(frontend)/lib/hooks/useFeeds'
6+
57
interface Props {
68
className?: string
7-
emojis: string[]
89
}
910

10-
export const Logo = ({ emojis = [] }: Props) => {
11+
// prettier-ignore
12+
// const EMOJIS = [
13+
// "👨‍💻", "➡️", "🛠️", "➡️", "💾", "➡️",
14+
// "🎨", "➡️", "🖌️", "➡️", "📸", "➡️"
15+
// ]
16+
17+
export const Logo = ({}: Props) => {
18+
const { data } = useEmojis()
19+
const [emojis, setEmojis] = useState<string[]>([])
20+
const [emoji, setEmoji] = useState<string | null>(null)
1121
const [hovered, setHovered] = useState(false)
12-
const [emoji, setEmoji] = useState(emojis[0])
1322
const indexRef = useRef(0)
1423
const timerRef = useRef<NodeJS.Timeout | null>(null)
1524

25+
useEffect(() => {
26+
if (data?.length) {
27+
setEmojis(data)
28+
setEmoji(data[0])
29+
}
30+
}, [data])
31+
1632
useEffect(() => {
1733
if (!hovered || !emoji) {
1834
if (timerRef.current) {

0 commit comments

Comments
 (0)