Document wemini credentials and native weapp workflow in README; add jscode2session auth endpoint. Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
3.9 KiB
TypeScript
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>
|
|
)
|
|
}
|