Route meetup RSVP to live/calendar and add Connect stats on profile.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
fe4f0f5983
commit
cc05691e9a
@ -275,15 +275,35 @@ async def rsvp_meetup(body: MeetupRsvpRequest, authorization: str | None = Heade
|
||||
raise HTTPException(400, "活动已满员")
|
||||
|
||||
meetup = community_store.get_meetup(body.meetup_id) or {}
|
||||
title = meetup.get("title") or "活动"
|
||||
mode = meetup.get("mode") or ""
|
||||
organizer_id = meetup.get("organizer_id") or ""
|
||||
if organizer_id and organizer_id != user_id:
|
||||
from app.services.notify import notify_user
|
||||
|
||||
if organizer_id and organizer_id != user_id:
|
||||
notify_user(
|
||||
organizer_id,
|
||||
"新的活动报名",
|
||||
f"{user.get('name', '游民')} 报名了「{meetup.get('title', '活动')}」",
|
||||
f"/meetups",
|
||||
f"{user.get('name', '游民')} 报名了「{title}」",
|
||||
"/meetups",
|
||||
"meetup",
|
||||
)
|
||||
|
||||
# Confirm to attendee with next-step deep link (live room or profile calendar)
|
||||
if mode in ("online", "hybrid"):
|
||||
notify_user(
|
||||
user_id,
|
||||
"报名成功",
|
||||
f"你已报名「{title}」,活动开始时可进入直播间",
|
||||
f"/meetups/{body.meetup_id}/live",
|
||||
"meetup",
|
||||
)
|
||||
else:
|
||||
notify_user(
|
||||
user_id,
|
||||
"报名成功",
|
||||
f"你已报名「{title}」,可在个人中心导出日历",
|
||||
"/profile",
|
||||
"meetup",
|
||||
)
|
||||
return {"success": True, "message": res["message"], "rsvp_count": res["rsvp_count"]}
|
||||
|
||||
@ -8,6 +8,14 @@ export const metadata: Metadata = {
|
||||
};
|
||||
|
||||
const LOGS = [
|
||||
{
|
||||
date: "2026-09-04",
|
||||
tag: "活动报名 · Connect 数据",
|
||||
items: [
|
||||
"报名成功 toast:线上/混合直达直播间,线下引导导出日历;报名确认写入通知箱",
|
||||
"个人中心新增 Connect 数据条(喜欢我的 / 互相喜欢 / 私信 / 已报名),可点进对应页面",
|
||||
],
|
||||
},
|
||||
{
|
||||
date: "2026-09-04",
|
||||
tag: "匹配资料 · 私信头",
|
||||
|
||||
@ -2767,6 +2767,22 @@ img { max-width: 100%; display: block; }
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.profile-connect-block {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.profile-connect-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 12px;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.profile-connect-stats {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.profile-stat-card {
|
||||
background: var(--bg-card);
|
||||
border: var(--border-glass);
|
||||
@ -2776,6 +2792,18 @@ img { max-width: 100%; display: block; }
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
a.profile-stat-link {
|
||||
display: block;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
transition: border-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
a.profile-stat-link:hover {
|
||||
border-color: var(--accent, #e8a87c);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.profile-stat-card strong {
|
||||
display: block;
|
||||
font-size: 1.75rem;
|
||||
|
||||
@ -60,6 +60,7 @@ export default function ProfilePage() {
|
||||
const [stats, setStats] = useState<ProfileStats | null>(null);
|
||||
const [favoriteDests, setFavoriteDests] = useState<Destination[]>([]);
|
||||
const [rsvps, setRsvps] = useState<Meetup[]>([]);
|
||||
const [connect, setConnect] = useState({ likes: 0, matches: 0, chats: 0 });
|
||||
const [trip, setTrip] = useState<TripItem[]>([]);
|
||||
const [meta, setMeta] = useState<PlanMeta | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@ -109,11 +110,21 @@ export default function ProfilePage() {
|
||||
const idSet = new Set(mine.ids || []);
|
||||
return all.filter((m) => idSet.has(m.id)).slice(0, 6);
|
||||
}),
|
||||
]).then(([s, f, v, myMeetups]) => {
|
||||
Promise.all([
|
||||
api.getMatchLikesReceived(token).catch(() => []),
|
||||
api.getMutualMatches(token).catch(() => []),
|
||||
api.getConversations(token).catch(() => []),
|
||||
]).then(([likes, matches, chats]) => ({
|
||||
likes: likes.length,
|
||||
matches: matches.length,
|
||||
chats: chats.length,
|
||||
})),
|
||||
]).then(([s, f, v, myMeetups, connectStats]) => {
|
||||
setStats(s);
|
||||
setFavoriteDests(f);
|
||||
setVip(Boolean(v.vip));
|
||||
setRsvps(myMeetups);
|
||||
setConnect(connectStats);
|
||||
}).catch(() => {}).finally(() => setLoading(false));
|
||||
|
||||
refreshPlan();
|
||||
@ -220,6 +231,32 @@ export default function ProfilePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="profile-connect-block">
|
||||
<h2 className="profile-connect-title">{t.profile.connectStrip}</h2>
|
||||
<div className="profile-stats-grid profile-connect-stats">
|
||||
<Link href="/dating/likes" className="profile-stat-card profile-stat-link">
|
||||
<span className="stat-emoji">💕</span>
|
||||
<strong>{connect.likes}</strong>
|
||||
<span>{t.profile.statLikes}</span>
|
||||
</Link>
|
||||
<Link href="/dating" className="profile-stat-card profile-stat-link">
|
||||
<span className="stat-emoji">✨</span>
|
||||
<strong>{connect.matches}</strong>
|
||||
<span>{t.profile.statMatches}</span>
|
||||
</Link>
|
||||
<Link href="/chat" className="profile-stat-card profile-stat-link">
|
||||
<span className="stat-emoji">✉️</span>
|
||||
<strong>{connect.chats}</strong>
|
||||
<span>{t.profile.statChats}</span>
|
||||
</Link>
|
||||
<Link href="/meetups" className="profile-stat-card profile-stat-link">
|
||||
<span className="stat-emoji">🎉</span>
|
||||
<strong>{rsvps.length}</strong>
|
||||
<span>{t.profile.statRsvps}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NomadDigest rsvpCount={rsvps.length} upcoming={rsvps} />
|
||||
|
||||
<section className="profile-trip">
|
||||
|
||||
@ -128,9 +128,10 @@ export default function MeetupsClient({ meetups }: Props) {
|
||||
try {
|
||||
const res = await api.rsvpMeetup(meetup.id, undefined, token);
|
||||
setRsvpIds(new Set(rsvpIds).add(meetup.id));
|
||||
const live = meetup.mode === "online" || meetup.mode === "hybrid";
|
||||
toast(res.message || t.meetups.rsvpOk, "success", {
|
||||
href: "/dating",
|
||||
label: t.meetups.goDating,
|
||||
href: live ? meetupEventHref(meetup) : "/profile",
|
||||
label: live ? t.meetups.liveBtn : t.meetups.rsvpCalCta,
|
||||
});
|
||||
void loadPeers(meetup.id);
|
||||
} catch {
|
||||
|
||||
@ -685,6 +685,7 @@ export const zh = {
|
||||
tripEvents: "行程城市活动",
|
||||
meetPeers: "同城可能认识的人",
|
||||
goDating: "去匹配",
|
||||
rsvpCalCta: "导出日历",
|
||||
viewProfile: "查看资料",
|
||||
},
|
||||
community: {
|
||||
@ -1074,6 +1075,11 @@ export const zh = {
|
||||
statCities: "计划城市",
|
||||
statReady: "出发就绪",
|
||||
statBadges: "成就徽章",
|
||||
connectStrip: "Connect 动态",
|
||||
statLikes: "喜欢我的",
|
||||
statMatches: "互相喜欢",
|
||||
statChats: "私信会话",
|
||||
statRsvps: "已报名活动",
|
||||
openPlan: "打开计划中心",
|
||||
tripEmpty: "还没有规划行程",
|
||||
goPlan: "去规划 →",
|
||||
@ -1953,6 +1959,7 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
|
||||
tripEvents: "Events near your trip",
|
||||
meetPeers: "People you may meet nearby",
|
||||
goDating: "Go match",
|
||||
rsvpCalCta: "Export calendar",
|
||||
viewProfile: "View profile",
|
||||
},
|
||||
community: {
|
||||
@ -2294,6 +2301,11 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
|
||||
statCities: "Plan cities",
|
||||
statReady: "Readiness",
|
||||
statBadges: "Badges",
|
||||
connectStrip: "Connect pulse",
|
||||
statLikes: "Liked you",
|
||||
statMatches: "Matches",
|
||||
statChats: "Chats",
|
||||
statRsvps: "RSVPs",
|
||||
openPlan: "Open plan hub",
|
||||
tripEmpty: "No trip planned yet",
|
||||
goPlan: "Start planning →",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user