| name | frontend-skill |
| description | Frontend development for real_deal platform using Next.js 14.2, React 18.2, TypeScript 5.3.3, and Tailwind CSS 3.4.3. Use when working on web UI development, creating React components, building pages, integrating with backend APIs, or styling with custom CSS variables. |
Frontend Development (Next.js)
Tech Stack
- Framework: Next.js 14.2.4 (App Router)
- UI Library: React 18.2.0
- Language: TypeScript 5.3.3
- Styling: Tailwind CSS 3.4.3 + CSS Variables
- API Client: Custom fetch wrapper
Project Structure
web/
app/
page.tsx # Home page (explore feed)
globals.css # Global styles and theme tokens
layout.tsx # Root layout
bookmarks/ # Bookmarks page
chat/ # Chat interface
companies/ # Company pages
investors/ # Investor pages
jobs/ # Job listings
login/ # Login page
media/ # Media pages
notifications/ # Notifications
post/ # Post creation
profile/ # User profiles
projects/ # Project pages
products/ # Product pages
lib/
api.ts # API client utility
components/
Card.tsx # Simple card component
Composer.tsx # Post composer
FacetFilter.tsx # Search and filter
FeedLayout.tsx # Feed layout wrapper
FeedRow.tsx # Feed row item
Navbar.tsx # Navigation
SidebarNav.tsx # Sidebar navigation
CenterHeader.tsx # Header section
RightSidebar.tsx # Right sidebar
# ... more components
package.json
tsconfig.json
tailwind.config.js
next.config.js
postcss.config.js
Key Patterns
API Client Pattern
export const API_BASE = process.env.NEXT_PUBLIC_API_BASE || 'http://localhost:8080'
export async function api<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${API_BASE}${path}`, {
cache: 'no-store',
credentials: 'include',
...init
})
if (!res.ok) throw new Error(`api ${path} ${res.status}`)
return res.json()
}
Server Component Pattern (App Router)
import { api } from './lib/api'
import FeedLayout from '../components/FeedLayout'
import Composer from '../components/Composer'
import FeedRow from '../components/FeedRow'
export default async function Page({ searchParams }: { searchParams?: { q?: string, type?: string, density?: string } }) {
const data = await api<{projects:any[];products:any[];posts:any[];jobs:any[];companies:any[]}>('/api/explore')
const feed = [
...data.projects.map(p=>({ id:p.id, title:p.title, subtitle:p.summary, tags:p.tags })),
...data.products.map(p=>({ id:p.id, title:p.name, subtitle:p.summary, tags:p.tags })),
...data.jobs.map(j=>({ id:j.id, title:j.title, subtitle:`${j.location} · ${j.level} · ${j.salary}`, tags:j.skills }))
]
return (
<FeedLayout trending={data.products} news={data.companies} title="动态">
<Composer />
<div className="divide-y divide-[color:var(--border)]">
{feed.map((f:any)=> (
<FeedRow key={f.id} title={f.title} subtitle={f.subtitle} tags={f.tags} />
))}
</div>
</FeedLayout>
)
}
Component Patterns
Simple Card Component
export default function Card({ title, subtitle }: { title: string, subtitle?: string }){
return (
<div className="border border-neutral-800 p-4 rounded">
<div className="font-medium">{title}</div>
{subtitle ? <div className="text-sm text-neutral-400">{subtitle}</div> : null}
</div>
)
}
Feed Row Component
export default function FeedRow({ title, subtitle, tags, dense }: { title: string, subtitle?: string, tags?: string[], dense?: boolean }){
return (
<div className={`px-4 ${dense?'py-2':'py-3'} hover:bg-[color:color-mix(in oklab, var(--accent) 6%, transparent)] transition`}>
<div className={`font-medium ${dense?'text-[15px]':'text-[16px]'}`}>{title}</div>
{subtitle ? <div className={`${dense?'text-[14px]':'text-[15px]'} text-neutral-600 mt-0.5`}>{subtitle}</div> : null}
{tags && tags.length ? <div className="text-[12px] mt-1 tag">{tags.join(', ')}</div> : null}
<div className="mt-2 flex items-center gap-8 interactive-actions">
<button className="flex items-center gap-1 interactive-action" aria-label="评论" type="button">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 15a4 4 0 0 1-4 4H7l-4 4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v8z"/>
</svg>
</button>
</div>
</div>
)
}
Composer Component
export default function Composer(){
return (
<div className="panel border rounded-2xl px-4 py-3 mb-4">
<div className="text-sm text-neutral-600">有什么新鲜事?</div>
<div className="mt-2 flex items-center gap-2">
<input className="flex-1 bg-transparent outline-none text-sm" placeholder="分享你的动态、作品或招聘信息" />
<a href="/post" className="btn-primary px-3 py-1 rounded-full text-sm">发布</a>
</div>
</div>
)
}
Facet Filter Component
"use client"
import { useRouter, useSearchParams, usePathname } from "next/navigation"
type Facet = { key: string; label: string; options: string[] }
export default function FacetFilter({ facets }: { facets: Facet[] }){
const router = useRouter()
const pathname = usePathname()
const params = useSearchParams()
const setParam = (key: string, values: string[]) => {
const usp = new URLSearchParams(params.toString())
if (values.length) usp.set(key, values.join(',')); else usp.delete(key)
router.push(`${pathname}?${usp.toString()}`)
}
const toggle = (key: string, value: string) => {
const cur = (params.get(key) || '').split(',').filter(Boolean)
const next = cur.includes(value) ? cur.filter(v=>v!==value) : [...cur, value]
setParam(key, next)
}
}
Theme & Styling
CSS Variables (in globals.css)
:root {
--bg: 255 255 255;
--fg: 0 0 0;
--border: 240 240 240;
--accent: 0 100 220;
}
Tailwind Custom Classes
.globals.css:
.btn-primary {
@apply bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700;
}
.panel {
@apply bg-white border rounded-lg p-4;
}
.chip {
@apply px-3 py-1 rounded-full text-sm border;
}
Available Backend APIs
GET /api/explore
GET /api/projects
GET /api/products
GET /api/posts
GET /api/jobs
GET /api/companies/:id
POST /api/login
GET /api/me
GET /api/users/:id
GET /api/investors
GET /api/pitch/:id
GET /api/deal-room/:id
GET /api/media/:id
GET /api/media-assets
GET /api/company-verifications/:companyId
GET /api/job-compliance/:jobId
GET /api/content-moderation/:id
GET /api/usage
GET /api/quota
GET /api/capacity-packs
GET /api/job-slots
GET /api/charges
GET /api/inbox
GET /api/notification-preferences
Development Commands
cd web
npm run dev
npm run build
npm start
npx tsc --noEmit
npm run lint
Common Tasks
Create New Page
touch web/app/your-page/page.tsx
export default async function YourPage() {
const data = await api<DataType>('/api/endpoint')
return <div>{/* content */}</div>
}
Create New Component
export default function YourComponent({ prop1, prop2 }: Props) {
return (
<div className="...">
{/* component content */}
</div>
)
}
Add Interactive State (Client Component)
"use client"
import { useState } from 'react'
export default function InteractiveComponent() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
)
}
Handle Authentication
"use client"
export default function LoginPage() {
const handleLogin = async () => {
await fetch('http://localhost:8080/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ email: 'user@example.com' })
})
window.location.href = '/'
}
return <button onClick={handleLogin}>Login</button>
}
Routing
app/page.tsx
app/login/page.tsx
app/jobs/page.tsx
app/companies/[id]/page.tsx
export default async function CompanyPage({ params }: { params: { id: string } }) {
const company = await api<Company>(`/api/companies/${params.id}`)
return <div>{company.name}</div>
}