nomadweb/wemini/src/pages/meetups/index.tsx
eric 79dda36dd3 Ship Taro WeChat mini-app with H5-aligned UI and WeChat login.
Document wemini credentials and native weapp workflow in README; add jscode2session auth endpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-27 22:05:56 -05:00

107 lines
3.9 KiB
TypeScript

import { View, Text, Button, ScrollView } from '@tarojs/components'
import Taro, { useLoad } from '@tarojs/taro'
import { useMemo, useState } from 'react'
import { BrandNav, SiteFooter } from '../../components/Chrome'
import { fetchMeetups, type Meetup } from '../../services/api'
import './index.scss'
export default function MeetupsPage() {
const [list, setList] = useState<Meetup[]>([])
const [mode, setMode] = useState<'all' | 'online' | 'offline' | 'hybrid'>('all')
const [err, setErr] = useState('')
const [loading, setLoading] = useState(true)
useLoad(() => {
fetchMeetups()
.then((d) => { setList(d); setLoading(false) })
.catch((e) => { setErr(e.message || '加载失败'); setLoading(false) })
})
const filtered = useMemo(() => {
if (mode === 'all') return list
return list.filter((m) => (m.mode || '').toLowerCase() === mode)
}, [list, mode])
return (
<ScrollView scrollY className='page-shell' showScrollbar={false}>
<BrandNav chip='活动' />
<View className='container' style={{ paddingBottom: '48px' }}>
<Text className='section-tag'>🎉 MEETUPS</Text>
<Text className='section-h2'>游民活动</Text>
<Text className='section-p'>同城与线上 · 登录后可 RSVP</Text>
<View className='meetups-toolbar'>
{([
['all', '全部'],
['online', '线上'],
['offline', '线下'],
['hybrid', '混合'],
] as const).map(([k, label]) => (
<Button
key={k}
className={`filter-btn${mode === k ? ' active' : ''}`}
onClick={() => setMode(k)}
>
{label}
</Button>
))}
</View>
{loading ? <Text className='dest-empty'>加载中…</Text> : null}
{err ? <Text className='dest-empty'>{err}</Text> : null}
{filtered.map((m) => (
<View key={m.id} className='meetup-card'>
<View className='meetup-card-top'>
<Text className='meetup-emoji'>{m.emoji || '🎉'}</Text>
<View style={{ flex: 1 }}>
<Text className='meetup-title'>{m.title}</Text>
<Text className='meetup-meta'>
{m.city || '—'} · {m.date || ''} {m.time || ''} · {m.mode || 'offline'}
</Text>
</View>
</View>
<Text className='meetup-desc'>{m.description || ''}</Text>
{m.venue ? <Text className='meetup-venue'>📍 {m.venue}</Text> : null}
<View className='meetup-tags'>
{(m.tags || []).map((tag) => (
<Text key={tag} className='meetup-tag'>{tag}</Text>
))}
</View>
<View className='meetup-footer'>
<Text className='meetup-footer-text'>
{m.rsvp_count ?? m.attendees ?? 0}/{m.max_attendees || '∞'} 人
</Text>
<View style={{ display: 'flex', gap: '8px' }}>
<Button
className='btn btn-sm btn-primary'
onClick={() => Taro.showToast({ title: '请先在「我的」登录', icon: 'none' })}
>
RSVP
</Button>
{m.destination_slug ? (
<Button
className='btn btn-sm btn-ghost'
onClick={() =>
Taro.navigateTo({
url: `/pages/destination/index?slug=${encodeURIComponent(m.destination_slug!)}`,
})
}
>
城市
</Button>
) : null}
</View>
</View>
</View>
))}
{!loading && !err && !filtered.length ? (
<Text className='dest-empty'>暂无活动</Text>
) : null}
</View>
<SiteFooter />
</ScrollView>
)
}