s
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Link, useTranslation } from "@/app/digital/i18n/navigation";
|
||||
import type { ResourceData } from "@/app/digital/lib/theme-data";
|
||||
import { useCrossSiteUrls } from "@/app/digital/lib/useCrossSiteUrls";
|
||||
@@ -30,13 +29,19 @@ const linkIconNames: Record<string, IconName> = {
|
||||
unmannedLive: "video",
|
||||
};
|
||||
|
||||
const topicHrefs: Record<(typeof topicKeys)[number], string> = {
|
||||
indieDev: "/ebook",
|
||||
aiAgent: "/#tools",
|
||||
cloudPhone: "/ebook",
|
||||
unmannedLive: "/ebook",
|
||||
};
|
||||
|
||||
type CommunityProps = {
|
||||
resourceData?: ResourceData;
|
||||
};
|
||||
|
||||
export default function Community({ resourceData }: CommunityProps) {
|
||||
const { t } = useTranslation("community");
|
||||
const [toast, setToast] = useState(false);
|
||||
const { meetupUrl, vipUrl } = useCrossSiteUrls();
|
||||
const linkHrefs: Record<string, { href: string; newTab?: boolean }> = {
|
||||
destinations: { href: meetupUrl, newTab: true },
|
||||
@@ -44,11 +49,6 @@ export default function Community({ resourceData }: CommunityProps) {
|
||||
appDownload: { href: "/app" },
|
||||
};
|
||||
|
||||
const showUpdating = () => {
|
||||
setToast(true);
|
||||
setTimeout(() => setToast(false), 2000);
|
||||
};
|
||||
|
||||
const renderCardContent = (key: string) => (
|
||||
<>
|
||||
<span className="dn-icon-box flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-slate-50 text-xl dark:bg-slate-800">
|
||||
@@ -101,18 +101,17 @@ export default function Community({ resourceData }: CommunityProps) {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 4 个专题 - 小卡片,暂不可点击 */}
|
||||
{/* 4 个专题 */}
|
||||
<div className="mt-8">
|
||||
<h3 className="mb-4 text-center text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">
|
||||
{t("topicTag")}
|
||||
</h3>
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{topicKeys.map((key) => (
|
||||
<button
|
||||
<Link
|
||||
key={key}
|
||||
type="button"
|
||||
onClick={showUpdating}
|
||||
className="dn-card dn-muted-card flex cursor-not-allowed items-start gap-4 rounded-xl border border-slate-200 bg-slate-50/80 p-4 opacity-90 transition-opacity hover:opacity-100 dark:border-slate-700 dark:bg-slate-800/60"
|
||||
href={topicHrefs[key]}
|
||||
className="dn-card dn-muted-card flex items-start gap-4 rounded-xl border border-slate-200 bg-slate-50/80 p-4 opacity-90 transition-all hover:opacity-100 dark:border-slate-700 dark:bg-slate-800/60"
|
||||
>
|
||||
<span className="dn-icon-box flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-slate-200 text-lg dark:bg-slate-700">
|
||||
<DigitalThemeIcon
|
||||
@@ -124,24 +123,14 @@ export default function Community({ resourceData }: CommunityProps) {
|
||||
<div className="min-w-0 flex-1 text-left">
|
||||
<h4 className="font-bold text-slate-700 dark:text-slate-300">{t(`links.${key}.title`)}</h4>
|
||||
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">{t(`links.${key}.desc`)}</p>
|
||||
<span className="mt-1.5 inline-block text-xs font-medium text-amber-600 dark:text-amber-400">
|
||||
{t("updating")}
|
||||
<span className="dn-text-link mt-1.5 inline-block text-xs font-medium text-sky-600">
|
||||
{t(`links.${key}.label`)}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 更新中 Toast */}
|
||||
{toast && (
|
||||
<div
|
||||
className="fixed bottom-8 left-1/2 z-50 -translate-x-1/2 rounded-lg bg-slate-800 px-6 py-3 text-sm font-medium text-white shadow-lg dark:bg-slate-700"
|
||||
role="alert"
|
||||
>
|
||||
{t("updating")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -19,18 +19,18 @@ export default function Footer() {
|
||||
{
|
||||
titleKey: "resources" as const,
|
||||
links: [
|
||||
{ labelKey: "remoteJobs" as const, href: "#" },
|
||||
{ labelKey: "visa" as const, href: "#" },
|
||||
{ labelKey: "coliving" as const, href: "#" },
|
||||
{ labelKey: "insurance" as const, href: "#" },
|
||||
{ labelKey: "remoteJobs" as const, href: "/jobs", internal: true },
|
||||
{ labelKey: "visa" as const, href: "/services", internal: true },
|
||||
{ labelKey: "coliving" as const, href: "/services", internal: true },
|
||||
{ labelKey: "insurance" as const, href: "/services", internal: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
titleKey: "community" as const,
|
||||
links: [
|
||||
{ labelKey: "discord" as const, href: "#" },
|
||||
{ labelKey: "discord" as const, href: "/join", internal: true },
|
||||
{ labelKey: "wechat" as const, href: vipUrl, openInNewTab: true },
|
||||
{ labelKey: "jike" as const, href: "#" },
|
||||
{ labelKey: "jike" as const, href: "/discuss", internal: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -39,7 +39,7 @@ export default function Footer() {
|
||||
{ labelKey: "aboutUs" as const, href: "/about", internal: true },
|
||||
{ labelKey: "recruit" as const, href: "/jobs", internal: true },
|
||||
{ labelKey: "privacy" as const, href: "/privacy", internal: true, openInNewTab: true },
|
||||
{ labelKey: "contact" as const, href: "#" },
|
||||
{ labelKey: "contact" as const, href: "/contact", internal: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -49,12 +49,6 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
|
||||
const [activePanCategory, setActivePanCategory] = useState(firstCategoryId);
|
||||
const [booksPage, setBooksPage] = useState(0);
|
||||
const [productsPage, setProductsPage] = useState(0);
|
||||
const [toast, setToast] = useState(false);
|
||||
|
||||
const showUpdating = () => {
|
||||
setToast(true);
|
||||
setTimeout(() => setToast(false), 2000);
|
||||
};
|
||||
|
||||
const activeCategory = data.pan.categories.find((c) => c.id === activePanCategory);
|
||||
|
||||
@@ -92,11 +86,12 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
|
||||
className="grid grid-cols-2 gap-3 gap-y-4 sm:gap-4 md:grid-cols-8"
|
||||
>
|
||||
{paginatedBooks.map((book, i) => (
|
||||
<button
|
||||
<a
|
||||
key={`book-${booksPage}-${i}`}
|
||||
type="button"
|
||||
onClick={showUpdating}
|
||||
className="dn-resource-tile group flex cursor-not-allowed flex-col text-left"
|
||||
href={book.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer sponsored"
|
||||
className="dn-resource-tile group flex cursor-pointer flex-col text-left focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-slate-50 dark:focus:ring-offset-slate-950"
|
||||
>
|
||||
<div
|
||||
className="overflow-hidden rounded-lg border border-slate-200 bg-slate-100 shadow-sm transition-all group-hover:shadow-md dark:border-slate-700 dark:bg-slate-800"
|
||||
@@ -111,7 +106,7 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
|
||||
<p className="mt-2 line-clamp-2 text-center text-xs font-medium text-slate-700 dark:text-slate-300 sm:text-sm">
|
||||
{book.title}
|
||||
</p>
|
||||
</button>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
{booksTotalPages > 1 && (
|
||||
@@ -160,11 +155,12 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
|
||||
className="grid grid-cols-2 gap-3 gap-y-4 sm:gap-4 md:grid-cols-8"
|
||||
>
|
||||
{paginatedProducts.map((product, i) => (
|
||||
<button
|
||||
<a
|
||||
key={`product-${productsPage}-${i}`}
|
||||
type="button"
|
||||
onClick={showUpdating}
|
||||
className="dn-resource-tile group flex cursor-not-allowed flex-col text-left"
|
||||
href={product.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer sponsored"
|
||||
className="dn-resource-tile group flex cursor-pointer flex-col text-left focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-slate-50 dark:focus:ring-offset-slate-950"
|
||||
>
|
||||
<div
|
||||
className="overflow-hidden rounded-xl border border-slate-200 bg-white p-3 shadow-sm transition-all group-hover:shadow-md dark:border-slate-700 dark:bg-slate-900"
|
||||
@@ -179,7 +175,7 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
|
||||
<p className="mt-2 line-clamp-2 text-center text-xs font-medium text-slate-700 dark:text-slate-300 sm:text-sm">
|
||||
{product.name}
|
||||
</p>
|
||||
</button>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
{productsTotalPages > 1 && (
|
||||
@@ -277,14 +273,6 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{toast && (
|
||||
<div
|
||||
className="fixed bottom-8 left-1/2 z-50 -translate-x-1/2 rounded-lg bg-slate-800 px-6 py-3 text-sm font-medium text-white shadow-lg dark:bg-slate-700"
|
||||
role="alert"
|
||||
>
|
||||
{t("updating")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* 与 join 报名支付分离,课程解锁用 VIP 状态
|
||||
*/
|
||||
|
||||
/** 前 2 章试看(暂时关闭,全部需 VIP 解锁) */
|
||||
export function isFreeTrial(_moduleIndex: number, _lessonIndex: number): boolean {
|
||||
return false;
|
||||
/** 第一模块前 2 节可试看,其余章节需 VIP 解锁 */
|
||||
export function isFreeTrial(moduleIndex: number, lessonIndex: number): boolean {
|
||||
return moduleIndex === 0 && lessonIndex < 2;
|
||||
}
|
||||
|
||||
/** 章节是否可观看(需传入 vip 状态,来自 useAuthAndVip 或 /api/digital/vip/check) */
|
||||
|
||||
Reference in New Issue
Block a user