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