Polish three rings: connect auth gates, explore shortcuts, grow nav and forms.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2ee0f3bf77
commit
ea030275ce
@ -1,4 +1,5 @@
|
||||
import type { Metadata } from "next";
|
||||
import { api } from "@/lib/api";
|
||||
import SiteShell from "@/components/SiteShell";
|
||||
import AiAssistantClient from "@/components/AiAssistantClient";
|
||||
|
||||
@ -7,10 +8,13 @@ export const metadata: Metadata = {
|
||||
description: "智能推荐下一站游民城市",
|
||||
};
|
||||
|
||||
export default function AiPage() {
|
||||
export const revalidate = 60;
|
||||
|
||||
export default async function AiPage() {
|
||||
const destinations = await api.getDestinations().catch(() => []);
|
||||
return (
|
||||
<SiteShell showFooter>
|
||||
<AiAssistantClient />
|
||||
<AiAssistantClient destinations={destinations} />
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,6 +8,15 @@ export const metadata: Metadata = {
|
||||
};
|
||||
|
||||
const LOGS = [
|
||||
{
|
||||
date: "2026-08-30",
|
||||
tag: "三环登录与导航",
|
||||
items: [
|
||||
"Connect:个人中心三环快捷入口、匹配加载态、发起活动/发赏金登录门、私信与登录提示",
|
||||
"Explore:全站快捷键、首页 hash 锚点、对比空态、AI 推荐真正写入计划",
|
||||
"Grow:导航补全服务/投稿、表单校验、学院空态与 VIP 课时直达开通",
|
||||
],
|
||||
},
|
||||
{
|
||||
date: "2026-08-30",
|
||||
tag: "三环可用深化",
|
||||
|
||||
@ -10903,6 +10903,14 @@ img { max-width: 100%; display: block; }
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.digital-lesson-list li a.digital-lesson-locked {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.digital-lesson-list li a.digital-lesson-locked:hover .digital-lesson-meta {
|
||||
color: var(--accent-3);
|
||||
}
|
||||
|
||||
.digital-lesson-meta {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
|
||||
@ -73,7 +73,7 @@ export default function ProfilePage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
router.push("/login");
|
||||
router.push("/login?next=/profile");
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@ -291,8 +291,13 @@ export default function ProfilePage() {
|
||||
<Link href="/#destinations" className="quick-card">🌍 浏览目的地</Link>
|
||||
<Link href="/plan" className="quick-card">🗓️ 旅居计划</Link>
|
||||
<Link href="/compare" className="quick-card">⚖️ 城市对比</Link>
|
||||
<Link href="/#visa" className="quick-card">📋 签证指南</Link>
|
||||
<Link href="/#blog" className="quick-card">📝 阅读博客</Link>
|
||||
<Link href="/next-stop" className="quick-card">🧭 智能下一站</Link>
|
||||
<Link href="/meetups" className="quick-card">🎉 游民活动</Link>
|
||||
<Link href="/dating" className="quick-card">💕 游民匹配</Link>
|
||||
<Link href="/chat" className="quick-card">✉️ 私信</Link>
|
||||
<Link href="/notifications" className="quick-card">🔔 通知</Link>
|
||||
<Link href={vip ? "/join" : "/join"} className="quick-card">{vip ? "✨ VIP 会员" : "✨ 开通 VIP"}</Link>
|
||||
<Link href="/digital" className="quick-card">🎓 游民学院</Link>
|
||||
<Link href="/tools" className="quick-card">🛠️ 工具箱</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -2,9 +2,12 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { api } from "@/lib/api";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
import { useToast } from "@/lib/toast";
|
||||
import { mergeDestinationsIntoTrip } from "@/lib/tripActions";
|
||||
import type { Destination } from "@/lib/types";
|
||||
import RingNext from "@/components/RingNext";
|
||||
import { useRingSteps } from "@/lib/rings";
|
||||
|
||||
@ -14,9 +17,14 @@ const SUGGESTIONS = [
|
||||
"清迈和巴厘岛怎么选?",
|
||||
];
|
||||
|
||||
export default function AiAssistantClient() {
|
||||
interface Props {
|
||||
destinations: Destination[];
|
||||
}
|
||||
|
||||
export default function AiAssistantClient({ destinations }: Props) {
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const rings = useRingSteps();
|
||||
const [message, setMessage] = useState("");
|
||||
const [reply, setReply] = useState("");
|
||||
@ -43,6 +51,24 @@ export default function AiAssistantClient() {
|
||||
}
|
||||
};
|
||||
|
||||
const addToPlan = () => {
|
||||
const dests = cities
|
||||
.map((c) => destinations.find((d) => d.slug === c.slug))
|
||||
.filter((d): d is Destination => Boolean(d));
|
||||
if (dests.length === 0) {
|
||||
toast("推荐城市暂无完整数据,请从详情页加入计划", "info");
|
||||
router.push("/plan");
|
||||
return;
|
||||
}
|
||||
const { added, skipped } = mergeDestinationsIntoTrip(dests, 1);
|
||||
if (added === 0) {
|
||||
toast(skipped ? t.compare.alreadyInPlan : "未能写入计划", "info");
|
||||
} else {
|
||||
toast(`${t.compare.addedN} ${added} ${t.compare.citiesUnit}`);
|
||||
}
|
||||
router.push("/plan");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="community-page">
|
||||
<div className="container">
|
||||
@ -93,9 +119,11 @@ export default function AiAssistantClient() {
|
||||
<Link href="/compare" className="btn btn-sm">
|
||||
城市对比
|
||||
</Link>
|
||||
<Link href="/plan" className="btn btn-sm">
|
||||
写入计划
|
||||
</Link>
|
||||
{cities.length > 0 && (
|
||||
<button type="button" className="btn btn-sm btn-primary" onClick={addToPlan}>
|
||||
写入计划
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -45,6 +45,7 @@ export default function ChatClient() {
|
||||
<div className="container">
|
||||
<div className="dating-gate reveal">
|
||||
<h2>{t.chat.loginTitle}</h2>
|
||||
<p>{t.chat.loginDesc}</p>
|
||||
<Link href="/login?next=/chat" className="btn btn-primary">{t.nav.login}</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -62,6 +62,7 @@ export default function ChatThreadClient({ convId }: { convId: string }) {
|
||||
<div className="container">
|
||||
<div className="dating-gate reveal">
|
||||
<h2>{t.chat.loginTitle}</h2>
|
||||
<p>{t.chat.loginDesc}</p>
|
||||
<Link href={`/login?next=/chat/${convId}`} className="btn btn-primary">
|
||||
{t.nav.login}
|
||||
</Link>
|
||||
|
||||
@ -125,9 +125,23 @@ export default function CommunityHubClient({ discussions, featured }: Props) {
|
||||
{filtered.length === 0 && (
|
||||
<div className="notif-empty reveal">
|
||||
<p className="dest-empty">{t.community.empty}</p>
|
||||
<Link href="/community/new" className="btn btn-primary btn-sm">
|
||||
{t.community.newTopic}
|
||||
</Link>
|
||||
<div className="dating-empty-actions">
|
||||
{(category !== "全部" || q.trim()) && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => {
|
||||
setCategory("全部");
|
||||
setQ("");
|
||||
}}
|
||||
>
|
||||
{t.meetups.all}
|
||||
</button>
|
||||
)}
|
||||
<Link href="/community/new" className="btn btn-primary btn-sm">
|
||||
{t.community.newTopic}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -145,7 +145,11 @@ export default function CompareClient({ destinations }: Props) {
|
||||
<div className="plan-empty compare-page-empty">
|
||||
<span className="plan-empty-icon">⚖️</span>
|
||||
<p>{t.compare.needMore} {Math.max(0, 2 - selected.length)} {t.compare.needMoreSuffix}</p>
|
||||
<Link href="/#destinations" className="btn btn-ghost">{t.compare.backDest}</Link>
|
||||
<div className="dating-empty-actions">
|
||||
<Link href="/#destinations" className="btn btn-ghost">{t.compare.backDest}</Link>
|
||||
<Link href="/next-stop" className="btn">{t.nav.nextStop}</Link>
|
||||
<Link href="/ai" className="btn btn-ghost">{t.nav.ai}</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@ -8,11 +8,15 @@ import { useAuth } from "@/lib/auth";
|
||||
import { useToast } from "@/lib/toast";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
|
||||
import RingNext from "@/components/RingNext";
|
||||
import { useRingSteps } from "@/lib/rings";
|
||||
|
||||
export default function ContentSubmitClient() {
|
||||
const { token } = useAuth();
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const { t } = useI18n();
|
||||
const rings = useRingSteps();
|
||||
const [title, setTitle] = useState("");
|
||||
const [url, setUrl] = useState("");
|
||||
const [notes, setNotes] = useState("");
|
||||
@ -29,6 +33,10 @@ export default function ContentSubmitClient() {
|
||||
toast("请填写更完整的标题", "info");
|
||||
return;
|
||||
}
|
||||
if ((contentType === "article" || contentType === "video") && !/^https?:\/\/.+/.test(url.trim())) {
|
||||
toast("文章/视频请填写有效链接(http/https)", "info");
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
await api.submitContent(
|
||||
@ -113,6 +121,7 @@ export default function ContentSubmitClient() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<RingNext steps={rings.afterGigs} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -160,7 +160,9 @@ export default function DatingClient() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading && joined && <p className="dest-empty reveal">加载候选人…</p>}
|
||||
{loading && (
|
||||
<p className="dest-empty reveal">{joined ? "加载候选人…" : "加载匹配资料…"}</p>
|
||||
)}
|
||||
|
||||
{top && (
|
||||
<div className="dating-card reveal">
|
||||
|
||||
@ -39,7 +39,14 @@ export default function DigitalCourseClient({ course }: { course: DigitalCourse
|
||||
)}
|
||||
</div>
|
||||
{course.modules.length === 0 ? (
|
||||
<p className="dest-empty">课程内容准备中</p>
|
||||
<div className="notif-empty reveal">
|
||||
<p className="dest-empty">课程内容准备中</p>
|
||||
<div className="dating-empty-actions">
|
||||
<Link href="/digital" className="btn">{t.digital.back}</Link>
|
||||
<Link href="/gigs" className="btn btn-ghost">{t.nav.gigs}</Link>
|
||||
<Link href="/join" className="btn btn-primary">{t.nav.join}</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="digital-modules reveal">
|
||||
{course.modules.map((mod, mi) => (
|
||||
@ -50,16 +57,22 @@ export default function DigitalCourseClient({ course }: { course: DigitalCourse
|
||||
const locked = !lesson.free && !vip;
|
||||
return (
|
||||
<li key={lesson.title}>
|
||||
<Link href={`/digital/course/${mi}/${li}`}>
|
||||
<span>
|
||||
{locked ? "🔒 " : ""}
|
||||
{lesson.title}
|
||||
</span>
|
||||
<span className="digital-lesson-meta">
|
||||
{lesson.duration}
|
||||
{lesson.free ? ` · ${t.digital.free}` : " · VIP"}
|
||||
</span>
|
||||
</Link>
|
||||
{locked ? (
|
||||
<Link href="/join" className="digital-lesson-locked">
|
||||
<span>🔒 {lesson.title}</span>
|
||||
<span className="digital-lesson-meta">
|
||||
{lesson.duration} · VIP · 开通解锁
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href={`/digital/course/${mi}/${li}`}>
|
||||
<span>{lesson.title}</span>
|
||||
<span className="digital-lesson-meta">
|
||||
{lesson.duration}
|
||||
{lesson.free ? ` · ${t.digital.free}` : " · VIP"}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -6,11 +6,14 @@ import { api } from "@/lib/api";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { useToast } from "@/lib/toast";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
import RingNext from "@/components/RingNext";
|
||||
import { useRingSteps } from "@/lib/rings";
|
||||
|
||||
export default function FeedbackClient() {
|
||||
const { token } = useAuth();
|
||||
const { toast } = useToast();
|
||||
const { t } = useI18n();
|
||||
const rings = useRingSteps();
|
||||
const [content, setContent] = useState("");
|
||||
const [category, setCategory] = useState("general");
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -79,6 +82,7 @@ export default function FeedbackClient() {
|
||||
{!token && <p className="section-desc">未登录也可提交;登录后便于我们回复你</p>}
|
||||
</div>
|
||||
)}
|
||||
<RingNext steps={rings.afterGigs} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -61,6 +61,9 @@ export default function Footer() {
|
||||
<h4>{t.footer.grow}</h4>
|
||||
<Link href="/digital">{t.footer.digital}</Link>
|
||||
<Link href="/gigs">{t.footer.gigs}</Link>
|
||||
<Link href="/services">{t.nav.services}</Link>
|
||||
<Link href="/submit">{t.submit.tag}</Link>
|
||||
<Link href="/feedback">{t.nav.feedback}</Link>
|
||||
<Link href="/tools">{t.footer.toolkit}</Link>
|
||||
<Link href="/join">{t.footer.join}</Link>
|
||||
</div>
|
||||
|
||||
@ -19,6 +19,21 @@ export default function GigPostClient() {
|
||||
const [deadline, setDeadline] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
if (!token) {
|
||||
return (
|
||||
<div className="community-page">
|
||||
<div className="container">
|
||||
<nav className="detail-nav"><Link href="/gigs">{t.gigs.back}</Link></nav>
|
||||
<div className="dating-gate reveal">
|
||||
<h2>{t.gigs.postTitle}</h2>
|
||||
<p>{t.gigs.loginFirst}</p>
|
||||
<Link href="/login?next=/gigs/post" className="btn btn-primary">{t.nav.login}</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (!token) {
|
||||
router.push("/login?next=/gigs/post");
|
||||
|
||||
@ -53,6 +53,17 @@ export default function HomeClient(props: Props) {
|
||||
const [chromeReady, setChromeReady] = useState(false);
|
||||
const rings = useRingSteps();
|
||||
|
||||
useEffect(() => {
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (!hash) return;
|
||||
const scrollToHash = () => {
|
||||
const el = document.getElementById(hash);
|
||||
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
};
|
||||
const t = window.setTimeout(scrollToHash, 400);
|
||||
return () => window.clearTimeout(t);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const onMatcher = () => setMatcherOpen(true);
|
||||
window.addEventListener("open-matcher", onMatcher);
|
||||
|
||||
@ -122,7 +122,17 @@ export default function MeetupLiveClient({ meetupId, title }: { meetupId: string
|
||||
<iframe title="chat" src={session.chatUrl} className="live-iframe live-chat" />
|
||||
)}
|
||||
{!session.videoUrl && !session.chatUrl && (
|
||||
<p className="dest-empty">直播链接尚未配置</p>
|
||||
<div className="notif-empty">
|
||||
<p className="dest-empty">直播链接尚未配置</p>
|
||||
<div className="dating-empty-actions">
|
||||
<Link href="/meetups" className="btn btn-primary">
|
||||
{t.live.back}
|
||||
</Link>
|
||||
<Link href="/feedback" className="btn btn-ghost">
|
||||
{t.nav.feedback}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -29,6 +29,21 @@ export default function MeetupsHostClient() {
|
||||
|
||||
const set = (k: string, v: string | number) => setForm((f) => ({ ...f, [k]: v }));
|
||||
|
||||
if (!token) {
|
||||
return (
|
||||
<div className="meetups-page">
|
||||
<div className="container">
|
||||
<nav className="detail-nav"><Link href="/meetups">← {t.meetups.title}</Link></nav>
|
||||
<div className="dating-gate reveal">
|
||||
<h2>{t.meetups.hostTitle}</h2>
|
||||
<p>{t.meetups.loginDesc}</p>
|
||||
<Link href="/login?next=/meetups/host" className="btn btn-primary">{t.nav.login}</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (!token) {
|
||||
router.push("/login?next=/meetups/host");
|
||||
|
||||
@ -58,11 +58,15 @@ export default function Navbar() {
|
||||
{ href: "/community", label: t.nav.community, section: "community" },
|
||||
{ href: "/dating", label: t.nav.dating, section: "dating" },
|
||||
{ href: "/chat", label: t.nav.chat, section: "chat" },
|
||||
{ href: "/notifications", label: t.nav.notifications, section: "notifications" },
|
||||
{ href: "/join", label: t.nav.join, section: "join" },
|
||||
];
|
||||
|
||||
const grow: NavItem[] = [
|
||||
{ href: "/digital", label: t.nav.digital, section: "digital" },
|
||||
{ href: "/gigs", label: t.nav.gigs, section: "gigs" },
|
||||
{ href: "/services", label: t.nav.services, section: "services" },
|
||||
{ href: "/submit", label: t.submit.tag, section: "submit" },
|
||||
{ href: "/tools", label: t.nav.tools, section: "tools" },
|
||||
{ href: "/book", label: t.nav.ebook, section: "ebook" },
|
||||
];
|
||||
|
||||
@ -30,6 +30,10 @@ export default function ServicesClient({ services }: { services: ServiceItem[] }
|
||||
toast("请填写姓名、邮箱和需求说明", "info");
|
||||
return;
|
||||
}
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(f.email.trim())) {
|
||||
toast("请填写有效邮箱地址", "info");
|
||||
return;
|
||||
}
|
||||
setBusyId(serviceId);
|
||||
try {
|
||||
await api.submitServiceLead(serviceId, {
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import Navbar from "./Navbar";
|
||||
import ParticleCanvas from "./ParticleCanvas";
|
||||
import Footer from "./Footer";
|
||||
import GlobalSearch from "./GlobalSearch";
|
||||
|
||||
const KeyboardShortcuts = dynamic(() => import("./KeyboardShortcuts"), { ssr: false });
|
||||
|
||||
export default function SiteShell({ children, showFooter = false }: { children: React.ReactNode; showFooter?: boolean }) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) =>
|
||||
@ -35,6 +42,15 @@ export default function SiteShell({ children, showFooter = false }: { children:
|
||||
<ParticleCanvas />
|
||||
<Navbar />
|
||||
<GlobalSearch />
|
||||
<KeyboardShortcuts
|
||||
onOpenMatcher={() => {
|
||||
if (pathname === "/") {
|
||||
window.dispatchEvent(new Event("open-matcher"));
|
||||
} else {
|
||||
router.push("/next-stop");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
{showFooter && <Footer />}
|
||||
</>
|
||||
|
||||
@ -7,6 +7,14 @@ type LoginDict = {
|
||||
hintCompare: string;
|
||||
hintTools: string;
|
||||
hintDestination: string;
|
||||
hintDating: string;
|
||||
hintChat: string;
|
||||
hintMeetups: string;
|
||||
hintJoin: string;
|
||||
hintNotifications: string;
|
||||
hintGigs: string;
|
||||
hintCommunity: string;
|
||||
hintSubmit: string;
|
||||
redirectTo: string;
|
||||
planMergeNote: string;
|
||||
destHome: string;
|
||||
@ -15,6 +23,14 @@ type LoginDict = {
|
||||
destCompare: string;
|
||||
destTools: string;
|
||||
destDestination: string;
|
||||
destDating: string;
|
||||
destChat: string;
|
||||
destMeetups: string;
|
||||
destJoin: string;
|
||||
destNotifications: string;
|
||||
destGigs: string;
|
||||
destCommunity: string;
|
||||
destSubmit: string;
|
||||
submitLoginDefault: string;
|
||||
submitLoginPlan: string;
|
||||
demoHintDefault: string;
|
||||
@ -55,6 +71,14 @@ function destLabel(path: string, t: LoginDict): string {
|
||||
if (path === "/compare") return t.destCompare;
|
||||
if (path === "/tools") return t.destTools;
|
||||
if (path.startsWith("/destinations/")) return t.destDestination;
|
||||
if (path.startsWith("/dating")) return t.destDating;
|
||||
if (path.startsWith("/chat")) return t.destChat;
|
||||
if (path.startsWith("/meetups")) return t.destMeetups;
|
||||
if (path.startsWith("/join")) return t.destJoin;
|
||||
if (path.startsWith("/notifications")) return t.destNotifications;
|
||||
if (path.startsWith("/gigs")) return t.destGigs;
|
||||
if (path.startsWith("/community")) return t.destCommunity;
|
||||
if (path === "/submit") return t.destSubmit;
|
||||
return t.destProfile;
|
||||
}
|
||||
|
||||
@ -64,6 +88,14 @@ function hintFor(path: string, t: LoginDict): string {
|
||||
if (path === "/tools") return t.hintTools;
|
||||
if (path.startsWith("/destinations/")) return t.hintDestination;
|
||||
if (path === "/profile") return t.hintProfile;
|
||||
if (path.startsWith("/dating")) return t.hintDating;
|
||||
if (path.startsWith("/chat")) return t.hintChat;
|
||||
if (path.startsWith("/meetups")) return t.hintMeetups;
|
||||
if (path.startsWith("/join")) return t.hintJoin;
|
||||
if (path.startsWith("/notifications")) return t.hintNotifications;
|
||||
if (path.startsWith("/gigs")) return t.hintGigs;
|
||||
if (path.startsWith("/community")) return t.hintCommunity;
|
||||
if (path === "/submit") return t.hintSubmit;
|
||||
return t.hintExplore;
|
||||
}
|
||||
|
||||
|
||||
@ -249,6 +249,14 @@ export const zh = {
|
||||
hintCompare: "登录后继续你的城市对比",
|
||||
hintTools: "登录后使用需要账号的工具与服务",
|
||||
hintDestination: "登录后回到该城市页面,收藏与计划自动同步",
|
||||
hintDating: "登录后开始滑动匹配,成功可私信聊天",
|
||||
hintChat: "登录后查看与回复私信",
|
||||
hintMeetups: "登录后报名活动或发起聚会",
|
||||
hintJoin: "登录后开通或管理 VIP 会员",
|
||||
hintNotifications: "登录后查看通知与提醒",
|
||||
hintGigs: "登录后申请赏金或发布任务",
|
||||
hintCommunity: "登录后发帖与参与讨论",
|
||||
hintSubmit: "登录后提交内容投稿",
|
||||
redirectTo: "登录后将前往",
|
||||
planMergeNote: "检测到本机已有行程,登录后会自动与账号合并",
|
||||
destHome: "首页",
|
||||
@ -257,6 +265,14 @@ export const zh = {
|
||||
destCompare: "城市对比",
|
||||
destTools: "工具箱",
|
||||
destDestination: "城市详情",
|
||||
destDating: "游民匹配",
|
||||
destChat: "私信",
|
||||
destMeetups: "游民活动",
|
||||
destJoin: "会员中心",
|
||||
destNotifications: "通知中心",
|
||||
destGigs: "赏金任务",
|
||||
destCommunity: "社区讨论",
|
||||
destSubmit: "内容投稿",
|
||||
tabLogin: "登录",
|
||||
tabRegister: "注册",
|
||||
nickname: "昵称",
|
||||
@ -341,6 +357,7 @@ export const zh = {
|
||||
tag: "✉️ MESSAGES",
|
||||
title: "私信",
|
||||
loginTitle: "登录后查看私信",
|
||||
loginDesc: "匹配成功或活动结识的游民,在这里继续对话",
|
||||
empty: "还没有对话,去匹配页滑一张卡片吧",
|
||||
noMessages: "暂无消息",
|
||||
back: "返回列表",
|
||||
@ -476,6 +493,7 @@ export const zh = {
|
||||
hostSubmit: "发布活动",
|
||||
hostOk: "活动已发布",
|
||||
hostFail: "发布失败",
|
||||
loginDesc: "登录后即可创建线上或线下游民活动",
|
||||
empty: "暂无匹配的活动",
|
||||
},
|
||||
community: {
|
||||
@ -889,6 +907,14 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
|
||||
hintCompare: "Continue your city comparison after sign-in",
|
||||
hintTools: "Access tools and services that need an account",
|
||||
hintDestination: "Return to this city with favorites and plans in sync",
|
||||
hintDating: "Sign in to swipe matches and chat on mutual like",
|
||||
hintChat: "Sign in to read and reply to messages",
|
||||
hintMeetups: "Sign in to RSVP or host nomad events",
|
||||
hintJoin: "Sign in to manage or upgrade VIP membership",
|
||||
hintNotifications: "Sign in to view alerts and reminders",
|
||||
hintGigs: "Sign in to apply for gigs or post bounties",
|
||||
hintCommunity: "Sign in to post and join discussions",
|
||||
hintSubmit: "Sign in to submit content",
|
||||
redirectTo: "After sign-in you'll go to",
|
||||
planMergeNote: "Local trip data found — we'll merge it with your account",
|
||||
destHome: "Home",
|
||||
@ -897,6 +923,14 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
|
||||
destCompare: "Compare",
|
||||
destTools: "Toolkit",
|
||||
destDestination: "City page",
|
||||
destDating: "Nomad match",
|
||||
destChat: "Messages",
|
||||
destMeetups: "Events",
|
||||
destJoin: "Membership",
|
||||
destNotifications: "Notifications",
|
||||
destGigs: "Bounty gigs",
|
||||
destCommunity: "Community",
|
||||
destSubmit: "Content submit",
|
||||
tabLogin: "Log in",
|
||||
tabRegister: "Sign up",
|
||||
nickname: "Nickname",
|
||||
@ -981,6 +1015,7 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
|
||||
tag: "✉️ MESSAGES",
|
||||
title: "Messages",
|
||||
loginTitle: "Sign in to view messages",
|
||||
loginDesc: "Continue conversations with nomads you matched or met at events",
|
||||
empty: "No conversations yet — swipe on the match page",
|
||||
noMessages: "No messages yet",
|
||||
back: "Back to inbox",
|
||||
@ -1116,6 +1151,7 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
|
||||
hostSubmit: "Publish",
|
||||
hostOk: "Event published",
|
||||
hostFail: "Could not publish",
|
||||
loginDesc: "Sign in to host online or in-person nomad events",
|
||||
empty: "No events match this filter",
|
||||
},
|
||||
community: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user