Skip to content

Commit 5447bc2

Browse files
authored
feat: qa-0421 (#40)
* feat: change main image * feat: change favicon * feat: client component * feat: add blog category chip * feat: update header * feat: useURLSearchParams * feat: style * feat: value card * feat: key * feat: suspence
1 parent b70f73d commit 5447bc2

23 files changed

+359
-174
lines changed

app/_components/about/OurValue.tsx

+3-33
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
"use client";
22

33
import { OUR_VALUE } from "@/app/_constants/about";
4-
import { ArrowUpRight, Plus } from "lucide-react";
5-
import Image from "next/image";
4+
import { ArrowUpRight } from "lucide-react";
65
import { Button } from "../common/button";
76
import { openToast } from "../common/toast";
8-
import { addBasePath } from "@/app/_lib/add-base-path";
7+
import ValueCard from "./ValueCard";
98

109
export default function OurValue() {
1110
return (
@@ -22,36 +21,7 @@ export default function OurValue() {
2221
</p>
2322
</div>
2423
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
25-
{OUR_VALUE.map(({ title, content, thumbnail }, i) => (
26-
<div key={i} className="flex flex-col gap-4">
27-
<h3 className="text-2xl text-[#002424] md:hidden">{title}</h3>
28-
<div className="group relative aspect-[703/518] overflow-hidden rounded-2xl text-[#E6FDFC]">
29-
<Image
30-
src={addBasePath(thumbnail)}
31-
alt={title}
32-
fill
33-
objectFit="cover"
34-
/>
35-
<div className="absolute top-0 h-full w-full bg-[#000000]/40 opacity-0 backdrop-blur-2xl transition-all duration-300 group-hover:opacity-100" />
36-
37-
<div className="absolute top-10 right-10 flex h-10 w-10 items-center justify-center rounded-full border-2 border-[#FFFFFF] group-hover:hidden">
38-
<Plus size={32} strokeWidth={1.5} color="#FFFFFF" />
39-
</div>
40-
41-
<div className="flex h-full w-full flex-col justify-center p-8 md:p-14">
42-
<p className="hidden translate-y-[-20px] transform text-base opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 md:block">
43-
Our Value {i + 1}
44-
</p>
45-
<h3 className="mt-4 hidden translate-y-[-20px] transform text-[32px] opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 md:block">
46-
{title}
47-
</h3>
48-
<p className="translate-y-[20px] transform text-sm opacity-0 transition-all duration-300 group-hover:block group-hover:translate-y-0 group-hover:opacity-60 md:mt-auto md:text-xl">
49-
{content}
50-
</p>
51-
</div>
52-
</div>
53-
</div>
54-
))}
24+
{OUR_VALUE.map((props, i) => <ValueCard key={i} {...props} index={i} />)}
5525
</div>
5626
</div>
5727

app/_components/about/ValueCard.tsx

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { addBasePath } from "@/app/_lib/add-base-path";
2+
import clsx from "clsx";
3+
import { Plus, X } from "lucide-react";
4+
import Image from "next/image";
5+
import { useState } from "react";
6+
7+
interface ValueCardProps {
8+
title: string;
9+
content: string;
10+
thumbnail: string;
11+
index: number;
12+
}
13+
14+
export default function ValueCard({ title, content, thumbnail, index }: ValueCardProps) {
15+
const [showContent, setShowContent] = useState(false);
16+
17+
return (
18+
<div key={index} className="flex flex-col gap-4">
19+
<h3 className="text-2xl text-[#002424] md:hidden">{title}</h3>
20+
<div className="group relative aspect-[703/518] overflow-hidden rounded-2xl text-[#E6FDFC]">
21+
<Image
22+
src={addBasePath(thumbnail)}
23+
alt={title}
24+
fill
25+
objectFit="cover"
26+
/>
27+
<div className={clsx(["absolute top-0 h-full w-full bg-[#000000]/40 opacity-0 backdrop-blur-2xl transition-all duration-300 md:group-hover:opacity-100", showContent && "opacity-100"])} />
28+
29+
<div
30+
className="absolute top-10 right-10 flex h-10 w-10 items-center justify-center rounded-full border-2 border-[#FFFFFF] cursor-pointer md:group-hover:hidden"
31+
onClick={() => setShowContent((prev) => !prev)}
32+
>
33+
{showContent ? <X size={32} strokeWidth={1.5} color="#FFFFFF" /> : <Plus size={32} strokeWidth={1.5} color="#FFFFFF" />}
34+
</div>
35+
36+
<div className="flex h-full w-full flex-col justify-center p-8 md:p-14">
37+
<p className="hidden translate-y-[-20px] transform text-base opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 md:block">
38+
Our Value {index + 1}
39+
</p>
40+
<h3 className="mt-4 hidden translate-y-[-20px] transform text-[32px] opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100 md:block">
41+
{title}
42+
</h3>
43+
44+
<p
45+
className={clsx(["mt-4 text-sm transition-opacity duration-300 md:mt-auto md:text-xl opacity-0 md:group-hover:block md:group-hover:opacity-60", showContent && "block opacity-60"])}
46+
>
47+
{content}
48+
</p>
49+
</div>
50+
</div>
51+
</div>
52+
)
53+
}

app/_components/blog/BlogList.tsx

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client";
2+
3+
import { useURLSearchParams } from "@/app/_hooks/useURLSearchParams";
4+
import Link from "next/link";
5+
import { Item } from "nextra/normalize-pages";
6+
7+
export default function BlogList({ posts }: { posts: Item[] }) {
8+
const { searchParams } = useURLSearchParams();
9+
const tag = searchParams.get("tag");
10+
11+
const filteredPosts = posts.filter((post) => {
12+
if (!post.frontMatter) return false;
13+
if (tag && !post.frontMatter.tags?.includes(tag)) return false;
14+
return true;
15+
});
16+
17+
if (filteredPosts.length === 0) {
18+
return (
19+
<p className="text-base text-[#002424] mt-4 text-center">No results found.</p>
20+
)
21+
}
22+
23+
return (
24+
<div className="flex flex-col items-center gap-3">
25+
{filteredPosts.map(({ frontMatter, route }) => {
26+
const { title, date, tags, author } = frontMatter;
27+
return (
28+
<Link
29+
href={`/blog${route}`}
30+
key={route}
31+
className="group flex w-full hover:bg-black/10 dark:hover:bg-white/20 duration-300 transition-all cursor-pointer flex-col gap-2 rounded-xl border-gray-700 p-4"
32+
>
33+
<h1 className="h-16 text-lg font-medium text-zinc-800 dark:text-zinc-50">
34+
{title}
35+
</h1>
36+
<div className="flex w-full items-center justify-between">
37+
<div className="flex gap-1 text-base text-gray-400 dark:text-gray-300">
38+
<p>{date}</p>
39+
<p></p>
40+
<p>{author}</p>
41+
</div>
42+
<div className="w-fit rounded-full bg-gray-100 dark:bg-gray-800 px-2 py-1 text-xs text-gray-400 border-[0.5px] border-gray-300 dark:border-gray-600">
43+
# {tags}
44+
</div>
45+
</div>
46+
</Link>
47+
)
48+
})}
49+
</div>
50+
)
51+
}

app/_components/blog/CategoryChip.tsx

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use client";
2+
3+
import { BLOG_TAGS } from "@/app/_constants/blog";
4+
import { useURLSearchParams } from "@/app/_hooks/useURLSearchParams";
5+
import { cva } from "class-variance-authority";
6+
7+
const chipVariants = cva(
8+
"w-fit rounded-full border cursor-pointer px-3 py-[6px] text-sm min-w-10 text-center",
9+
{
10+
variants: {
11+
select: {
12+
true: "bg-gray-800 border-gray-800 text-gray-100 dark:bg-gray-100 dark:border-gray-100 dark:text-gray-900",
13+
false: "border-gray-300 dark:border-gray-600 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white transition-all duration-200",
14+
},
15+
},
16+
defaultVariants: {
17+
select: false,
18+
},
19+
},
20+
);
21+
22+
export default function CategoryChip() {
23+
const { searchParams, searchParamsChange, deleteSearchParams } = useURLSearchParams()
24+
const selectTag = searchParams.get("tag");
25+
26+
return (
27+
<div className="flex w-full flex-wrap gap-2 select-none">
28+
<div className={chipVariants({ select: selectTag === null })} onClick={() => deleteSearchParams("tag")}>All</div>
29+
{BLOG_TAGS?.map((tag, i) => <div key={tag + i} className={chipVariants({ select: selectTag === tag })} onClick={() => searchParamsChange({ tag })}>{tag}</div>)}
30+
</div>
31+
)
32+
}

app/_components/blog/index.tsx

+8-39
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,19 @@
1+
import CategoryChip from "./CategoryChip";
2+
import BlogList from "./BlogList";
13
import { getPosts } from "@/app/_lib/getPost";
2-
// import { getTags } from "@/app/_lib/getTags";
3-
import Link from "next/link";
4+
import { Suspense } from "react";
45

5-
export default async function BlogList() {
6+
export default async function BlogHome() {
67
const posts = await getPosts();
7-
// const allTags = await getTags();
8-
// const tags = [...new Set(allTags)].filter((tag) => tag !== undefined);
98

109
return (
1110
<div className="pt-20 min-h-screen bg-white dark:bg-black px-4">
1211
<div className="max-w-2xl mx-auto flex-col gap-6 flex">
1312
<h1 className="text-3xl dark:text-white text-gray-900 font-oceanic">Blog</h1>
14-
15-
{/* Todo: Category Tag */}
16-
{/* <div className="flex w-full flex-wrap gap-2">
17-
<div className="w-fit rounded-full cursor-pointer bg-gray-100 px-2 py-1 text-sm text-gray-900 min-w-10 text-center">All</div>
18-
{tags?.map((tag, i) => <div key={tag + i} className="w-fit rounded-full cursor-pointer bg-gray-100 px-2 py-1 text-sm text-gray-900">{tag}</div>)}
19-
</div> */}
20-
21-
<div className="flex flex-col items-center gap-3">
22-
{posts.map(({ frontMatter, route }) => {
23-
if (!frontMatter) return null;
24-
const { title, date, tags, author } = frontMatter;
25-
return (
26-
<Link
27-
href={`/blog${route}`}
28-
key={route}
29-
className="group flex w-full hover:bg-black/10 dark:hover:bg-white/20 duration-300 transition-all cursor-pointer flex-col gap-2 rounded-xl border-gray-700 p-4"
30-
>
31-
<h1 className="h-16 text-2xl font-medium text-zinc-800 dark:text-zinc-50">
32-
{title}
33-
</h1>
34-
<div className="flex w-full items-center justify-between">
35-
<div className="flex gap-1 text-base text-gray-400 dark:text-gray-300">
36-
<p>{date}</p>
37-
<p></p>
38-
<p>{author}</p>
39-
</div>
40-
<div className="w-fit rounded-full bg-gray-100 dark:bg-gray-800 px-2 py-1 text-xs text-gray-400 border-[0.5px] border-gray-300 dark:border-gray-600">
41-
# {tags}
42-
</div>
43-
</div>
44-
</Link>
45-
)
46-
})}
47-
</div>
13+
<Suspense>
14+
<CategoryChip />
15+
<BlogList posts={posts} />
16+
</Suspense>
4817
</div>
4918
</div >
5019
);

app/_components/common/Banner.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default function Banner() {
88
<div className="w-full bg-[#E6FDFC] md:flex md:justify-between md:px-20">
99
<div className="flex flex-col gap-10 px-4 pt-20 pb-9 text-[#002424] md:pt-60">
1010
<p className="text-base md:h-18">Coming soon</p>
11-
<h3 className="text-[50px] md:text-6xl font-oceanic">Wrtn Agent OS</h3>
11+
<h3 className="text-[42px] md:text-6xl font-oceanic">Wrtn Agent OS</h3>
1212

1313
<Button variant="secondary" className="w-fit" onClick={openToast}>Take a tour
1414
<ArrowRightIcon strokeWidth={1.5} size={20} />

app/_components/home/Client.tsx

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"use client";
2+
3+
import { useRef } from "react";
4+
import { useInView } from "framer-motion";
5+
import { Item } from "nextra/normalize-pages";
6+
7+
import Header from "../layout/Header";
8+
import Welcome from "./Welcome";
9+
import HowItWorks from "./HowItWorks";
10+
import Service from "./Service";
11+
import OpenSource from "./OpenSource";
12+
import Roadmap from "./Roadmap";
13+
import Banner from "../common/Banner";
14+
import LatestArticles from "./LatestArticles";
15+
16+
export default function HomeClient({ posts }: { posts: Item[] }) {
17+
const darkSectionRef = useRef(null);
18+
const isDark = useInView(darkSectionRef, { margin: "0px 0px -99% 0px" });
19+
20+
return (
21+
<>
22+
<Header isDark={isDark} />
23+
<div className="flex flex-col items-center bg-white">
24+
<Welcome />
25+
<HowItWorks />
26+
<Service />
27+
<div ref={darkSectionRef}>
28+
<OpenSource />
29+
<Roadmap />
30+
</div>
31+
<LatestArticles posts={posts} />
32+
<Banner />
33+
</div>
34+
</>
35+
);
36+
}

app/_components/home/HowItWorks.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default function HowItWorks() {
1010
return (
1111
<section className="flex w-full max-w-[1440px] flex-col gap-5 px-4 py-24 text-[#002424] md:px-8 md:py-40">
1212
<p className="text-base">Coming soon</p>
13-
<h2 className="font-oceanic text-[40px] leading-[46px] whitespace-pre-line md:text-[48px] md:leading-[56px] ">
13+
<h2 className="font-oceanic text-[24px] leading-normal whitespace-pre-line md:text-[48px] md:leading-[56px] ">
1414
{"Unchain your potential\nwith Wrtn Agent OS"}
1515
</h2>
1616
<p className="text-base leading-6 md:whitespace-pre-line text-[#767676]">
@@ -21,7 +21,7 @@ export default function HowItWorks() {
2121
<Button variant="secondary" className="w-fit" onClick={openToast}>
2222
Agent OS <ArrowRightIcon strokeWidth={1.5} size={20} />
2323
</Button>
24-
<div className="relative aspect-72/31 min-h-[300px] w-full cursor-pointer overflow-hidden rounded-3xl">
24+
<div className="relative aspect-72/31 min-h-[320px] w-full cursor-pointer overflow-hidden rounded-3xl">
2525
<Image
2626
src={addBasePath("/images/main.png")}
2727
alt="thumbnail"

app/_components/home/LatestArticles.tsx

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import { daysAgo, formatDate } from "@/app/_lib/date";
2-
import { getPosts } from "@/app/_lib/getPost";
32
import { ArrowRightIcon } from "lucide-react";
43
import Image from "next/image";
54
import Link from "next/link";
65
import { addBasePath } from "@/app/_lib/add-base-path";
6+
import { Item } from "nextra/normalize-pages";
77

8-
9-
export default async function LatestArticles() {
10-
const posts = await getPosts();
8+
export default function LatestArticles({ posts }: { posts: Item[] }) {
119
const viewPosts = posts.filter(({ name }) => ["the-journey-of-building-a-cs-refund-agent", "meet-our-new-member"].includes(name));
1210

1311
return (

app/_components/home/OpenSource.tsx

+2-5
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { addBasePath } from "@/app/_lib/add-base-path";
77

88
export default function OpenSource() {
99
return (
10-
<div className="flex w-full flex-col bg-black items-center gap-20 pb-[100px] pt-[200px] px-4 md:pb-0 md:px-8">
10+
<div className="flex w-full flex-col bg-[linear-gradient(0deg,_#000_90%,_rgba(0,0,0,0)_100%)] items-center gap-20 pb-[100px] pt-[200px] px-4 md:pb-0 md:px-8">
1111
<div className="flex flex-col md:items-center md:text-center gap-6">
12-
<h2 className="font-oceanic text-[40px] leading-[46px] text-[#E6FDFC] md:whitespace-pre-line">
12+
<h2 className="font-oceanic text-[24px] leading-normal md:text-[40px] md:leading-[46px] text-[#E6FDFC] md:whitespace-pre-line">
1313
{"Based on powerful\nOpen Source Ecosystem"}
1414
</h2>
1515
<p className="text-base text-[#BEBEBE] md:text-lg md:whitespace-pre-line">
@@ -29,9 +29,6 @@ export default function OpenSource() {
2929
autoPlay
3030
loop
3131
muted
32-
controls
33-
controlsList="nodownload"
34-
disablePictureInPicture
3532
playsInline
3633
className="h-full w-full object-cover"
3734
preload="auto"

app/_components/home/Roadmap.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { ROADMAP } from "@/app/_constants/roadmap";
55

66
export default function Roadmap() {
77
return (
8-
<section className="flex w-full items-center justify-center bg-[#071511] px-4 py-40 md:h-[960px] md:max-h-screen">
8+
<section className="flex w-full items-center justify-center bg-linear-to-b from-[#000000] to-[#002424] px-4 md:px-8 py-40 md:h-[960px] md:max-h-screen">
99
<div className="flex w-full max-w-[1440px] flex-col items-center justify-between gap-6 md:flex-row">
1010
<div className="flex flex-col gap-6">
1111
<p className="text-base text-[#E6FDFC]">Roadmap</p>
12-
<h2 className="font-oceanic text-[40px] leading-[46px] whitespace-pre-line text-[#E6FDFC]">
12+
<h2 className="font-oceanic text-[24px] leading-normal md:text-[40px] md:leading-[46px] whitespace-pre-line text-[#E6FDFC]">
1313
{"Built from the ground up\nfor every layer of AI Agents"}
1414
</h2>
1515
<p className="text-lg md:whitespace-pre-line text-[#A7B4B3]">
@@ -28,20 +28,20 @@ export default function Roadmap() {
2828
</Link>
2929
</div>
3030

31-
<div className="flex w-full max-w-[646px] flex-col gap-4 drop-shadow-[0_0_100px_rgba(134,255,217,0.40)] md:w-auto">
31+
<div className="flex w-full md:max-w-[646px] flex-col gap-4 drop-shadow-[0_0_100px_rgba(134,255,217,0.40)] md:w-auto">
3232
{ROADMAP.map(({ title, keywords, className, itemClassName }) => (
3333
<div
3434
key={title}
35-
className={`flex w-full flex-row gap-2 rounded-2xl p-3.5 md:w-[646px] md:rounded-[20px] overflow-scroll ${className}`}
35+
className={`flex w-full md:flex-row flex-col gap-2 rounded-2xl p-3.5 md:w-[646px] md:rounded-[20px] overflow-scroll ${className}`}
3636
>
37-
<h3 className="flex items-center min-w-32 flex-1 justify-center text-center text-lg md:text-[22px] whitespace-pre-line">
37+
<h3 className="flex items-center min-w-32 font-semibold flex-1 justify-center text-center text-lg md:text-[22px] md:whitespace-pre-line">
3838
{title}
3939
</h3>
4040
<div className="flex flex-row gap-2">
4141
{keywords.map((keyword) => (
4242
<div
4343
key={keyword}
44-
className="relative flex-1 rounded-md bg-linear-to-b from-[#18EDC9CC] to-[#18EDC933] p-px h-28 w-28 md:h-[132px] md:w-[132px] md:rounded-[10px]"
44+
className="relative flex-1 rounded-md bg-linear-to-b from-[#18EDC9CC] to-[#18EDC933] p-px min-h-28 min-w-28 md:h-[132px] md:w-[132px] md:rounded-[10px]"
4545
>
4646
<div
4747
className={`font-md flex h-full w-full bg-linear-to-b items-center justify-center gap-2 text-sm rounded-md text-center md:text-lg md:rounded-[10px] ${itemClassName}`}

0 commit comments

Comments
 (0)