Skip to content

Commit 5dc42a0

Browse files
quote
1 parent 4798a5f commit 5dc42a0

File tree

6 files changed

+148
-10
lines changed

6 files changed

+148
-10
lines changed

src/api/fetchQuote.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Dispatch, SetStateAction } from 'react'
2+
import { QuoteType } from '@/types/types'
3+
import { setItemInSessionStorage } from '@/utils/setItemInSessionStorage'
4+
5+
const fetchQuote = async (
6+
setQuote: Dispatch<SetStateAction<QuoteType | null>>
7+
) => {
8+
try {
9+
const response = await fetch(
10+
'https://jsonplaceholder.typicode.com/posts/1',
11+
{
12+
method: 'GET',
13+
}
14+
)
15+
16+
if (!response.ok) {
17+
console.error('An error occurred while fetching the quote.')
18+
throw new Error('Network response was not ok')
19+
}
20+
21+
const data = await response.json()
22+
if (!data) {
23+
console.error('An error occurred while fetching the quote.')
24+
throw new Error('Received data was not ok')
25+
}
26+
27+
setQuote(() => {
28+
setItemInSessionStorage('quote', data)
29+
return data
30+
})
31+
} catch (error: unknown) {
32+
console.error('An error occurred while fetching the quote.', error)
33+
}
34+
}
35+
36+
export default fetchQuote

src/app/page.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import Feed from '@/components/feed/Feed'
55
import Footer from '@/components/footer/Footer'
66
import Hero from '@/components/hero/Hero'
77
import Navigation from '@/components/navigation/Navigation'
8+
import Quote from '@/components/quote/Quote'
89
import Users from '@/components/users/Users'
10+
import { ContextQuote } from '@/context/ContextQuote'
911
import { ContextTopTenPosts } from '@/context/ContextTopTenPosts'
1012
import { ContextTopUsers } from '@/context/ContextTopUsers'
1113
import { useFocusTrap } from '@/hooks/useFocusTrap'
1214
import { useSetWindowScrollY } from '@/hooks/useSetWindowScrollY'
1315

1416
export default function Home() {
17+
const quoteRef = useRef<HTMLQuoteElement>(null)
1518
const topTenPostsRef = useRef<HTMLDivElement | null>(null)
1619
const topUsersRef = useRef<HTMLDivElement | null>(null)
1720

@@ -20,15 +23,18 @@ export default function Home() {
2023

2124
return (
2225
<div className="min-h-svh flex flex-col items-center justify-start bg-gradient-to-b from-stone-700 to-stone-800">
23-
<ContextTopTenPosts.Provider value={topTenPostsRef}>
24-
<ContextTopUsers.Provider value={topUsersRef}>
25-
<Navigation />
26-
<Hero />
27-
<Users />
28-
<Feed />
29-
<Footer />
30-
</ContextTopUsers.Provider>
31-
</ContextTopTenPosts.Provider>
26+
<ContextQuote.Provider value={quoteRef}>
27+
<ContextTopTenPosts.Provider value={topTenPostsRef}>
28+
<ContextTopUsers.Provider value={topUsersRef}>
29+
<Navigation />
30+
<Hero />
31+
<Users />
32+
<Quote />
33+
<Feed />
34+
<Footer />
35+
</ContextTopUsers.Provider>
36+
</ContextTopTenPosts.Provider>
37+
</ContextQuote.Provider>
3238
</div>
3339
)
3440
}

src/components/navigation/Navigation.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@
22

33
import { useContext, useRef, useState } from 'react'
44
import NavigationLogo from './NavigationLogo'
5+
import { ContextQuote } from '@/context/ContextQuote'
56
import { ContextTopTenPosts } from '@/context/ContextTopTenPosts'
67
import { ContextTopUsers } from '@/context/ContextTopUsers'
78
import { useNavigationOpacity } from '@/hooks/useNavigationOpacity'
89
import NavigationButton from './NavigationButton'
910

1011
const Navigation = () => {
12+
const contextQuote = useContext(ContextQuote)
13+
if (!contextQuote) {
14+
throw new Error(
15+
'Navigation must be used within a ContextQuote.Provider'
16+
)
17+
}
18+
const quoteRef = contextQuote
19+
1120
const contextTopTenPosts = useContext(ContextTopTenPosts)
1221
if (!contextTopTenPosts) {
1322
throw new Error(
@@ -29,6 +38,18 @@ const Navigation = () => {
2938

3039
useNavigationOpacity({ setNavOpacity, timerRef })
3140

41+
const handleScrollToQuote = () => {
42+
if (quoteRef.current) {
43+
const OFFSET = 96
44+
const top =
45+
quoteRef.current.getBoundingClientRect().top +
46+
window.scrollY -
47+
OFFSET
48+
49+
window.scrollTo({ top, behavior: 'smooth' })
50+
}
51+
}
52+
3253
const handleScrollToTopUsers = () => {
3354
if (topUsersRef.current) {
3455
const OFFSET = 64
@@ -43,7 +64,7 @@ const Navigation = () => {
4364

4465
const handleScrollToTopTenPosts = () => {
4566
if (topTenPostsRef.current) {
46-
const OFFSET = 64
67+
const OFFSET = 96
4768
const top =
4869
topTenPostsRef.current.getBoundingClientRect().top +
4970
window.scrollY -
@@ -66,6 +87,10 @@ const Navigation = () => {
6687
handleScroll={handleScrollToTopUsers}
6788
label="Most Active Users"
6889
/>
90+
<NavigationButton
91+
handleScroll={handleScrollToQuote}
92+
label="Quote of the Day"
93+
/>
6994
<NavigationButton
7095
handleScroll={handleScrollToTopTenPosts}
7196
label="Top 10 Posts"

src/components/quote/Quote.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { useCallback, useContext, useEffect, useState } from 'react'
2+
import { ContextQuote } from '@/context/ContextQuote'
3+
import { QuoteType } from '@/types/types'
4+
import fetchQuote from '@/api/fetchQuote'
5+
import { Skeleton } from '@/components/ui/skeleton'
6+
import { getItemFromSessionStorage } from '@/utils/getItemFromSessionStorage'
7+
8+
const Quote = () => {
9+
const contextQuote = useContext(ContextQuote)
10+
if (!contextQuote) {
11+
throw new Error('Users must be used within a ContextQuote.Provider')
12+
}
13+
const quoteRef = contextQuote
14+
15+
const [quote, setQuote] = useState<QuoteType | null>(null)
16+
const [isLoading, setIsLoading] = useState(false)
17+
18+
const loadQuote = useCallback(async () => {
19+
if (isLoading) {
20+
return
21+
}
22+
setIsLoading(true)
23+
await fetchQuote(setQuote)
24+
setIsLoading(false)
25+
}, [isLoading])
26+
27+
useEffect(() => {
28+
const parsedStorageData = getItemFromSessionStorage()
29+
setQuote(parsedStorageData?.quote || [])
30+
31+
if (!parsedStorageData?.quote?.length) {
32+
loadQuote()
33+
}
34+
}, [loadQuote])
35+
36+
return (
37+
<blockquote
38+
className="flex flex-col gap-2 sm:gap-4 text-center w-full max-w-7xl bg-stone-100 text-stone-950 p-3 sm:p-4 lg:p-6 my-6 lg:my-8"
39+
ref={quoteRef}
40+
>
41+
<h2 className="self-start text-very-large font-semibold underline">
42+
Quote of the Day
43+
</h2>
44+
{quote ? (
45+
<div className="flex flex-col">
46+
<i>"{quote.body}"</i>
47+
<p className="self-end px-4"> - unknown author</p>
48+
</div>
49+
) : (
50+
<div className="flex flex-col gap-2">
51+
<Skeleton className="h-[18px] w-full rounded-full" />
52+
<Skeleton className="self-end h-[18px] w-32 rounded-full" />
53+
</div>
54+
)}
55+
</blockquote>
56+
)
57+
}
58+
59+
export default Quote

src/context/ContextQuote.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createContext, RefObject } from 'react'
2+
3+
export const ContextQuote = createContext<
4+
RefObject<HTMLQuoteElement | null> | undefined
5+
>(undefined)

src/types/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ export type FeedItemsType = {
66
body: string
77
}
88

9+
export type QuoteType = {
10+
userId: number
11+
id: number
12+
title: string
13+
body: string
14+
}
15+
916
export type UsersType = {
1017
id: number
1118
name: string

0 commit comments

Comments
 (0)