Spaces:
Sleeping
Sleeping
Commit
·
c6f90aa
1
Parent(s):
6f19b05
feat: add the layout of `for students`
Browse files- app/components/TopNav.tsx +39 -0
- app/for-students/components/ChatBotCard.tsx +36 -0
- app/for-students/components/ChatBotGrid.tsx +60 -0
- app/for-students/components/ChatBotGridSkeleton.tsx +25 -0
- app/for-students/components/SearchBar.tsx +28 -0
- app/for-students/components/SubjectFilter.tsx +19 -0
- app/for-students/page.tsx +36 -0
- app/layout.tsx +5 -1
- app/page.tsx +0 -33
app/components/TopNav.tsx
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client'
|
2 |
+
|
3 |
+
import Link from 'next/link'
|
4 |
+
import { useTheme } from './ThemeProvider'
|
5 |
+
|
6 |
+
export function TopNav() {
|
7 |
+
const { theme, toggleTheme } = useTheme()
|
8 |
+
|
9 |
+
return (
|
10 |
+
<header className="fixed w-full bg-background-secondary/80 backdrop-blur-sm border-b border-border z-50">
|
11 |
+
<div className="container mx-auto px-4 py-4 flex items-center">
|
12 |
+
<Link href="/" className="text-3xl font-bold">
|
13 |
+
<span className="text-[#FF6B6B]">P</span>
|
14 |
+
<span className="text-[#4ECDC4]">l</span>
|
15 |
+
<span className="text-[#45B7D1]">a</span>
|
16 |
+
<span className="text-[#FDCB6E]">y</span>
|
17 |
+
<span className="text-[#FF6B6B]">G</span>
|
18 |
+
<span className="text-[#4ECDC4]">o</span>
|
19 |
+
<span className="ml-2 text-[#45B7D1]">A</span>
|
20 |
+
<span className="text-[#FDCB6E]">I</span>
|
21 |
+
</Link>
|
22 |
+
<nav className="flex items-center justify-center flex-1 space-x-8">
|
23 |
+
<Link href="/for-teachers" className="text-text-secondary hover:text-primary">
|
24 |
+
我是老師
|
25 |
+
</Link>
|
26 |
+
<Link href="/for-students" className="text-text-secondary hover:text-primary">
|
27 |
+
我是學生
|
28 |
+
</Link>
|
29 |
+
</nav>
|
30 |
+
<button
|
31 |
+
onClick={toggleTheme}
|
32 |
+
className="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90"
|
33 |
+
>
|
34 |
+
{theme === 'light' ? '🌙' : '☀️'}
|
35 |
+
</button>
|
36 |
+
</div>
|
37 |
+
</header>
|
38 |
+
)
|
39 |
+
}
|
app/for-students/components/ChatBotCard.tsx
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
interface ChatBotCardProps {
|
2 |
+
chatbot: {
|
3 |
+
id: string
|
4 |
+
title: string
|
5 |
+
description: string
|
6 |
+
subject: string
|
7 |
+
icon: string
|
8 |
+
author?: string
|
9 |
+
}
|
10 |
+
}
|
11 |
+
|
12 |
+
export default function ChatBotCard({ chatbot }: ChatBotCardProps) {
|
13 |
+
return (
|
14 |
+
<div className="group relative overflow-hidden rounded-2xl border border-border/50 bg-background-primary/50 p-6 shadow-sm transition-all hover:shadow-md backdrop-blur-sm">
|
15 |
+
<div className="mb-4 flex items-center justify-between">
|
16 |
+
<div className="flex items-center gap-3">
|
17 |
+
<span className="text-3xl">{chatbot.icon}</span>
|
18 |
+
<h3 className="text-xl font-semibold text-text-primary">{chatbot.title}</h3>
|
19 |
+
</div>
|
20 |
+
<span className="rounded-full bg-background-secondary/50 px-3 py-1 text-sm text-text-secondary">
|
21 |
+
{chatbot.subject}
|
22 |
+
</span>
|
23 |
+
</div>
|
24 |
+
|
25 |
+
<p className="mb-4 text-text-secondary">{chatbot.description}</p>
|
26 |
+
|
27 |
+
{chatbot.author && (
|
28 |
+
<p className="text-sm text-text-secondary">作者:{chatbot.author}</p>
|
29 |
+
)}
|
30 |
+
|
31 |
+
<button className="mt-4 w-full rounded-xl bg-primary px-4 py-2 text-white transition-colors hover:bg-primary/90">
|
32 |
+
開始對話
|
33 |
+
</button>
|
34 |
+
</div>
|
35 |
+
)
|
36 |
+
}
|
app/for-students/components/ChatBotGrid.tsx
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client'
|
2 |
+
|
3 |
+
import { useState } from 'react'
|
4 |
+
import ChatBotCard from './ChatBotCard'
|
5 |
+
|
6 |
+
interface ChatBot {
|
7 |
+
id: string
|
8 |
+
title: string
|
9 |
+
description: string
|
10 |
+
subject: string
|
11 |
+
icon: string
|
12 |
+
author?: string
|
13 |
+
}
|
14 |
+
|
15 |
+
const SAMPLE_CHATBOTS: ChatBot[] = [
|
16 |
+
{
|
17 |
+
id: '1',
|
18 |
+
title: 'Code Tutor',
|
19 |
+
description: "Let's code together! I'm Khanmigo Lite, by Khan Academy.",
|
20 |
+
subject: '程式設計',
|
21 |
+
icon: '👨💻',
|
22 |
+
author: 'khanacademy.org'
|
23 |
+
},
|
24 |
+
{
|
25 |
+
id: '2',
|
26 |
+
title: 'Whimsical Diagrams',
|
27 |
+
description: 'Explains and visualizes concepts with flowcharts, mindmaps and sequence diagrams.',
|
28 |
+
subject: '視覺化',
|
29 |
+
icon: '📊',
|
30 |
+
author: 'whimsical.com'
|
31 |
+
},
|
32 |
+
{
|
33 |
+
id: '3',
|
34 |
+
title: 'Resume',
|
35 |
+
description: 'By combining the expertise of top resume writers with advanced AI, we assist in diagnosing and improving resumes.',
|
36 |
+
subject: '求職',
|
37 |
+
icon: '📝',
|
38 |
+
author: 'jobright.ai'
|
39 |
+
},
|
40 |
+
{
|
41 |
+
id: '4',
|
42 |
+
title: 'Universal Primer',
|
43 |
+
description: 'The fastest way to learn anything hard.',
|
44 |
+
subject: '學習',
|
45 |
+
icon: '📚',
|
46 |
+
author: 'Siqi Chen'
|
47 |
+
}
|
48 |
+
]
|
49 |
+
|
50 |
+
export default function ChatBotGrid() {
|
51 |
+
const [chatbots] = useState<ChatBot[]>(SAMPLE_CHATBOTS)
|
52 |
+
|
53 |
+
return (
|
54 |
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-2">
|
55 |
+
{chatbots.map((chatbot) => (
|
56 |
+
<ChatBotCard key={chatbot.id} chatbot={chatbot} />
|
57 |
+
))}
|
58 |
+
</div>
|
59 |
+
)
|
60 |
+
}
|
app/for-students/components/ChatBotGridSkeleton.tsx
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export default function ChatBotGridSkeleton() {
|
2 |
+
return (
|
3 |
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
4 |
+
{[...Array(6)].map((_, index) => (
|
5 |
+
<div
|
6 |
+
key={index}
|
7 |
+
className="animate-pulse rounded-lg border border-gray-200 bg-white p-6 shadow-sm"
|
8 |
+
>
|
9 |
+
<div className="mb-4 flex items-center justify-between">
|
10 |
+
<div className="h-8 w-8 rounded-full bg-gray-200" />
|
11 |
+
<div className="h-6 w-24 rounded-full bg-gray-200" />
|
12 |
+
</div>
|
13 |
+
|
14 |
+
<div className="mb-2 h-6 w-3/4 rounded bg-gray-200" />
|
15 |
+
<div className="mb-4 space-y-2">
|
16 |
+
<div className="h-4 w-full rounded bg-gray-200" />
|
17 |
+
<div className="h-4 w-5/6 rounded bg-gray-200" />
|
18 |
+
</div>
|
19 |
+
|
20 |
+
<div className="h-10 w-full rounded-lg bg-gray-200" />
|
21 |
+
</div>
|
22 |
+
))}
|
23 |
+
</div>
|
24 |
+
)
|
25 |
+
}
|
app/for-students/components/SearchBar.tsx
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client'
|
2 |
+
|
3 |
+
export default function SearchBar() {
|
4 |
+
return (
|
5 |
+
<div className="relative max-w-full mx-auto">
|
6 |
+
<input
|
7 |
+
type="text"
|
8 |
+
placeholder="搜尋 GPT..."
|
9 |
+
className="w-full rounded-2xl border border-border/50 bg-background-primary/50 px-4 py-6 pl-12 pr-36
|
10 |
+
backdrop-blur-sm shadow-lg focus:outline-none focus:ring-2 focus:ring-primary/50
|
11 |
+
text-lg transition-all duration-200 placeholder:text-text-secondary/50"
|
12 |
+
/>
|
13 |
+
<svg
|
14 |
+
className="absolute left-4 top-1/2 h-6 w-6 -translate-y-1/2 text-text-secondary/50"
|
15 |
+
fill="none"
|
16 |
+
stroke="currentColor"
|
17 |
+
viewBox="0 0 24 24"
|
18 |
+
>
|
19 |
+
<path
|
20 |
+
strokeLinecap="round"
|
21 |
+
strokeLinejoin="round"
|
22 |
+
strokeWidth={2}
|
23 |
+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
24 |
+
/>
|
25 |
+
</svg>
|
26 |
+
</div>
|
27 |
+
)
|
28 |
+
}
|
app/for-students/components/SubjectFilter.tsx
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client'
|
2 |
+
|
3 |
+
export default function SubjectFilter() {
|
4 |
+
const subjects = ['熱門精選', '寫作', '生產力', '研究與分析', '教育', '日常生活', '程式設計']
|
5 |
+
|
6 |
+
return (
|
7 |
+
<div className="flex flex-wrap gap-4">
|
8 |
+
{subjects.map((subject) => (
|
9 |
+
<button
|
10 |
+
key={subject}
|
11 |
+
className="rounded-full border border-border/50 px-6 py-2 text-base transition-colors
|
12 |
+
hover:bg-background-secondary/50 focus:outline-none focus:ring-2 focus:ring-primary/50"
|
13 |
+
>
|
14 |
+
{subject}
|
15 |
+
</button>
|
16 |
+
))}
|
17 |
+
</div>
|
18 |
+
)
|
19 |
+
}
|
app/for-students/page.tsx
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Suspense } from 'react'
|
2 |
+
import ChatBotGrid from './components/ChatBotGrid'
|
3 |
+
import SubjectFilter from './components/SubjectFilter'
|
4 |
+
import SearchBar from './components/SearchBar'
|
5 |
+
import ChatBotGridSkeleton from './components/ChatBotGridSkeleton'
|
6 |
+
|
7 |
+
export default function ForStudentsPage() {
|
8 |
+
return (
|
9 |
+
<main className="min-h-screen bg-background-primary">
|
10 |
+
{/* Hero Section */}
|
11 |
+
<section className="mb-12 pt-16 text-center">
|
12 |
+
<h1 className="mb-4 text-5xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
13 |
+
GPT
|
14 |
+
</h1>
|
15 |
+
<p className="mx-auto max-w-2xl text-lg text-text-secondary">
|
16 |
+
探索並建立結合指令、額外知識庫和任何技能組合的 ChatGPT 自訂版本。
|
17 |
+
</p>
|
18 |
+
</section>
|
19 |
+
|
20 |
+
{/* Search and Filter Section */}
|
21 |
+
<div className="container mx-auto px-4">
|
22 |
+
<div className="mb-8">
|
23 |
+
<SearchBar />
|
24 |
+
</div>
|
25 |
+
<div className="mb-12">
|
26 |
+
<SubjectFilter />
|
27 |
+
</div>
|
28 |
+
|
29 |
+
{/* Chatbots Grid */}
|
30 |
+
<Suspense fallback={<ChatBotGridSkeleton />}>
|
31 |
+
<ChatBotGrid />
|
32 |
+
</Suspense>
|
33 |
+
</div>
|
34 |
+
</main>
|
35 |
+
)
|
36 |
+
}
|
app/layout.tsx
CHANGED
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|
2 |
import localFont from "next/font/local";
|
3 |
import "./globals.css";
|
4 |
import { ThemeProvider } from './components/ThemeProvider'
|
|
|
5 |
|
6 |
const geistSans = localFont({
|
7 |
src: "./fonts/GeistVF.woff",
|
@@ -23,7 +24,10 @@ export default function RootLayout({
|
|
23 |
<html lang="en" className={`${geistSans.variable}`}>
|
24 |
<body className="antialiased">
|
25 |
<ThemeProvider>
|
26 |
-
|
|
|
|
|
|
|
27 |
</ThemeProvider>
|
28 |
</body>
|
29 |
</html>
|
|
|
2 |
import localFont from "next/font/local";
|
3 |
import "./globals.css";
|
4 |
import { ThemeProvider } from './components/ThemeProvider'
|
5 |
+
import { TopNav } from './components/TopNav'
|
6 |
|
7 |
const geistSans = localFont({
|
8 |
src: "./fonts/GeistVF.woff",
|
|
|
24 |
<html lang="en" className={`${geistSans.variable}`}>
|
25 |
<body className="antialiased">
|
26 |
<ThemeProvider>
|
27 |
+
<TopNav />
|
28 |
+
<main className="pt-14">
|
29 |
+
{children}
|
30 |
+
</main>
|
31 |
</ThemeProvider>
|
32 |
</body>
|
33 |
</html>
|
app/page.tsx
CHANGED
@@ -1,44 +1,11 @@
|
|
1 |
'use client'
|
2 |
|
3 |
import Link from "next/link"
|
4 |
-
import { useTheme } from './components/ThemeProvider'
|
5 |
import ChatBot from './components/LandingPageChatBot'
|
6 |
|
7 |
export default function LandingPage() {
|
8 |
-
const { theme, toggleTheme } = useTheme()
|
9 |
-
|
10 |
return (
|
11 |
<div className="min-h-screen bg-background-primary">
|
12 |
-
{/* Modern Header */}
|
13 |
-
<header className="fixed w-full bg-background-secondary/80 backdrop-blur-sm border-b border-border z-50">
|
14 |
-
<div className="container mx-auto px-4 py-4 flex items-center">
|
15 |
-
<Link href="/" className="text-3xl font-bold">
|
16 |
-
<span className="text-[#FF6B6B]">P</span>
|
17 |
-
<span className="text-[#4ECDC4]">l</span>
|
18 |
-
<span className="text-[#45B7D1]">a</span>
|
19 |
-
<span className="text-[#FDCB6E]">y</span>
|
20 |
-
<span className="text-[#FF6B6B]">G</span>
|
21 |
-
<span className="text-[#4ECDC4]">o</span>
|
22 |
-
<span className="ml-2 text-[#45B7D1]">A</span>
|
23 |
-
<span className="text-[#FDCB6E]">I</span>
|
24 |
-
</Link>
|
25 |
-
<nav className="flex items-center justify-center flex-1 space-x-8">
|
26 |
-
<Link href="/for-teachers" className="text-text-secondary hover:text-primary">
|
27 |
-
我是老師
|
28 |
-
</Link>
|
29 |
-
<Link href="/for-students" className="text-text-secondary hover:text-primary">
|
30 |
-
我是學生
|
31 |
-
</Link>
|
32 |
-
</nav>
|
33 |
-
<button
|
34 |
-
onClick={toggleTheme}
|
35 |
-
className="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90"
|
36 |
-
>
|
37 |
-
{theme === 'light' ? '🌙' : '☀️'}
|
38 |
-
</button>
|
39 |
-
</div>
|
40 |
-
</header>
|
41 |
-
|
42 |
<main className="pt-4">
|
43 |
{/* Hero Section */}
|
44 |
<div className="">
|
|
|
1 |
'use client'
|
2 |
|
3 |
import Link from "next/link"
|
|
|
4 |
import ChatBot from './components/LandingPageChatBot'
|
5 |
|
6 |
export default function LandingPage() {
|
|
|
|
|
7 |
return (
|
8 |
<div className="min-h-screen bg-background-primary">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
<main className="pt-4">
|
10 |
{/* Hero Section */}
|
11 |
<div className="">
|