Skip to content

Commit 9646906

Browse files
authored
feat: add roadmap page (#70)
* feat: add roadmap page * chore: link * chore: delete unnecessary files * build: build error * style: modify styles * chore: open version
1 parent bfb6a11 commit 9646906

17 files changed

Lines changed: 854 additions & 24 deletions

File tree

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@
2121
"Yuque"
2222
]
2323
}
24+

app/[lang]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default async function RootLayout({
2828
params,
2929
}: {
3030
children: React.ReactNode;
31-
params: { lang: string };
31+
params: Promise<{ lang: string }>;
3232
}) {
3333
const { lang } = await params;
3434
const currentLang = lang || defaultLocale;

app/[lang]/roadmap/page.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import RoadmapPage from "@/components/roadmap/RoadmapPage";
2+
import { getDictionary } from "@/i18n/i18n";
3+
import { Metadata } from "next";
4+
5+
export async function generateMetadata({
6+
params,
7+
}: {
8+
params: Promise<{ lang: string }>;
9+
}): Promise<Metadata> {
10+
const { lang } = await params;
11+
const dict = await getDictionary(lang);
12+
13+
return {
14+
title: dict.roadmap?.title || "Roadmap Overview",
15+
description:
16+
dict.roadmap?.subtitle ||
17+
"Features currently in development and planned for the future.",
18+
};
19+
}
20+
21+
export default async function Roadmap({
22+
params,
23+
}: {
24+
params: Promise<{ lang: string }>;
25+
}) {
26+
const { lang } = await params;
27+
const dict = await getDictionary(lang);
28+
29+
return <RoadmapPage dict={dict} lang={lang} />;
30+
}
31+

components/header/NavTab.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export default function NavTab({
4545

4646
const getContainerStyles = () => {
4747
const baseStyles =
48-
"flex items-center justify-center bg-[#EBF6FF] dark:bg-[#04071B] text-white rounded-[39px]";
48+
"flex items-center justify-center bg-[#EBF6FF] dark:bg-[#04071B] text-white rounded-[39px] min-w-fit";
4949

5050
const variants = {
5151
default: "h-[52px] px-0.5",
@@ -58,7 +58,7 @@ export default function NavTab({
5858

5959
const getTabItemStyles = (isActive: boolean) => {
6060
const baseStyles =
61-
"relative z-10 h-full flex items-center rounded-full font-semibold transition-colors";
61+
"relative z-10 h-full flex items-center justify-center rounded-full font-semibold transition-colors whitespace-nowrap";
6262

6363
const variants = {
6464
default: {
@@ -99,9 +99,11 @@ export default function NavTab({
9999

100100
const getTextStyles = (isActive: boolean) => {
101101
const variants = {
102-
default: "inline-block",
103-
compact: isActive ? "inline-block" : "hidden md:inline-block",
104-
large: "inline-block",
102+
default: "inline-block whitespace-nowrap",
103+
compact: isActive
104+
? "inline-block whitespace-nowrap"
105+
: "hidden md:inline-block whitespace-nowrap",
106+
large: "inline-block whitespace-nowrap",
105107
};
106108

107109
return variants[variant];

components/integration/ExtensionDetail.tsx

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ export default function ExtensionDetail({
174174
width={144}
175175
height={144}
176176
className="w-full h-full object-contain"
177+
style={{
178+
filter: "drop-shadow(rgb(255, 255, 255) 0px 0px 6px)",
179+
}}
177180
/>
178181
</div>
179182

@@ -264,16 +267,30 @@ export default function ExtensionDetail({
264267
</span>
265268
</div>
266269
<div className="flex items-center space-x-1 md:space-x-2">
267-
<GitFork className="w-5 h-5 md:w-6 md:h-6" />
268-
<span className="text-sm md:text-base">
269-
{extension.stats.views}
270-
</span>
270+
<a
271+
href={extension.url?.code}
272+
target="_blank"
273+
rel="noopener noreferrer"
274+
className="flex items-center space-x-1 md:space-x-2"
275+
>
276+
<GitFork className="w-5 h-5 md:w-6 md:h-6" />
277+
<span className="text-sm md:text-base">
278+
{extension.stats.views}
279+
</span>
280+
</a>
271281
</div>
272282
<div className="flex items-center space-x-1 md:space-x-2">
273-
<Github className="w-5 h-5 md:w-6 md:h-6" />
274-
<span className="text-sm md:text-base">
275-
{locale?.repo || "Repo"}
276-
</span>
283+
<a
284+
href={extension.url?.code}
285+
target="_blank"
286+
rel="noopener noreferrer"
287+
className="flex items-center space-x-1 md:space-x-2"
288+
>
289+
<Github className="w-5 h-5 md:w-6 md:h-6" />
290+
<span className="text-sm md:text-base">
291+
{locale?.repo || "Repo"}
292+
</span>
293+
</a>
277294
</div>
278295
</div>
279296
</div>

components/integration/ExtensionDetailContent.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ export default function ExtensionDetailContent({
4747
const fetchRelatedExtensions = useCallback(async () => {
4848
try {
4949
setLoading(true);
50-
const apiUrl = `/api/extensions/_search?from=0&size=10`;
50+
const apiUrl =
51+
process.env.NODE_ENV === "development"
52+
? `/api/extensions/_search?from=0&size=10&sort=created:desc`
53+
: `https://coco.infini.cloud/store/extension/_search??from=0&size=10&sort=created:desc`;
5154
const response = await fetch(apiUrl);
5255
const data: ApiResponse = await response.json();
5356

@@ -154,7 +157,7 @@ export default function ExtensionDetailContent({
154157

155158
<div className="mt-4 sm:mt-6 pt-4 sm:pt-6 border-t border-blue-400/20 dark:border-gray-700">
156159
<div className="text-sm sm:text-base font-normal text-[#9696B4] mb-2">
157-
{locale?.commands || "Commands"}
160+
{locale?.commands}
158161
</div>
159162
<div className="space-y-3 sm:space-y-4">
160163
{extension.platforms && (
@@ -228,11 +231,12 @@ export default function ExtensionDetailContent({
228231
{locale?.lastUpdate || "Last update"}
229232
</div>
230233
<p className="text-black dark:text-white text-sm sm:text-base">
231-
{new Date(extension.updated).toLocaleTimeString("en-US", {
232-
hour: "2-digit",
233-
minute: "2-digit",
234-
hour12: true,
235-
})}
234+
{extension.updated
235+
? new Date(extension.updated)
236+
.toISOString()
237+
.replace("T", " ")
238+
.substring(0, 19)
239+
: ""}
236240
</p>
237241
</div>
238242
</div>
@@ -241,7 +245,7 @@ export default function ExtensionDetailContent({
241245
<div className="mt-4 sm:mt-5 p-[2px] rounded-[16px] bg-gradient-to-br from-[#5E85FF33] to-[#49FFF333]">
242246
<div className="p-3 sm:p-6 bg-[#EBF6FF] dark:bg-[#0B1020] rounded-xl">
243247
<div className="text-lg sm:text-xl font-medium text-gray-900 dark:text-white mb-4 sm:mb-6">
244-
{locale?.youMayAlsoLike || "You may also like"}
248+
{locale?.youMayAlsoLike}
245249
</div>
246250
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
247251
{relatedExtensions.map((relatedExt, index) => (
@@ -259,6 +263,9 @@ export default function ExtensionDetailContent({
259263
width={40}
260264
height={40}
261265
className="w-10 h-10 rounded"
266+
style={{
267+
filter: "drop-shadow(rgb(255, 255, 255) 0px 0px 6px)",
268+
}}
262269
/>
263270
) : (
264271
<span className="text-white text-xs sm:text-sm font-bold">

components/integration/ExtensionDeveloperInfo.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ export default function ExtensionDeveloperInfo({
176176
width={40}
177177
height={40}
178178
className="rounded"
179+
style={{
180+
filter: "drop-shadow(rgb(255, 255, 255) 0px 0px 6px)",
181+
}}
179182
/>
180183
<div className="flex-1 min-w-0">
181184
<h4 className="text-black dark:text-white font-medium text-xs sm:text-sm group-hover:text-blue-400 transition-colors truncate">

components/integration/ExtensionList.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ export default function ExtensionList({
101101
width={56}
102102
height={56}
103103
className="object-cover rounded-xl"
104+
style={{
105+
filter: "drop-shadow(rgb(255, 255, 255) 0px 0px 6px)",
106+
}}
104107
/>
105108
<Link
106109
href={`coco://install_extension_from_store?id=${extension.id}`}

components/roadmap/RoadmapPage.tsx

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"use client";
2+
3+
import * as Collapsible from "@radix-ui/react-collapsible";
4+
import { motion } from "framer-motion";
5+
import { ChevronRight } from "lucide-react";
6+
import Image from "next/image";
7+
import Link from "next/link";
8+
import { useCallback, useEffect, useState } from "react";
9+
10+
import { getDictionary } from "@/i18n/i18n";
11+
12+
interface RoadmapPageProps {
13+
dict: any;
14+
lang: string;
15+
}
16+
17+
export default function RoadmapPage({ dict, lang }: RoadmapPageProps) {
18+
const [openStates, setOpenStates] = useState<Record<string, boolean>>({});
19+
20+
const [locale, setLocale] = useState<any>();
21+
const getLocale = useCallback(async () => {
22+
const dict = await getDictionary(lang);
23+
setLocale(dict.Roadmap);
24+
}, [lang]);
25+
26+
useEffect(() => {
27+
getLocale();
28+
}, [getLocale]);
29+
30+
if (!locale) {
31+
return (
32+
<div className="min-h-screen flex items-center justify-center">
33+
Loading...
34+
</div>
35+
);
36+
}
37+
38+
return (
39+
<div className="w-full max-w-6xl mx-auto px-4 pt-24 sm:px-6 lg:px-8">
40+
{/* Header */}
41+
<motion.div
42+
initial={{ opacity: 0, y: 20 }}
43+
animate={{ opacity: 1, y: 0 }}
44+
transition={{ duration: 0.6 }}
45+
className="text-center"
46+
>
47+
<div className="mb-4 font-medium text-3xl md:text-5xl bg-gradient-to-r from-[#843DFF] to-[#00CEFF] bg-clip-text text-transparent">
48+
{locale.title}
49+
</div>
50+
<div className="mb-14 font-normal text-base text-black dark:text-white">
51+
{locale.subtitle}
52+
</div>
53+
54+
<Link
55+
href={`https://github.com/infinilabs/coco-app/issues`}
56+
aria-label="download"
57+
className="mb-14 inline-block"
58+
target="_blank"
59+
>
60+
<div
61+
className={`h-12 leading-[48px] px-8 rounded-full font-semibold text-base text-[#04071b] inline-block hover:scale-105 transform transition-transform duration-200`}
62+
style={{
63+
background: "linear-gradient(90deg, #F5D9FF 0%, #00FFF6 100%)",
64+
boxShadow: "0 2px 12px 0 #19F3FF55",
65+
}}
66+
>
67+
{locale.cta}
68+
</div>
69+
</Link>
70+
</motion.div>
71+
72+
<div className="w-full max-w-7xl mt-32">
73+
<div className="progress mb-40">
74+
<div className="">
75+
<div className="flex items-center gap-5 text-3xl font-medium mb-4">
76+
<Image
77+
src={locale.progressad.icon}
78+
alt="progress"
79+
width={40}
80+
height={40}
81+
/>
82+
{locale.progressad.title}
83+
</div>
84+
<div className="text-base dark:text-[#999]">
85+
{locale.progressad.subTitle}
86+
</div>
87+
</div>
88+
89+
{locale.progressad.content.map((item: any, index: number) => (
90+
<div className="mt-12" key={item.title + index}>
91+
<div className="flex items-center gap-5 text-xl font-medium mb-4">
92+
<div className="w-3 h-3 bg-[#0053FF] rounded-full"></div>
93+
{item.title}
94+
</div>
95+
<div className="text-base dark:text-[#999]">{item.subTitle}</div>
96+
</div>
97+
))}
98+
</div>
99+
100+
<div className="next mb-40">
101+
<div className="">
102+
<div className="flex items-center gap-5 text-3xl font-medium mb-4">
103+
<Image
104+
src={locale.next.icon}
105+
alt="progress"
106+
width={40}
107+
height={40}
108+
/>
109+
{locale.next.title}
110+
</div>
111+
<div className="text-base dark:text-[#999]">
112+
{locale.next.subTitle}
113+
</div>
114+
</div>
115+
116+
{locale.next.content.map((item: any, index: number) => (
117+
<div className="mt-12" key={item.title + index}>
118+
<div className="flex items-center gap-5 text-xl font-medium mb-4">
119+
<div className="w-3 h-3 bg-[#D700FF] rounded-full"></div>
120+
{item.title}
121+
</div>
122+
<div className="text-base dark:text-[#999]">{item.subTitle}</div>
123+
</div>
124+
))}
125+
</div>
126+
127+
<div className="completed mb-40">
128+
<div className="">
129+
<div className="flex items-center gap-5 text-3xl font-medium mb-4">
130+
<Image
131+
src={locale.completed.icon}
132+
alt="progress"
133+
width={40}
134+
height={40}
135+
/>
136+
{locale.completed.title}
137+
</div>
138+
<div className="text-base dark:text-[#999]">
139+
{locale.completed.subTitle}
140+
</div>
141+
</div>
142+
143+
{locale.completed.versionContent.map(
144+
(itemVersion: any, indexVersion: number) => {
145+
const versionKey = `${itemVersion.version}-${indexVersion}`;
146+
const isOpen =
147+
openStates[versionKey] === undefined
148+
? true
149+
: openStates[versionKey];
150+
151+
return (
152+
<Collapsible.Root
153+
key={itemVersion.version + indexVersion}
154+
className=""
155+
open={isOpen}
156+
onOpenChange={(open) =>
157+
setOpenStates((prev) => ({ ...prev, [versionKey]: open }))
158+
}
159+
>
160+
<Collapsible.Trigger asChild>
161+
<div className="flex items-center gap-2 mt-10 text-xl font-medium cursor-pointer hover:text-blue-600 transition-colors">
162+
<ChevronRight
163+
className={`transition-transform duration-200 ${
164+
isOpen ? "rotate-90" : ""
165+
}`}
166+
/>
167+
{itemVersion.version}
168+
</div>
169+
</Collapsible.Trigger>
170+
171+
<Collapsible.Content>
172+
{itemVersion.content.map((item: any, index: number) => (
173+
<div className="mt-12 pl-8" key={item.title + index}>
174+
<div className="flex items-center gap-5 text-xl font-medium mb-4">
175+
<div className="w-3 h-3 bg-[#22D454] rounded-full"></div>
176+
{item.title}
177+
</div>
178+
<div className="text-base dark:text-[#999]">
179+
{item.subTitle}
180+
</div>
181+
</div>
182+
))}
183+
</Collapsible.Content>
184+
</Collapsible.Root>
185+
);
186+
}
187+
)}
188+
</div>
189+
</div>
190+
</div>
191+
);
192+
}

0 commit comments

Comments
 (0)