feat: site is pretty much done now, all functionality is cover just losing some details
This commit is contained in:
parent
fa67c88c6c
commit
50ba72f950
|
@ -0,0 +1,34 @@
|
|||
"use client"
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline"
|
||||
import Link from "next/link"
|
||||
// page https://www.businessinsider.com/web3-music-platforms-blockchain-even-sound-iyk-streaming-services-artists-2023-9#:~:text=Web3%20music%20platforms%20are%20combining,and%20'create%20dope%20consumer%20experiences'&text=Web3%20music%20platforms%20are%20changing,and%20ways%20to%20reach%20fans.
|
||||
import React from "react"
|
||||
|
||||
type Props = {
|
||||
params: {
|
||||
slug: string
|
||||
}
|
||||
}
|
||||
|
||||
const Page = ({ params }: Props) => {
|
||||
// TODO: Explore based on the slug, we can also change the slug to be like the id or something the backend understands
|
||||
// We can also pre-render the article from the backend
|
||||
return (
|
||||
<>
|
||||
<Link href="/" className="w-full mt-1">
|
||||
<button className="px-3 py-2 text-gray-400 hover:bg-gray-800 hover:text-white rounded">
|
||||
<ArrowLeftIcon className="w-4 h-4 inline mr-2 -mt-1" />
|
||||
Go Back Home
|
||||
</button>
|
||||
</Link>
|
||||
<div className="w-full min-h-[calc(100%-80px)] !h-full !mt-1 !mb-0">
|
||||
<iframe
|
||||
className="w-full h-full"
|
||||
src="https://www.businessinsider.com/web3-music-platforms-blockchain-even-sound-iyk-streaming-services-artists-2023-9#:~:text=Web3%20music%20platforms%20are%20combining,and%20'create%20dope%20consumer%20experiences'&text=Web3%20music%20platforms%20are%20changing,and%20ways%20to%20reach%20fans"
|
||||
></iframe>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
|
@ -2,6 +2,8 @@ import type { Metadata } from "next"
|
|||
import Image from "next/image"
|
||||
import { Inter, Jaldi } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import Header from "@/components/LayoutHeader"
|
||||
import Footer from "@/components/LayoutFooter"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" })
|
||||
const jaldi = Jaldi({
|
||||
|
@ -23,55 +25,12 @@ export default function RootLayout({
|
|||
return (
|
||||
<html lang="en">
|
||||
<body className={`font-main bg-gray-900 flex`}>
|
||||
<main className="flex w-full min-h-screen flex-col md:px-40 items-center space-y-10 py-16 mx-auto">
|
||||
<main className="flex w-full min-h-screen flex-col md:px-40 items-center py-16 mx-auto">
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
export const Header = () => {
|
||||
return (
|
||||
<header className="w-full flex flex-row justify-between relative">
|
||||
<div className="flex-1">
|
||||
<Web3NewsLogo />
|
||||
</div>
|
||||
<div className="w-28 h-8 relative">
|
||||
<Image
|
||||
className="-right-4 top-0 absolute"
|
||||
width={28}
|
||||
height={24}
|
||||
src="/lume-logo-sm.png"
|
||||
alt=""
|
||||
/>
|
||||
<span className="left-0 top-[6px] absolute text-white text-opacity-50 text-sm font-normal font-secondary leading-7">
|
||||
a Lume project
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
const Web3NewsLogo = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width="159"
|
||||
height="23"
|
||||
viewBox="0 0 159 23"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0.55 0.299999H3.309V16.172L8.703 8.081H11.059L16.391 16.172V0.299999H19.15V22H17.104L9.85 11.367L2.627 22H0.55V0.299999ZM22.2251 22L22.2561 0.299999H36.7641V3.059H25.0151V9.755H33.6331V12.545H25.0151V19.241H36.7641V22H22.2251ZM39.4824 22L39.4514 0.299999H47.7284C53.1844 0.299999 55.8194 6.903 52.0374 10.654C57.2144 14.157 55.0444 22 48.9374 22H39.4824ZM42.2414 9.786H47.7284C52.0064 9.786 52.2234 3.059 47.7284 3.059H42.2414V9.786ZM42.2414 19.241H48.9374C53.2154 19.241 53.4324 12.514 48.9374 12.514H42.2414V19.241ZM62.7651 22C59.9751 22 57.8051 19.768 57.8051 16.761H60.5021C60.5021 18.342 61.5251 19.303 62.7651 19.303H66.7021C71.1661 19.303 71.2281 12.297 66.7021 12.297H63.3541V10.003H66.0511C70.5771 10.003 70.5461 2.997 66.0511 2.997H62.8891C61.6181 2.997 60.5951 4.02 60.5951 5.415H57.9291C57.9291 2.532 60.1611 0.299999 62.8891 0.299999H66.0511C71.6931 0.299999 74.4831 7.771 69.7091 10.902C75.3511 13.909 72.6541 22 66.7021 22H62.7651Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M77.9783 22.248C76.8003 22.248 75.8703 21.504 75.8703 20.326C75.8703 19.148 76.8003 18.435 77.9783 18.435C79.1253 18.435 80.1173 19.148 80.1173 20.326C80.1173 21.504 79.1253 22.248 77.9783 22.248ZM83.2268 22V0.299999H85.3348L97.9828 16.854V0.299999H100.742V22H98.6028L85.9858 5.477V22H83.2268ZM103.812 22L103.843 0.299999H118.351V3.059H106.602V9.755H115.22V12.545H106.602V19.241H118.351V22H103.812ZM121.038 0.299999H123.797V16.172L129.191 8.081H131.547L136.879 16.172V0.299999H139.638V22H137.592L130.338 11.367L123.115 22H121.038V0.299999ZM142.744 15.614H145.503C145.503 18.218 146.991 19.427 148.944 19.427H152.044C153.408 19.427 156.105 18.59 156.105 15.924C156.105 10.065 143.054 14.064 143.054 6.19C143.054 2.563 145.968 0.175998 148.727 0.175998H152.323C155.733 0.175998 158.337 2.594 158.337 6.624H155.578C155.578 4.082 154.09 2.935 152.106 2.935H148.913C147.208 2.935 145.813 4.237 145.813 6.159C145.813 11.522 158.864 7.306 158.864 16.42C158.864 19.83 155.671 22.186 152.044 22.186H148.944C145.534 22.186 142.744 19.799 142.744 15.614Z"
|
||||
fill="#ACF9C0"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { FormEvent } from "react"
|
||||
import Link from "next/link"
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline"
|
||||
import { formatDate } from "@/utils"
|
||||
import { formatDate, getResults } from "@/utils"
|
||||
import SimplifiedSearchBar from "@/components/SimplifiedSearchBar"
|
||||
|
||||
type Props = {
|
||||
|
@ -16,17 +16,25 @@ const Page = async ({ searchParams }: Props) => {
|
|||
|
||||
return (
|
||||
<div className="w-full items-center text-lg">
|
||||
<SimplifiedSearchBar value={query} placeholder={query ? undefined : "Search for web3 news from the community"}/>
|
||||
|
||||
<button className="my-4 px-3 py-2 text-gray-400 hover:bg-gray-800 hover:text-white rounded">
|
||||
<ArrowLeftIcon className="w-4 h-4 inline mr-2 -mt-1" />
|
||||
Go Back Home
|
||||
</button>
|
||||
<SimplifiedSearchBar
|
||||
value={query}
|
||||
placeholder={
|
||||
query ? undefined : "Search for web3 news from the community"
|
||||
}
|
||||
/>
|
||||
|
||||
<Link href="/">
|
||||
<button className="my-4 px-3 py-2 text-gray-400 hover:bg-gray-800 hover:text-white rounded">
|
||||
<ArrowLeftIcon className="w-4 h-4 inline mr-2 -mt-1" />
|
||||
Go Back Home
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
{results.length > 0 && (
|
||||
<>
|
||||
{results.map((item) => (
|
||||
<div
|
||||
<Link
|
||||
href={`/article/${item.slug}`}
|
||||
key={item.id}
|
||||
className="flex cursor-pointer flex-row items-center space-x-5 my-2 py-2 px-4 hover:bg-gray-800 rounded-md"
|
||||
>
|
||||
|
@ -34,7 +42,7 @@ const Page = async ({ searchParams }: Props) => {
|
|||
{formatDate(item.timestamp)}
|
||||
</span>
|
||||
<h3 className="text-md font-semibold text-white">{item.title}</h3>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<Link href={`/search?q=${encodeURIComponent(query)}`}>
|
||||
<button className="rounded mt-4 flex justify-center items-center bg-gray-800 mx-auto w-44 py-7 text-white hover:bg-gray-800/50 transition-colors">
|
||||
|
@ -47,27 +55,4 @@ const Page = async ({ searchParams }: Props) => {
|
|||
)
|
||||
}
|
||||
|
||||
async function getResults({
|
||||
query
|
||||
}: {
|
||||
query?: string
|
||||
}): Promise<SearchResult[]> {
|
||||
if (!query) return []
|
||||
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
timestamp: new Date(),
|
||||
title: "Mock Title 1",
|
||||
description: "Mock Description 1"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
timestamp: new Date(),
|
||||
title: "Mock Title 2",
|
||||
description: "Mock Description 2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default Page
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import React from "react"
|
||||
|
||||
type Props = {}
|
||||
|
||||
const Footer = ({}: Props) => {
|
||||
return (<div className="w-full mt-5 flex flex-row items-center justify-center text-gray-400">
|
||||
<a>About Web3.news</a>
|
||||
<div className="h-7 w-[1px] bg-current mx-4" />
|
||||
<a className="text-white">Contribute to the cause</a>
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default Footer
|
|
@ -0,0 +1,50 @@
|
|||
import React from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
type Props = {}
|
||||
|
||||
export const Header = ({}: Props) => {
|
||||
return (
|
||||
<header className="w-full flex flex-row justify-between relative">
|
||||
<div className="flex-1">
|
||||
<Web3NewsLogo />
|
||||
</div>
|
||||
<div className="w-28 h-8 relative">
|
||||
<Image
|
||||
className="-right-4 top-0 absolute"
|
||||
width={28}
|
||||
height={24}
|
||||
src="/lume-logo-sm.png"
|
||||
alt=""
|
||||
/>
|
||||
<span className="left-0 top-[6px] absolute text-white text-opacity-50 text-sm font-normal font-secondary leading-7">
|
||||
a Lume project
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
const Web3NewsLogo = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width="159"
|
||||
height="23"
|
||||
viewBox="0 0 159 23"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0.55 0.299999H3.309V16.172L8.703 8.081H11.059L16.391 16.172V0.299999H19.15V22H17.104L9.85 11.367L2.627 22H0.55V0.299999ZM22.2251 22L22.2561 0.299999H36.7641V3.059H25.0151V9.755H33.6331V12.545H25.0151V19.241H36.7641V22H22.2251ZM39.4824 22L39.4514 0.299999H47.7284C53.1844 0.299999 55.8194 6.903 52.0374 10.654C57.2144 14.157 55.0444 22 48.9374 22H39.4824ZM42.2414 9.786H47.7284C52.0064 9.786 52.2234 3.059 47.7284 3.059H42.2414V9.786ZM42.2414 19.241H48.9374C53.2154 19.241 53.4324 12.514 48.9374 12.514H42.2414V19.241ZM62.7651 22C59.9751 22 57.8051 19.768 57.8051 16.761H60.5021C60.5021 18.342 61.5251 19.303 62.7651 19.303H66.7021C71.1661 19.303 71.2281 12.297 66.7021 12.297H63.3541V10.003H66.0511C70.5771 10.003 70.5461 2.997 66.0511 2.997H62.8891C61.6181 2.997 60.5951 4.02 60.5951 5.415H57.9291C57.9291 2.532 60.1611 0.299999 62.8891 0.299999H66.0511C71.6931 0.299999 74.4831 7.771 69.7091 10.902C75.3511 13.909 72.6541 22 66.7021 22H62.7651Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M77.9783 22.248C76.8003 22.248 75.8703 21.504 75.8703 20.326C75.8703 19.148 76.8003 18.435 77.9783 18.435C79.1253 18.435 80.1173 19.148 80.1173 20.326C80.1173 21.504 79.1253 22.248 77.9783 22.248ZM83.2268 22V0.299999H85.3348L97.9828 16.854V0.299999H100.742V22H98.6028L85.9858 5.477V22H83.2268ZM103.812 22L103.843 0.299999H118.351V3.059H106.602V9.755H115.22V12.545H106.602V19.241H118.351V22H103.812ZM121.038 0.299999H123.797V16.172L129.191 8.081H131.547L136.879 16.172V0.299999H139.638V22H137.592L130.338 11.367L123.115 22H121.038V0.299999ZM142.744 15.614H145.503C145.503 18.218 146.991 19.427 148.944 19.427H152.044C153.408 19.427 156.105 18.59 156.105 15.924C156.105 10.065 143.054 14.064 143.054 6.19C143.054 2.563 145.968 0.175998 148.727 0.175998H152.323C155.733 0.175998 158.337 2.594 158.337 6.624H155.578C155.578 4.082 154.09 2.935 152.106 2.935H148.913C147.208 2.935 145.813 4.237 145.813 6.159C145.813 11.522 158.864 7.306 158.864 16.42C158.864 19.83 155.671 22.186 152.044 22.186H148.944C145.534 22.186 142.744 19.799 142.744 15.614Z"
|
||||
fill="#ACF9C0"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
|
@ -1,70 +1,70 @@
|
|||
"use client"
|
||||
|
||||
import React, { FormEvent, useCallback, useEffect, useRef, useState } from "react"
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon
|
||||
} from "@heroicons/react/24/outline" // Assuming usage of Heroicons for icons
|
||||
import React, {
|
||||
FormEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from "react"
|
||||
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/outline" // Assuming usage of Heroicons for icons
|
||||
import { flushSync } from "react-dom"
|
||||
import Link from "next/link"
|
||||
import { usePathname, useSearchParams, useRouter } from "next/navigation"
|
||||
import { formatDate } from "@/utils"
|
||||
import { formatDate, getResults } from "@/utils"
|
||||
|
||||
type Props = {
|
||||
variant: "default" | "simplified"
|
||||
}
|
||||
|
||||
const SearchBar = ({variant}: Props) => {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [query, setQuery] = useState(searchParams.get("q") ?? "");
|
||||
const SearchBar = ({ variant }: Props) => {
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const [query, setQuery] = useState(searchParams.get("q") ?? "")
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [activeInput, setActiveInput] = useState(true)
|
||||
const [results, setResults] = useState<
|
||||
SearchResult[]
|
||||
>([])
|
||||
const [results, setResults] = useState<SearchResult[]>([])
|
||||
|
||||
const handleSearch = useCallback(async (event?: FormEvent<HTMLFormElement>) => {
|
||||
event?.preventDefault()
|
||||
setIsLoading(true)
|
||||
const newSearchParams = new URLSearchParams(searchParams)
|
||||
const handleSearch = useCallback(
|
||||
async (event?: FormEvent<HTMLFormElement>) => {
|
||||
event?.preventDefault()
|
||||
setIsLoading(true)
|
||||
const newSearchParams = new URLSearchParams(searchParams)
|
||||
|
||||
if(query) {
|
||||
newSearchParams.set('q', query)
|
||||
} else {
|
||||
newSearchParams.delete('q')
|
||||
}
|
||||
|
||||
router.push(`${pathname}?${newSearchParams}`)
|
||||
|
||||
// Perform search and update results state
|
||||
// Assume fetchResults is a function that fetches search results
|
||||
// const searchResults = await fetchResults(query);
|
||||
// Mock the search results
|
||||
const searchResults = [
|
||||
{
|
||||
id: 1,
|
||||
timestamp: new Date(),
|
||||
title: "Mock Title 1",
|
||||
description: "Mock Description 1"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
timestamp: new Date(),
|
||||
title: "Mock Title 2",
|
||||
description: "Mock Description 2"
|
||||
if (query) {
|
||||
newSearchParams.set("q", query)
|
||||
} else {
|
||||
newSearchParams.delete("q")
|
||||
}
|
||||
|
||||
router.push(`${pathname}?${newSearchParams}`)
|
||||
|
||||
// Perform search and update results state
|
||||
// Assume fetchResults is a function that fetches search results
|
||||
// const searchResults = await fetchResults(query);
|
||||
// Mock the search results
|
||||
const searchResults = await getResults({ query })
|
||||
|
||||
setResults(searchResults)
|
||||
setIsLoading(false)
|
||||
setActiveInput(false)
|
||||
},
|
||||
[
|
||||
query,
|
||||
setResults,
|
||||
setIsLoading,
|
||||
setActiveInput,
|
||||
searchParams,
|
||||
router,
|
||||
pathname
|
||||
]
|
||||
setResults(searchResults)
|
||||
setIsLoading(false)
|
||||
setActiveInput(false)
|
||||
}, [query, setResults, setIsLoading, setActiveInput, searchParams, router, pathname])
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if(searchParams.get("q") && searchParams.get("q") !== "") {
|
||||
handleSearch();
|
||||
if (searchParams.get("q") && searchParams.get("q") !== "") {
|
||||
handleSearch()
|
||||
}
|
||||
}, [searchParams, handleSearch])
|
||||
|
||||
|
@ -128,10 +128,10 @@ const SearchBar = ({variant}: Props) => {
|
|||
<span
|
||||
className="block w-full flex-1 text-blue-300"
|
||||
onClick={() => {
|
||||
flushSync(() => {
|
||||
setActiveInput(true)
|
||||
})
|
||||
inputRef.current?.focus()
|
||||
flushSync(() => {
|
||||
setActiveInput(true)
|
||||
})
|
||||
inputRef.current?.focus()
|
||||
}}
|
||||
>
|
||||
{'"'}
|
||||
|
@ -162,7 +162,8 @@ const SearchBar = ({variant}: Props) => {
|
|||
<>
|
||||
<hr className="my-4 border-1" />
|
||||
{results.map((item) => (
|
||||
<div
|
||||
<Link
|
||||
href={`/article/${item.slug}`}
|
||||
key={item.id}
|
||||
className="flex cursor-pointer flex-row items-center space-x-5 my-2 py-2 px-4 hover:bg-gray-800 rounded-md"
|
||||
>
|
||||
|
@ -170,11 +171,12 @@ const SearchBar = ({variant}: Props) => {
|
|||
{formatDate(item.timestamp)}
|
||||
</span>
|
||||
<h3 className="text-md font-semibold text-white">{item.title}</h3>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<Link href={`/search?q=${encodeURIComponent(query)}`}>
|
||||
<button className="mt-4 flex justify-center items-center bg-secondary w-full py-7 text-white hover:bg-teal-800 transition-colors">
|
||||
{results.length}+ search results for <span className="text-blue-300 ml-1">{query}</span>
|
||||
{results.length}+ search results for{" "}
|
||||
<span className="text-blue-300 ml-1">{query}</span>
|
||||
<ChevronRightIcon className="w-5 h-5 inline ml-2 mt-[1px]" />
|
||||
</button>
|
||||
</Link>
|
||||
|
|
|
@ -3,4 +3,5 @@ type SearchResult = {
|
|||
timestamp: Date
|
||||
title: string
|
||||
description: string
|
||||
slug: string
|
||||
}
|
41
src/utils.ts
41
src/utils.ts
|
@ -2,11 +2,36 @@ import { formatDistanceToNow } from "date-fns"
|
|||
|
||||
// Utility function to format dates
|
||||
export const formatDate = (date: string | Date) => {
|
||||
const distance = formatDistanceToNow(new Date(date), { addSuffix: true })
|
||||
return distance
|
||||
.replace(/less than a minute?/, "<1m")
|
||||
.replace(/ minutes?/, "m")
|
||||
.replace(/ hours?/, "h")
|
||||
.replace(/ days?/, "d")
|
||||
.replace(/ weeks?/, "w")
|
||||
}
|
||||
const distance = formatDistanceToNow(new Date(date), { addSuffix: true })
|
||||
return distance
|
||||
.replace(/less than a minute?/, "<1m")
|
||||
.replace(/ minutes?/, "m")
|
||||
.replace(/ hours?/, "h")
|
||||
.replace(/ days?/, "d")
|
||||
.replace(/ weeks?/, "w")
|
||||
}
|
||||
|
||||
export async function getResults({
|
||||
query
|
||||
}: {
|
||||
query?: string
|
||||
}): Promise<SearchResult[]> {
|
||||
if (!query) return []
|
||||
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
timestamp: new Date(),
|
||||
title: "Mock Title 1",
|
||||
description: "Mock Description 1",
|
||||
slug: "hello-world"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
timestamp: new Date(),
|
||||
title: "Mock Title 2",
|
||||
description: "Mock Description 2",
|
||||
slug: "hello-world-2"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue