's'
This commit is contained in:
@@ -79,3 +79,16 @@ export async function sendChatMessage(conversationId: string, body: string): Pro
|
||||
export async function markChatRead(conversationId: string): Promise<void> {
|
||||
await apiFetch(`/api/conversations/${conversationId}/read`, { method: "POST", body: JSON.stringify({}) });
|
||||
}
|
||||
|
||||
export async function startConversation(options: {
|
||||
profileId?: string;
|
||||
targetUserId?: string;
|
||||
intent?: string;
|
||||
opener?: string;
|
||||
}): Promise<ConversationItem> {
|
||||
const data = await apiFetch<{ conversation: ConversationItem }>("/api/conversations/start", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(options),
|
||||
});
|
||||
return data.conversation;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,34 @@ const CITY_SLUG_BY_NAME: Record<string, string> = {
|
||||
青岛: "qingdao",
|
||||
苏州: "suzhou",
|
||||
珠海: "zhuhai",
|
||||
天津: "tianjin",
|
||||
石家庄: "shijiazhuang",
|
||||
太原: "taiyuan",
|
||||
呼和浩特: "hohhot",
|
||||
大连: "dalian",
|
||||
沈阳: "shenyang",
|
||||
长春: "changchun",
|
||||
哈尔滨: "harbin",
|
||||
无锡: "wuxi",
|
||||
宁波: "ningbo",
|
||||
合肥: "hefei",
|
||||
福州: "fuzhou",
|
||||
南昌: "nanchang",
|
||||
济南: "jinan",
|
||||
郑州: "zhengzhou",
|
||||
武汉: "wuhan",
|
||||
桂林: "guilin",
|
||||
南宁: "nanning",
|
||||
海口: "haikou",
|
||||
贵阳: "guiyang",
|
||||
拉萨: "lhasa",
|
||||
兰州: "lanzhou",
|
||||
西宁: "xining",
|
||||
银川: "yinchuan",
|
||||
乌鲁木齐: "urumqi",
|
||||
香港: "hong-kong",
|
||||
澳门: "macau",
|
||||
台北: "taipei",
|
||||
};
|
||||
|
||||
function normalizeSlug(value: string): string {
|
||||
|
||||
64
app/lib/city-weather.ts
Normal file
64
app/lib/city-weather.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
export interface CityWeatherLive {
|
||||
temperature: number;
|
||||
humidity?: number;
|
||||
apparentTemperature?: number;
|
||||
weatherCode?: number;
|
||||
observedAt?: string;
|
||||
}
|
||||
|
||||
export type CityWeatherMap = Record<string, CityWeatherLive>;
|
||||
|
||||
/** WMO weather code → 简短展示 */
|
||||
export function weatherCodeLabel(code?: number, locale: "zh" | "en" = "zh"): string | null {
|
||||
if (code == null) return null;
|
||||
const zh: Record<number, string> = {
|
||||
0: "晴",
|
||||
1: "多云",
|
||||
2: "多云",
|
||||
3: "阴",
|
||||
45: "雾",
|
||||
48: "雾",
|
||||
51: "小雨",
|
||||
53: "小雨",
|
||||
55: "小雨",
|
||||
61: "雨",
|
||||
63: "雨",
|
||||
65: "大雨",
|
||||
71: "雪",
|
||||
73: "雪",
|
||||
75: "大雪",
|
||||
80: "阵雨",
|
||||
81: "阵雨",
|
||||
82: "暴雨",
|
||||
95: "雷雨",
|
||||
};
|
||||
const en: Record<number, string> = {
|
||||
0: "Clear",
|
||||
1: "Cloudy",
|
||||
2: "Cloudy",
|
||||
3: "Overcast",
|
||||
45: "Fog",
|
||||
48: "Fog",
|
||||
51: "Drizzle",
|
||||
53: "Drizzle",
|
||||
55: "Drizzle",
|
||||
61: "Rain",
|
||||
63: "Rain",
|
||||
65: "Heavy rain",
|
||||
71: "Snow",
|
||||
73: "Snow",
|
||||
75: "Heavy snow",
|
||||
80: "Showers",
|
||||
81: "Showers",
|
||||
82: "Storms",
|
||||
95: "Thunder",
|
||||
};
|
||||
const table = locale === "zh" ? zh : en;
|
||||
return table[code] ?? (locale === "zh" ? "天气" : "Weather");
|
||||
}
|
||||
|
||||
export function cityWeatherKey(city: { slug?: string; id?: number; name?: string }): string {
|
||||
if (city.slug) return city.slug;
|
||||
if (city.id != null) return String(city.id);
|
||||
return city.name || "";
|
||||
}
|
||||
@@ -134,7 +134,7 @@ export function getLoungeServerLabel(): string {
|
||||
}
|
||||
|
||||
export function buildMeetupChatUrl(meetup: MeetupLike): string {
|
||||
const channel = createLoungeChannel(meetup);
|
||||
const channel = createLoungeChannel(meetup).replace(/^#+/, "");
|
||||
return `${getLoungeBaseUrl()}/#/chan-${channel}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,6 @@ export function usePayStatusPoll(onPaid: (orderId: string) => void): boolean {
|
||||
attempts += 1;
|
||||
if (attempts > MAX_POLLS) {
|
||||
stop();
|
||||
clearPendingOrder();
|
||||
setPolling(false);
|
||||
return;
|
||||
}
|
||||
|
||||
97
app/lib/social.ts
Normal file
97
app/lib/social.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { apiFetch } from "@/app/lib/api-client";
|
||||
|
||||
export interface PublicProfile {
|
||||
id: string;
|
||||
userId?: string;
|
||||
name?: string;
|
||||
age?: number;
|
||||
location?: string;
|
||||
citySlug?: string;
|
||||
gender?: string;
|
||||
single?: string;
|
||||
tags?: string[];
|
||||
bio?: string;
|
||||
photo?: string;
|
||||
lookingFor?: string[];
|
||||
}
|
||||
|
||||
export interface DiscussionReply {
|
||||
id: string;
|
||||
discussionId?: string;
|
||||
userId?: string;
|
||||
author?: string;
|
||||
body: string;
|
||||
created?: string;
|
||||
}
|
||||
|
||||
export async function fetchPublicProfile(profileId: string): Promise<PublicProfile | null> {
|
||||
try {
|
||||
const data = await apiFetch<{ profile?: PublicProfile }>(`/api/profiles/${profileId}`);
|
||||
return data.profile || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function rsvpMeetup(meetupId: string): Promise<{ rsvp: boolean; meetup?: Record<string, unknown> }> {
|
||||
return apiFetch(`/api/meetups/${meetupId}/rsvp`, { method: "POST", body: JSON.stringify({}) });
|
||||
}
|
||||
|
||||
export async function cancelMeetupRsvp(meetupId: string): Promise<{ rsvp: boolean; meetup?: Record<string, unknown> }> {
|
||||
return apiFetch(`/api/meetups/${meetupId}/rsvp`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export async function fetchMeetupRsvpStatus(meetupId: string): Promise<boolean> {
|
||||
try {
|
||||
const data = await apiFetch<{ rsvp?: boolean }>(`/api/meetups/${meetupId}/rsvp`);
|
||||
return Boolean(data.rsvp);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchDiscussion(discussionId: string) {
|
||||
return apiFetch<{ item: Record<string, unknown> }>(`/api/discussions/${discussionId}`);
|
||||
}
|
||||
|
||||
export async function fetchDiscussionReplies(discussionId: string): Promise<DiscussionReply[]> {
|
||||
const data = await apiFetch<{ items: DiscussionReply[] }>(`/api/discussions/${discussionId}/replies`);
|
||||
return data.items || [];
|
||||
}
|
||||
|
||||
export async function postDiscussionReply(discussionId: string, body: string): Promise<DiscussionReply> {
|
||||
const data = await apiFetch<{ reply: DiscussionReply }>(`/api/discussions/${discussionId}/replies`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ body }),
|
||||
});
|
||||
return data.reply;
|
||||
}
|
||||
|
||||
export async function toggleDiscussionLike(
|
||||
discussionId: string
|
||||
): Promise<{ liked: boolean; likes: number; item?: Record<string, unknown> }> {
|
||||
return apiFetch(`/api/discussions/${discussionId}/like`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function toggleDiscussionPin(
|
||||
discussionId: string
|
||||
): Promise<{ pinned: boolean; item?: Record<string, unknown> }> {
|
||||
return apiFetch(`/api/discussions/${discussionId}/pin`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchMeetupSocialSuggestions(meetupId: string): Promise<{
|
||||
items: Array<PublicProfile & { rsvpAttendee?: boolean }>;
|
||||
city: string;
|
||||
}> {
|
||||
const data = await apiFetch<{
|
||||
items: Array<PublicProfile & { rsvpAttendee?: boolean }>;
|
||||
city: string;
|
||||
}>(`/api/meetups/${meetupId}/social-suggestions`);
|
||||
return { items: data.items || [], city: data.city || "" };
|
||||
}
|
||||
Reference in New Issue
Block a user