Skip to content

Commit

Permalink
add or level-up inline comments
Browse files Browse the repository at this point in the history
  • Loading branch information
gregrickaby committed Feb 13, 2024
1 parent dd5c864 commit 1839cf7
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 52 deletions.
2 changes: 1 addition & 1 deletion app/r/[slug]/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function Loading() {
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<span>Loading...</span>
<span>Loading subreddit...</span>
</div>
)
}
2 changes: 2 additions & 0 deletions components/BackToTop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default function BackToTop() {
useEffect(() => {
// Handle scroll event.
const scrollHandler = () => {
// If the user has scrolled down 200px, show the button.
if (window.scrollY > 200) {
setShowButton(true)
} else {
Expand All @@ -37,6 +38,7 @@ export default function BackToTop() {
})
}

// No button? Bail.
if (!showButton) {
return null
}
Expand Down
4 changes: 2 additions & 2 deletions components/BossButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ export default function BossButton() {
}
}

// Add event listener.
// Add event listeners.
window.addEventListener('keydown', keydownHandler)
window.addEventListener('resize', resizeHandler)

// Cleanup the event listener.
// Cleanup the event listeners.
return () => {
window.removeEventListener('keydown', keydownHandler)
window.removeEventListener('resize', resizeHandler)
Expand Down
103 changes: 70 additions & 33 deletions components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import {fetchSearchResults} from '@/lib/actions'
import {RedditSearchResponse} from '@/lib/types'
import {IconX} from '@tabler/icons-react'
import Link from 'next/link'
import {usePathname, useRouter} from 'next/navigation'
import {usePathname, useRouter, useSearchParams} from 'next/navigation'
import {useCallback, useEffect, useRef, useState} from 'react'
import config from '@/lib/config'

Expand All @@ -25,75 +24,122 @@ function useDebounce(callback: () => void, delay: number, dependencies: any[]) {
* The search component.
*/
export default function Search() {
// Setup the router, path, and search params.
const router = useRouter()
const pathname = usePathname()
const initialSubreddit = pathname.split('/r/')[1]
const searchParams = useSearchParams()

// Setup the initial subreddit, sort, and input ref.
const initialSubreddit = pathname.split('/r/')[1] || ''
const initialSort = searchParams.get('sort') || config.redditApi.sort
const inputRef = useRef<HTMLInputElement>(null)

const [query, setQuery] = useState(initialSubreddit || '')
const [searchFilter, setSearchFilter] = useState(config.redditApi.sort)
// Setup component state.
const [query, setQuery] = useState(initialSubreddit)
const [sort, setSort] = useState(initialSort)
const [results, setResults] = useState<RedditSearchResponse>({})
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(0)

// Search input field handler.
const searchInputHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputQuery = e.target.value.trim()
setQuery(inputQuery)
// Get the input value.
let inputValue = e.target.value

// Trim the input value.
inputValue = inputValue.trim()

// Set component state.
setQuery(inputValue)
setSelectedIndex(0)
setIsDrawerOpen(!!inputQuery)
setIsDrawerOpen(!!inputValue)
}

const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSearchFilter(e.target.value)
if (query.length > 0) {
router.push(`${pathname}?sort=${e.target.value}`)
}
// Sort select field handler.
const sortSelectHandler = (e: React.ChangeEvent<HTMLSelectElement>) => {
// Set the sort value.
const sortValue = e.target.value

// Set component state.
setSort(sortValue)

// If there's no query, return.
if (query.length < 2) return

// If there is a query, push the route with the sort value.
router.push(`${pathname}?sort=${e.target.value}`)
}

const performSearch = useCallback(async () => {
// Setup the search query.
const searchQuery = useCallback(async () => {
// If there's no query, return.
if (query.length < 2) return

// Fetch the search results.
const results = await fetchSearchResults(query)

// Set component state.
setResults(results)
}, [query])

useDebounce(performSearch, 500, [query])
// Debounce the search query.
useDebounce(searchQuery, 500, [query])

// Reset all component state.
const resetSearch = () => {
setQuery('')
setResults({})
setIsDrawerOpen(false)
setSelectedIndex(0)
setSearchFilter(config.redditApi.sort)
setSort(config.redditApi.sort)
}

// Keyboard event handlers.
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// If the drawer is not open, return.
if (!isDrawerOpen) return

// Setup the item count.
const itemCount = results?.data?.children?.length || 0

// Handle the down arrow key event.
if (e.key === 'ArrowDown') {
// If the selected index is the last item, set the selected index to 0.
setSelectedIndex((prevIndex) => (prevIndex + 1) % itemCount)
e.preventDefault()

// Handle the up arrow key event.
} else if (e.key === 'ArrowUp') {
// If the selected index is the first item, set the selected index to the last item.
setSelectedIndex((prevIndex) => (prevIndex - 1 + itemCount) % itemCount)
e.preventDefault()

// Handle the enter key event.
} else if (e.key === 'Enter' && itemCount > 0) {
// Get the selected result.
const selectedResult = results?.data?.children[selectedIndex]

// If the selected result exists, push the route and reset the search.
if (selectedResult) {
router.push(`${selectedResult.data.url}?sort=${searchFilter}`)
router.push(`${selectedResult.data.url}?sort=${sort}`)
resetSearch()
}
e.preventDefault()
}
}

// Add the event listener.
window.addEventListener('keydown', handleKeyDown)

// Cleanup the event listener.
return () => window.removeEventListener('keydown', handleKeyDown)
}, [isDrawerOpen, results, selectedIndex, router, searchFilter])
}, [isDrawerOpen, results, selectedIndex, router, sort])

// If we're on the homepage, reset the search query.
useEffect(() => {
if (pathname === '/' || !initialSubreddit) {
setQuery('')
resetSearch()
} else {
setQuery(initialSubreddit)
}
Expand All @@ -117,22 +163,11 @@ export default function Search() {
value={query}
/>

{query.length > 0 && (
<button
aria-label="clear search"
className="absolute right-28 z-10 rounded bg-zinc-400 p-1 font-mono text-xs text-zinc-200 transition-all duration-300 ease-in-out hover:bg-zinc-600 dark:bg-zinc-700 dark:text-zinc-400"
onClick={resetSearch}
type="reset"
>
<IconX />
</button>
)}

<div className="select-wrapper">
<select
className="ml-2 h-16 w-24 appearance-none rounded px-4 py-2 outline-none"
onChange={handleFilterChange}
value={searchFilter}
onChange={sortSelectHandler}
value={sort}
>
<option value="hot">Hot</option>
<option value="new">New</option>
Expand All @@ -151,7 +186,9 @@ export default function Search() {
className={`m-0 flex items-center justify-start gap-2 p-1 hover:bg-zinc-300 hover:no-underline dark:hover:bg-zinc-800 ${selectedIndex === index ? 'bg-zinc-300 dark:bg-zinc-800' : ''}`}
href={{
pathname: data.url,
query: {sort: searchFilter}
query: {
sort
}
}}
onClick={resetSearch}
prefetch={false}
Expand Down
42 changes: 26 additions & 16 deletions lib/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,45 @@ import {ImageAsset} from '@/lib/types'
* Helper function to get the medium sized image.
*/
export function getMediumImage(images: ImageAsset[]): ImageAsset | null {
// If there are no images, return null.
if (!Array.isArray(images) || images.length === 0) {
return null
}

// Find the medium sized image.
const mediumSize = images.find((res) => res.width === 640)

// Return the medium size, or the last image if not found.
return mediumSize || images[images.length - 1]
}

/**
* Helper function to get the time in "___ ago" format.
*/
export function getTimeAgo(timestampInSeconds: number): string {
const secondsElapsed = Date.now() / 1000 - timestampInSeconds
const minutesElapsed = Math.floor(secondsElapsed / 60)
const hoursElapsed = Math.floor(secondsElapsed / 3600)
const daysElapsed = Math.floor(secondsElapsed / 86400)
const monthsElapsed = Math.floor(secondsElapsed / 2592000)
const yearsElapsed = Math.floor(secondsElapsed / 31536000)
// Constants for time conversions.
const SECOND = 1
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
const MONTH = 30 * DAY
const YEAR = 12 * MONTH

// Calculate elapsed time.
const elapsedSeconds = Math.floor(Date.now() / 1000 - timestampInSeconds)

if (minutesElapsed < 1) {
// Return the appropriate string.
if (elapsedSeconds < MINUTE) {
return 'just now'
} else if (minutesElapsed < 60) {
return `${minutesElapsed}m ago`
} else if (hoursElapsed < 24) {
return `${hoursElapsed}h ago`
} else if (daysElapsed < 30) {
return `${daysElapsed}d ago`
} else if (monthsElapsed < 12) {
return `${monthsElapsed}mo ago`
} else if (elapsedSeconds < HOUR) {
return `${Math.floor(elapsedSeconds / MINUTE)}m ago`
} else if (elapsedSeconds < DAY) {
return `${Math.floor(elapsedSeconds / HOUR)}h ago`
} else if (elapsedSeconds < MONTH) {
return `${Math.floor(elapsedSeconds / DAY)}d ago`
} else if (elapsedSeconds < YEAR) {
return `${Math.floor(elapsedSeconds / MONTH)}mo ago`
} else {
return `${yearsElapsed}y ago`
return `${Math.floor(elapsedSeconds / YEAR)}y ago`
}
}

0 comments on commit 1839cf7

Please sign in to comment.