@@ -115,8 +120,26 @@ export default function ChatThreadClient({ convId }: { convId: string }) {
{loading && messages.length === 0 &&
{t.chat.loadingMessages}
}
{!loading && messages.length === 0 && (
diff --git a/frontend/src/components/DatingLikesClient.tsx b/frontend/src/components/DatingLikesClient.tsx
index 3db98b9..4320b01 100644
--- a/frontend/src/components/DatingLikesClient.tsx
+++ b/frontend/src/components/DatingLikesClient.tsx
@@ -108,7 +108,11 @@ export default function DatingLikesClient() {
{!loading &&
list.map((p) => {
const photo = p.photo || "";
- const href = p.userId ? `/members/${p.userId}` : "/dating";
+ const href = p.userId
+ ? `/members/${p.userId}?from=likes`
+ : p.id
+ ? `/members/${p.id}?from=likes`
+ : "/dating";
return (
diff --git a/frontend/src/components/MemberProfileClient.tsx b/frontend/src/components/MemberProfileClient.tsx
index aa2e972..6787b0f 100644
--- a/frontend/src/components/MemberProfileClient.tsx
+++ b/frontend/src/components/MemberProfileClient.tsx
@@ -1,7 +1,12 @@
"use client";
+import { useEffect, useState } from "react";
import Link from "next/link";
+import { useRouter, useSearchParams } from "next/navigation";
+import { api } from "@/lib/api";
+import { useAuth } from "@/lib/auth";
import { useI18n } from "@/lib/i18n";
+import { useToast } from "@/lib/toast";
import type { MemberProfile } from "@/lib/types";
import RingNext from "@/components/RingNext";
import { useRingSteps } from "@/lib/rings";
@@ -15,11 +20,59 @@ function cityFromLocation(location?: string) {
export default function MemberProfileClient({ profile }: { profile: MemberProfile }) {
const { t } = useI18n();
+ const { token, user } = useAuth();
+ const { toast } = useToast();
+ const router = useRouter();
+ const search = useSearchParams();
const rings = useRingSteps();
const photo = profile.photo || "";
const isUrl = photo.startsWith("http");
const city = cityFromLocation(profile.location);
const meetupsHref = city ? meetupCityHref(city) : "/meetups";
+ const [convId, setConvId] = useState
(null);
+ const [liking, setLiking] = useState(false);
+ const fromLikes = search.get("from") === "likes";
+
+ const profileId = profile.id || (profile.userId ? `user-${profile.userId}` : "");
+ const memberHref = profile.userId || profile.id || "";
+
+ useEffect(() => {
+ if (!token || !profile.userId) return;
+ api
+ .getMutualMatches(token)
+ .then((res) => {
+ const hit = (res.items || []).find(
+ (m) => m.peer?.userId === profile.userId || m.peer?.id === profile.id
+ );
+ if (hit?.conversationId) setConvId(hit.conversationId);
+ })
+ .catch(() => {});
+ }, [token, profile.userId, profile.id]);
+
+ const likeBack = async () => {
+ if (!token) {
+ router.push(`/login?next=/members/${memberHref}`);
+ return;
+ }
+ if (!profileId || liking) return;
+ setLiking(true);
+ try {
+ const res = await api.swipe(token, profileId, "like", "friends");
+ if (res.matched && res.match?.conversationId) {
+ setConvId(res.match.conversationId);
+ toast(t.dating.matched + " " + profile.name, "success", {
+ href: `/chat/${res.match.conversationId}`,
+ label: t.dating.chatNow,
+ });
+ } else {
+ toast(t.dating.likeSent, "success", { href: "/dating/likes", label: t.dating.likesTitle });
+ }
+ } catch {
+ toast(t.dating.swipeFail, "error");
+ } finally {
+ setLiking(false);
+ }
+ };
return (
@@ -27,6 +80,8 @@ export default function MemberProfileClient({ profile }: { profile: MemberProfil
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index fedd719..c2df3c3 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -250,6 +250,15 @@ export const api = {
}),
getConversations: (token: string) =>
fetchAPI("/social/conversations", { headers: { Authorization: `Bearer ${token}` } }),
+ getConversation: (token: string, convId: string) =>
+ fetchAPI(`/social/conversations/${convId}`, {
+ headers: { Authorization: `Bearer ${token}` },
+ }),
+ getMutualMatches: (token: string) =>
+ fetchAPI<{ items: Array<{ conversationId?: string; peer?: MatchProfile; intent?: string }> }>(
+ "/social/matches/mutual",
+ { headers: { Authorization: `Bearer ${token}` } }
+ ),
getMessages: (token: string, convId: string) =>
fetchAPI(`/social/conversations/${convId}/messages`, {
headers: { Authorization: `Bearer ${token}` },
diff --git a/frontend/src/lib/i18n/dictionaries.ts b/frontend/src/lib/i18n/dictionaries.ts
index 34b5542..923cbd9 100644
--- a/frontend/src/lib/i18n/dictionaries.ts
+++ b/frontend/src/lib/i18n/dictionaries.ts
@@ -457,6 +457,9 @@ export const zh = {
likesReceivedSubtitle: "对你右滑的游民会出现在这里",
likesReceivedEmpty: "还没有人喜欢你,先去完善资料并滑动匹配",
likesYou: "喜欢了你",
+ likeBack: "回赞 / 匹配",
+ likeSent: "已发送喜欢",
+ viewProfile: "查看资料",
},
chat: {
tag: "✉️ MESSAGES",
@@ -1722,6 +1725,9 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
likesReceivedSubtitle: "People who swiped right on you",
likesReceivedEmpty: "No incoming likes yet — keep your profile fresh and swipe",
likesYou: "Liked you",
+ likeBack: "Like back",
+ likeSent: "Like sent",
+ viewProfile: "View profile",
},
chat: {
tag: "✉️ MESSAGES",