From 7671ad8e559123bb982b2fa87d359ef491a5104d Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Thu, 9 Apr 2026 21:52:16 +0200 Subject: [PATCH] feat: improve Help Center UI with mixed dark/light theme and structured content - Updated Help Center with dark hero and light content sections - Added ArticleContent component for rendering structured content blocks - Updated seed data with detailed articles matching admin KB categories - Fixed article alignment and spacing issues - Uses ContentBlock[] instead of HTML strings for type-safe content --- src/components/ArticleContent.tsx | 137 +++ .../dashboard/HelpCenterDashboardPage.tsx | 206 +++- src/data/help-center-seed.ts | 1017 +++++++++++------ src/lib/help-center.ts | 93 +- src/routes/help-center/article/[slug].tsx | 551 +++++++-- src/routes/help-center/index.tsx | 384 ++++++- vite.config.timestamp_1775706024731.js | 11 + 7 files changed, 1875 insertions(+), 524 deletions(-) create mode 100644 src/components/ArticleContent.tsx create mode 100644 vite.config.timestamp_1775706024731.js diff --git a/src/components/ArticleContent.tsx b/src/components/ArticleContent.tsx new file mode 100644 index 0000000..4541d69 --- /dev/null +++ b/src/components/ArticleContent.tsx @@ -0,0 +1,137 @@ +import { For, Show } from "solid-js"; +import type { ContentBlock } from "~/data/help-center-seed"; + +interface ArticleContentProps { + blocks: ContentBlock[]; +} + +export default function ArticleContent(props: ArticleContentProps) { + const renderBlock = (block: ContentBlock) => { + switch (block.type) { + case "paragraph": + return ( +

+ {block.text} +

+ ); + + case "heading": + if (block.level === 2) { + return ( +

+ {block.text} +

+ ); + } + return ( +

+ {block.text} +

+ ); + + case "list": + if (block.ordered) { + return ( +
    + + {(item) => ( +
  1. + {item.split(":")[0]}: + {item.split(":")[1] || ""} +
  2. + )} +
    +
+ ); + } + return ( + + ); + + case "section": + return ( +
+

+ {block.title} +

+ {(subBlock) => renderBlock(subBlock)} +
+ ); + + default: + return null; + } + }; + + return ( +
+ {(block) => renderBlock(block)} +
+ ); +} diff --git a/src/components/dashboard/HelpCenterDashboardPage.tsx b/src/components/dashboard/HelpCenterDashboardPage.tsx index 585cfb3..64f038f 100644 --- a/src/components/dashboard/HelpCenterDashboardPage.tsx +++ b/src/components/dashboard/HelpCenterDashboardPage.tsx @@ -1,30 +1,37 @@ -import { For, Show, createMemo, createSignal, onMount } from 'solid-js'; -import { BTN_GHOST, CARD, INPUT } from '~/components/DashboardShell'; -import { type RoleKey } from './RoleDashboardShared'; +import { For, Show, createMemo, createSignal, onMount } from "solid-js"; +import { BTN_GHOST, CARD, INPUT } from "~/components/DashboardShell"; +import { type RoleKey } from "./RoleDashboardShared"; -const API = '/api/gateway'; +const API = "/api/gateway"; type Props = { roleKey: RoleKey }; type Category = { id: string; name: string; slug: string; description?: string }; -type Article = { id: string; title: string; slug: string; summary?: string; category?: string; updatedAt?: string }; +type Article = { + id: string; + title: string; + slug: string; + summary?: string; + category?: string; + updatedAt?: string; +}; async function apiFetch(path: string, opts?: RequestInit) { return fetch(`${API}${path}`, { ...opts, - credentials: 'include', - headers: { 'Content-Type': 'application/json', ...(opts?.headers ?? {}) }, + credentials: "include", + headers: { "Content-Type": "application/json", ...(opts?.headers ?? {}) }, }); } function normalizeArticle(raw: any): Article { return { - id: String(raw?.id || ''), - title: String(raw?.title || ''), - slug: String(raw?.slug || ''), - summary: raw?.summary ? String(raw.summary) : '', - category: String(raw?.category || raw?.category_name || ''), - updatedAt: String(raw?.updatedAt || raw?.updated_at || ''), + id: String(raw?.id || ""), + title: String(raw?.title || ""), + slug: String(raw?.slug || ""), + summary: raw?.summary ? String(raw.summary) : "", + category: String(raw?.category || raw?.category_name || ""), + updatedAt: String(raw?.updatedAt || raw?.updated_at || ""), }; } @@ -32,15 +39,15 @@ export default function HelpCenterDashboardPage(props: Props) { const [categories, setCategories] = createSignal([]); const [articles, setArticles] = createSignal([]); const [loading, setLoading] = createSignal(true); - const [search, setSearch] = createSignal(''); - const [err, setErr] = createSignal(''); + const [search, setSearch] = createSignal(""); + const [err, setErr] = createSignal(""); const loadData = async () => { setLoading(true); - setErr(''); + setErr(""); try { const [catRes, artRes] = await Promise.all([ - apiFetch('/api/kb/categories'), + apiFetch("/api/kb/categories"), apiFetch(`/api/kb/articles?role=${encodeURIComponent(props.roleKey)}&page=1&limit=200`), ]); const catJson = await catRes.json().catch(() => ({})); @@ -61,12 +68,16 @@ export default function HelpCenterDashboardPage(props: Props) { : Array.isArray(artJson) ? artJson : []; - setArticles(rawArticles.map(normalizeArticle).filter((a: Article) => Boolean(a.id && a.slug && a.title))); + setArticles( + rawArticles + .map(normalizeArticle) + .filter((a: Article) => Boolean(a.id && a.slug && a.title)) + ); } - if (!catRes.ok && !artRes.ok) setErr('Failed to load help center resources.'); + if (!catRes.ok && !artRes.ok) setErr("Failed to load help center resources."); } catch { - setErr('Network error while loading help center.'); + setErr("Network error while loading help center."); } finally { setLoading(false); } @@ -77,42 +88,108 @@ export default function HelpCenterDashboardPage(props: Props) { const filtered = createMemo(() => { const q = search().trim().toLowerCase(); if (!q) return articles(); - return articles().filter((a) => - String(a.title || '').toLowerCase().includes(q) - || String(a.summary || '').toLowerCase().includes(q) - || String(a.category || '').toLowerCase().includes(q)); + return articles().filter( + (a) => + String(a.title || "") + .toLowerCase() + .includes(q) || + String(a.summary || "") + .toLowerCase() + .includes(q) || + String(a.category || "") + .toLowerCase() + .includes(q) + ); }); return ( -
+
-

Help Center

-

Find guides and articles for your role.

+

+ Help Center +

+

+ Find guides and articles for your role. +

-
{err()}
+
+ {err()} +
-
-
- setSearch(e.currentTarget.value)} style={INPUT} placeholder="Search help articles" /> - +
+
+ setSearch(e.currentTarget.value)} + style={INPUT} + placeholder="Search help articles" + /> +
-

Loading help center...

+

+ Loading help center... +

0}>
-

Categories

-
+

+ Categories +

+
{(cat) => ( -
-

{cat.name}

-

{cat.description || 'Knowledge base category'}

+
+

+ {cat.name} +

+

+ {cat.description || "Knowledge base category"} +

)} @@ -121,17 +198,58 @@ export default function HelpCenterDashboardPage(props: Props) {
-

Articles

+

+ Articles +

-

No articles found.

+

No articles found.

0}> -
+
{(a) => ( - -

{a.title}

-

{a.summary || 'Open article'}

+
+

+ {a.title} +

+

+ {a.summary || "Open article"} +

)}
diff --git a/src/data/help-center-seed.ts b/src/data/help-center-seed.ts index a6c0306..80ad43f 100644 --- a/src/data/help-center-seed.ts +++ b/src/data/help-center-seed.ts @@ -4,6 +4,12 @@ export type SeedHelpCategory = { title: string; }; +export type ContentBlock = + | { type: "paragraph"; text: string } + | { type: "heading"; level: 2 | 3; text: string } + | { type: "list"; items: string[]; ordered?: boolean } + | { type: "section"; title: string; blocks: ContentBlock[] }; + export type SeedHelpArticle = { id: string; slug: string; @@ -11,385 +17,706 @@ export type SeedHelpArticle = { summary: string; categoryKey: string; category: string; - role: 'ALL' | 'company' | 'jobSeeker' | 'professional' | 'customer' | 'platform'; + role: "ALL" | "company" | "jobSeeker" | "professional" | "customer" | "platform"; tags: string[]; updatedAt: string; - content: string; + content: ContentBlock[]; }; +// Categories matching the admin panel KB management export const HELP_CENTER_SEED_CATEGORIES: SeedHelpCategory[] = [ - { id: 'seed-cat-getting-started', key: 'getting-started', title: 'Getting Started' }, - { id: 'seed-cat-account-login', key: 'account-login', title: 'Account & Login' }, - { id: 'seed-cat-profile-verification', key: 'profile-verification', title: 'Profile & Verification' }, - { id: 'seed-cat-platform-basics', key: 'platform-basics', title: 'Platform Basics' }, - { id: 'seed-cat-jobs-applications', key: 'jobs-applications', title: 'Jobs & Applications' }, - { id: 'seed-cat-leads-requests', key: 'leads-requests', title: 'Leads & Requests' }, - { id: 'seed-cat-tracecoins-billing', key: 'tracecoins-billing', title: 'TraceCoins & Billing' }, - { id: 'seed-cat-privacy-safety', key: 'privacy-safety', title: 'Privacy & Safety' }, - { id: 'seed-cat-troubleshooting', key: 'troubleshooting', title: 'Troubleshooting' }, + { id: "seed-cat-verifications", key: "verifications", title: "Verifications" }, + { id: "seed-cat-lead-system", key: "lead-system", title: "Lead System" }, + { id: "seed-cat-credits-payments", key: "credits-payments", title: "Credits & Payments" }, + { id: "seed-cat-account-profiles", key: "account-profiles", title: "Account & Profiles" }, + { id: "seed-cat-security", key: "security", title: "Security" }, + { id: "seed-cat-troubleshooting", key: "troubleshooting", title: "Troubleshooting" }, + { id: "seed-cat-policies", key: "policies", title: "Policies" }, + { id: "seed-cat-general", key: "general", title: "General" }, ]; -const updatedAt = '2026-04-06T00:00:00.000Z'; +const updatedAt = "2026-04-06T00:00:00.000Z"; + +// ========== VERIFICATIONS ========== +const businessApprovalContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Overview" }, + { + type: "paragraph", + text: "Business approval is required for companies and professional service providers to verify their legitimacy on the Nxtgauge platform.", + }, + { type: "heading", level: 2, text: "Required Documents" }, + { + type: "list", + items: [ + "Business registration certificate", + "Tax identification number", + "Proof of address (utility bill or lease agreement)", + "Identity verification of business owner/director", + ], + }, + { type: "heading", level: 2, text: "Approval Timeline" }, + { + type: "paragraph", + text: "Business verification typically takes 24-48 hours. Complex cases may require up to 5 business days.", + }, + { type: "heading", level: 2, text: "What Happens After Approval" }, + { + type: "list", + items: [ + "Verified badge appears on your profile", + "Full platform features unlocked", + "Higher visibility in search results", + "Ability to post jobs and receive leads", + ], + }, +]; + +const identityVerificationContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Why Identity Verification Matters" }, + { + type: "paragraph", + text: "Identity verification helps maintain trust and safety on the Nxtgauge platform for all users.", + }, + { type: "heading", level: 2, text: "Acceptable Documents" }, + { + type: "section", + title: "Primary ID", + blocks: [ + { + type: "list", + items: ["Passport", "Driver license", "National ID card", "Government-issued photo ID"], + }, + ], + }, + { + type: "section", + title: "Address Proof", + blocks: [ + { + type: "list", + items: [ + "Bank statement (last 3 months)", + "Utility bill", + "Rental agreement", + "Government correspondence", + ], + }, + ], + }, + { type: "heading", level: 2, text: "Photo Requirements" }, + { + type: "list", + items: [ + "Clear, high-resolution image", + "All corners visible", + "No glare or shadows", + "Valid (not expired) document", + ], + }, +]; + +// ========== LEAD SYSTEM ========== +const howToFindLeadsContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Understanding the Lead System" }, + { + type: "paragraph", + text: "Leads are potential customers who have expressed interest in services matching your expertise.", + }, + { type: "heading", level: 2, text: "Finding Leads" }, + { + type: "list", + items: [ + "Check your Leads dashboard daily", + "Set up lead alerts for your service categories", + "Use filters to find high-quality matches", + "Respond quickly to new leads", + ], + ordered: true, + }, + { type: "heading", level: 2, text: "Lead Quality Indicators" }, + { + type: "list", + items: [ + "Budget range specified", + "Detailed requirements provided", + "Timeline clearly defined", + "Previous platform activity", + ], + }, +]; + +const responseQualityContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Crafting Effective Responses" }, + { + type: "paragraph", + text: "Your response quality directly impacts your conversion rate and reputation on the platform.", + }, + { type: "heading", level: 2, text: "Best Practices" }, + { + type: "list", + items: [ + "Personalize each response to the specific requirement", + "Highlight relevant experience and portfolio items", + "Provide clear pricing or rate information", + "Ask clarifying questions when needed", + "Maintain professional and courteous tone", + ], + }, + { type: "heading", level: 2, text: "Response Timing" }, + { + type: "paragraph", + text: "Responding within the first hour increases your chances of conversion by 70%.", + }, +]; + +// ========== CREDITS & PAYMENTS ========== +const whatAreTracecoinsContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "What Are Tracecoins" }, + { + type: "paragraph", + text: "Tracecoins are Nxtgauge platform credits used to unlock premium features and contact leads.", + }, + { type: "heading", level: 2, text: "How to Use Tracecoins" }, + { + type: "list", + items: [ + "Unlock lead contact details", + "Boost profile visibility", + "Access premium job postings", + "Feature your services", + ], + }, + { type: "heading", level: 2, text: "Purchasing Tracecoins" }, + { + type: "paragraph", + text: "Buy Tracecoins in bundles through the Credits & Billing section. Various payment methods accepted.", + }, + { type: "heading", level: 2, text: "Do Tracecoins Expire" }, + { type: "paragraph", text: "Tracecoins remain valid for 12 months from the date of purchase." }, +]; + +const refundPolicyContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Refund Policy Overview" }, + { + type: "paragraph", + text: "Nxtgauge offers refunds under specific circumstances to ensure fair usage of credits.", + }, + { type: "heading", level: 2, text: "Eligible for Refund" }, + { + type: "list", + items: [ + "Technical errors preventing lead contact", + "Fraudulent lead submissions", + "Duplicate charges", + "Unused credits within 7 days of purchase", + ], + }, + { type: "heading", level: 2, text: "Not Eligible for Refund" }, + { + type: "list", + items: [ + "Lead did not respond to your message", + "Client chose another provider", + "Change of mind after credit usage", + "Credits expired after 12 months", + ], + }, + { type: "heading", level: 2, text: "How to Request a Refund" }, + { + type: "paragraph", + text: "Submit a refund request through the support ticket system with transaction details and reason.", + }, +]; + +// ========== ACCOUNT & PROFILES ========== +const portfolioBestPracticesContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Building a Strong Portfolio" }, + { + type: "paragraph", + text: "Your portfolio showcases your skills and helps potential clients understand your capabilities.", + }, + { type: "heading", level: 2, text: "Portfolio Essentials" }, + { + type: "list", + items: [ + "High-quality images of completed work", + "Detailed project descriptions", + "Client testimonials and reviews", + "Clear categorization by service type", + "Regular updates with recent work", + ], + }, + { type: "heading", level: 2, text: "Image Guidelines" }, + { + type: "list", + items: [ + "Minimum resolution: 1200x800 pixels", + "Professional lighting and composition", + "Before/after comparisons where applicable", + "Consistent style across portfolio", + ], + }, +]; + +const serviceSwitchingContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Switching Between Services" }, + { + type: "paragraph", + text: "Nxtgauge allows professionals to offer multiple services under one account.", + }, + { type: "heading", level: 2, text: "How to Add Services" }, + { + type: "list", + items: [ + "Go to Profile Settings", + "Select Service Categories", + "Add new service areas", + "Complete verification for each service", + "Update portfolio for new services", + ], + ordered: true, + }, + { type: "heading", level: 2, text: "Managing Multiple Services" }, + { + type: "paragraph", + text: "Each service has its own leads, reviews, and portfolio. Keep all services updated for best results.", + }, +]; + +// ========== SECURITY ========== +const passwordRecoveryContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Resetting Your Password" }, + { + type: "paragraph", + text: "Forgot your password? Follow these steps to regain access to your account.", + }, + { type: "heading", level: 2, text: "Password Reset Steps" }, + { + type: "list", + items: [ + "Click Forgot Password on login screen", + "Enter your registered email or phone", + "Receive OTP via email or SMS", + "Enter OTP and create new password", + "Log in with new credentials", + ], + ordered: true, + }, + { type: "heading", level: 2, text: "Password Requirements" }, + { + type: "list", + items: [ + "Minimum 8 characters", + "At least one uppercase letter", + "At least one number", + "At least one special character", + ], + }, +]; + +const twoFactorAuthContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Two-Factor Authentication (2FA)" }, + { type: "paragraph", text: "Add an extra layer of security to your Nxtgauge account with 2FA." }, + { type: "heading", level: 2, text: "Setting Up 2FA" }, + { + type: "list", + items: [ + "Go to Security Settings", + "Enable Two-Factor Authentication", + "Choose SMS or Authenticator App", + "Verify your device", + "Save backup codes securely", + ], + ordered: true, + }, + { type: "heading", level: 2, text: "Using 2FA" }, + { + type: "paragraph", + text: "After entering your password, you will receive a code via your chosen method. Enter this code to complete login.", + }, +]; + +// ========== TROUBLESHOOTING ========== +const appPerformanceContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Improving App Performance" }, + { + type: "paragraph", + text: "Experiencing slow loading or crashes? Try these troubleshooting steps.", + }, + { type: "heading", level: 2, text: "Quick Fixes" }, + { + type: "list", + items: [ + "Clear browser cache and cookies", + "Update to latest browser version", + "Disable unnecessary browser extensions", + "Check internet connection stability", + "Try incognito/private browsing mode", + ], + }, + { type: "heading", level: 2, text: "Still Having Issues" }, + { + type: "paragraph", + text: "Contact support with details about your browser, device, and the specific issue you are experiencing.", + }, +]; + +const notificationSettingsContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Managing Notifications" }, + { type: "paragraph", text: "Customize how and when you receive alerts from Nxtgauge." }, + { type: "heading", level: 2, text: "Notification Types" }, + { + type: "list", + items: [ + "New lead alerts", + "Message notifications", + "Application status updates", + "Platform announcements", + "Payment confirmations", + ], + }, + { type: "heading", level: 2, text: "Delivery Methods" }, + { + type: "list", + items: [ + "Email notifications", + "SMS alerts", + "In-app notifications", + "Push notifications (mobile app)", + ], + }, +]; + +// ========== POLICIES ========== +const termsOfServiceContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Terms of Service Overview" }, + { + type: "paragraph", + text: "By using Nxtgauge, you agree to comply with our terms of service designed to ensure a safe and fair platform.", + }, + { type: "heading", level: 2, text: "Key Points" }, + { + type: "list", + items: [ + "Provide accurate and truthful information", + "Respect intellectual property rights", + "Do not engage in fraudulent activities", + "Maintain professional conduct in all interactions", + "Comply with applicable laws and regulations", + ], + }, + { type: "heading", level: 2, text: "Account Responsibilities" }, + { + type: "paragraph", + text: "You are responsible for all activity on your account. Keep your credentials secure and report unauthorized access immediately.", + }, +]; + +const privacyPolicyContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Privacy Policy" }, + { + type: "paragraph", + text: "Nxtgauge is committed to protecting your privacy and personal data.", + }, + { type: "heading", level: 2, text: "Data We Collect" }, + { + type: "list", + items: [ + "Account registration information", + "Profile and service details", + "Communication records", + "Transaction history", + "Usage analytics", + ], + }, + { type: "heading", level: 2, text: "How We Use Your Data" }, + { + type: "list", + items: [ + "Provide and improve platform services", + "Match you with relevant opportunities", + "Process payments and transactions", + "Ensure platform security", + "Communicate important updates", + ], + }, + { type: "heading", level: 2, text: "Your Rights" }, + { + type: "paragraph", + text: "You have the right to access, modify, or delete your personal data. Contact support for data-related requests.", + }, +]; + +// ========== GENERAL ========== +const welcomeGuideContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Welcome to Nxtgauge" }, + { + type: "paragraph", + text: "Your journey to finding the right opportunities and connections starts here.", + }, + { type: "heading", level: 2, text: "Getting Started" }, + { + type: "list", + items: [ + "Complete your profile with accurate information", + "Verify your account for full access", + "Set up your service categories or job preferences", + "Upload portfolio or resume", + "Start exploring leads or job postings", + ], + ordered: true, + }, + { type: "heading", level: 2, text: "Need Help" }, + { type: "paragraph", text: "Browse our help center articles or contact support for assistance." }, +]; + +const masteringDashboardContent: ContentBlock[] = [ + { type: "heading", level: 2, text: "Understanding Your Dashboard" }, + { + type: "paragraph", + text: "Your dashboard is the central hub for managing your Nxtgauge activities.", + }, + { type: "heading", level: 2, text: "Dashboard Sections" }, + { + type: "section", + title: "Overview", + blocks: [ + { + type: "paragraph", + text: "Quick summary of your account activity, new leads, and pending actions.", + }, + ], + }, + { + type: "section", + title: "Leads/Applications", + blocks: [ + { + type: "paragraph", + text: "View and manage your leads or job applications with status tracking.", + }, + ], + }, + { + type: "section", + title: "Messages", + blocks: [ + { type: "paragraph", text: "Communicate with clients, employers, or service providers." }, + ], + }, + { + type: "section", + title: "Profile", + blocks: [ + { type: "paragraph", text: "Update your information, portfolio, and service settings." }, + ], + }, + { type: "heading", level: 2, text: "Customization" }, + { + type: "paragraph", + text: "Personalize your dashboard layout and notification preferences for optimal workflow.", + }, +]; export const HELP_CENTER_SEED_ARTICLES: SeedHelpArticle[] = [ + // Verifications { - id: 'seed-1', - slug: 'create-account-3-steps', - title: 'Create your Nxtgauge account in 3 steps', - summary: 'Quick steps to sign up, verify, and get ready for your role journey.', - categoryKey: 'getting-started', - category: 'Getting Started', - role: 'ALL', - tags: ['account', 'signup', 'getting started'], + id: "seed-verif-1", + slug: "business-approval", + title: "Business Approval", + summary: "Guide to getting your business verified on Nxtgauge.", + categoryKey: "verifications", + category: "Verifications", + role: "ALL", + tags: ["business", "verification", "approval"], updatedAt, - content: 'Step 1: Sign up with your email or mobile number.\n\nStep 2: Verify OTP and complete basic account details.\n\nStep 3: Continue to role setup and complete your profile.', + content: businessApprovalContent, }, { - id: 'seed-2', - slug: 'choose-role-after-signup', - title: 'Choose your role after signup', - summary: 'You can create one account first and activate the role path later.', - categoryKey: 'getting-started', - category: 'Getting Started', - role: 'ALL', - tags: ['role', 'signup'], + id: "seed-verif-2", + slug: "identity-verification", + title: "Identity Verification", + summary: "How to verify your identity with acceptable documents.", + categoryKey: "verifications", + category: "Verifications", + role: "ALL", + tags: ["identity", "verification", "documents"], updatedAt, - content: 'Nxtgauge supports one account with role-based journeys.\n\nAfter signup, choose the role flow you need: company, professional, job seeker, or customer.', + content: identityVerificationContent, + }, + // Lead System + { + id: "seed-lead-1", + slug: "how-to-find-leads", + title: "How to Find Leads", + summary: "Discover potential clients and opportunities on the platform.", + categoryKey: "lead-system", + category: "Lead System", + role: "professional", + tags: ["leads", "opportunities", "finding"], + updatedAt, + content: howToFindLeadsContent, }, { - id: 'seed-3', - slug: 'understand-account-statuses', - title: 'Understand account statuses: Draft, Review, Approved', - summary: 'Learn what each status means and what to do next.', - categoryKey: 'getting-started', - category: 'Getting Started', - role: 'ALL', - tags: ['status', 'approval'], + id: "seed-lead-2", + slug: "response-quality", + title: "Response Quality", + summary: "Best practices for responding to leads effectively.", + categoryKey: "lead-system", + category: "Lead System", + role: "professional", + tags: ["response", "leads", "communication"], updatedAt, - content: 'Draft means incomplete.\n\nReview means your submission is being checked.\n\nApproved means your profile or listing is visible in discovery.', + content: responseQualityContent, + }, + // Credits & Payments + { + id: "seed-pay-1", + slug: "what-are-tracecoins", + title: "What are Tracecoins", + summary: "Understanding platform credits and how to use them.", + categoryKey: "credits-payments", + category: "Credits & Payments", + role: "ALL", + tags: ["credits", "tracecoins", "payments"], + updatedAt, + content: whatAreTracecoinsContent, }, { - id: 'seed-4', - slug: 'how-nxtgauge-platform-works', - title: 'How the Nxtgauge platform works', - summary: 'A trust-led submission, review, approval, and discovery workflow.', - categoryKey: 'platform-basics', - category: 'Platform Basics', - role: 'platform', - tags: ['workflow', 'trust'], + id: "seed-pay-2", + slug: "refund-policy", + title: "Refund Policy", + summary: "When and how you can request refunds for credits.", + categoryKey: "credits-payments", + category: "Credits & Payments", + role: "ALL", + tags: ["refund", "policy", "credits"], updatedAt, - content: 'Users submit profiles, requirements, or jobs.\n\nNxtgauge verifies submissions.\n\nApproved records become visible for discovery and matching.', + content: refundPolicyContent, + }, + // Account & Profiles + { + id: "seed-acc-1", + slug: "portfolio-best-practices", + title: "Portfolio Best Practices", + summary: "Build a compelling portfolio to attract clients.", + categoryKey: "account-profiles", + category: "Account & Profiles", + role: "professional", + tags: ["portfolio", "profile", "best practices"], + updatedAt, + content: portfolioBestPracticesContent, }, { - id: 'seed-5', - slug: 'one-account-multiple-journeys', - title: 'One account, multiple journeys explained', - summary: 'Use one account and activate the journey you need.', - categoryKey: 'platform-basics', - category: 'Platform Basics', - role: 'platform', - tags: ['account', 'roles'], + id: "seed-acc-2", + slug: "service-switching", + title: "Service Switching", + summary: "How to add and manage multiple services.", + categoryKey: "account-profiles", + category: "Account & Profiles", + role: "professional", + tags: ["services", "switching", "management"], updatedAt, - content: 'You do not need separate accounts for each role.\n\nKeep each role profile complete for better matching quality.', + content: serviceSwitchingContent, + }, + // Security + { + id: "seed-sec-1", + slug: "password-recovery", + title: "Password Recovery", + summary: "Reset your password and regain account access.", + categoryKey: "security", + category: "Security", + role: "ALL", + tags: ["password", "recovery", "login"], + updatedAt, + content: passwordRecoveryContent, }, { - id: 'seed-6', - slug: 'review-approval-improve-trust', - title: 'How review and approval improve trust', - summary: 'Approval checkpoints reduce noise and increase confidence.', - categoryKey: 'platform-basics', - category: 'Platform Basics', - role: 'platform', - tags: ['review', 'approval', 'trust'], + id: "seed-sec-2", + slug: "2fa-setup", + title: "2FA Setup", + summary: "Enable two-factor authentication for extra security.", + categoryKey: "security", + category: "Security", + role: "ALL", + tags: ["2fa", "security", "authentication"], updatedAt, - content: 'Review and approval reduce low-quality submissions.\n\nThis creates cleaner discovery and better first-contact confidence.', + content: twoFactorAuthContent, + }, + // Troubleshooting + { + id: "seed-trouble-1", + slug: "app-performance", + title: "App Performance", + summary: "Fix slow loading and performance issues.", + categoryKey: "troubleshooting", + category: "Troubleshooting", + role: "ALL", + tags: ["performance", "issues", "fix"], + updatedAt, + content: appPerformanceContent, }, { - id: 'seed-7', - slug: 'track-progress-without-confusion', - title: 'How to track progress without confusion', - summary: 'Use clear status labels and next actions from your dashboard.', - categoryKey: 'platform-basics', - category: 'Platform Basics', - role: 'ALL', - tags: ['tracking', 'status'], + id: "seed-trouble-2", + slug: "notification-settings", + title: "Notification Settings", + summary: "Manage your notification preferences.", + categoryKey: "troubleshooting", + category: "Troubleshooting", + role: "ALL", + tags: ["notifications", "settings", "alerts"], updatedAt, - content: 'Check status labels daily.\n\nRespond quickly to changes.\n\nFollow the next action shown in your panel.', + content: notificationSettingsContent, + }, + // Policies + { + id: "seed-policy-1", + slug: "terms-of-service", + title: "Terms of Service", + summary: "Platform terms and conditions all users must follow.", + categoryKey: "policies", + category: "Policies", + role: "ALL", + tags: ["terms", "service", "policy"], + updatedAt, + content: termsOfServiceContent, }, { - id: 'seed-8', - slug: 'platform-terms-simple-words', - title: 'Common platform terms in simple words', - summary: 'A quick glossary for Draft, Review, Approved, Requirement, and Lead.', - categoryKey: 'platform-basics', - category: 'Platform Basics', - role: 'ALL', - tags: ['glossary', 'terms'], + id: "seed-policy-2", + slug: "privacy-policy", + title: "Privacy Policy", + summary: "How we collect, use, and protect your data.", + categoryKey: "policies", + category: "Policies", + role: "ALL", + tags: ["privacy", "data", "policy"], updatedAt, - content: 'Draft: incomplete.\n\nReview: in verification.\n\nApproved: visible in platform.\n\nRequirement: customer need.\n\nLead: potential opportunity.', + content: privacyPolicyContent, + }, + // General + { + id: "seed-gen-1", + slug: "welcome-guide", + title: "Welcome Guide", + summary: "Get started with Nxtgauge platform.", + categoryKey: "general", + category: "General", + role: "ALL", + tags: ["welcome", "getting started", "guide"], + updatedAt, + content: welcomeGuideContent, }, { - id: 'seed-9', - slug: 'get-better-platform-visibility', - title: 'How to get better platform visibility', - summary: 'Practical profile improvements that increase discovery quality.', - categoryKey: 'platform-basics', - category: 'Platform Basics', - role: 'ALL', - tags: ['visibility', 'profile'], + id: "seed-gen-2", + slug: "mastering-dashboard", + title: "Mastering the Dashboard", + summary: "Navigate and customize your dashboard effectively.", + categoryKey: "general", + category: "General", + role: "ALL", + tags: ["dashboard", "navigation", "guide"], updatedAt, - content: 'Complete profile fields, clarify scope, and keep proof updated.\n\nClear profiles usually perform better in discovery.', - }, - { - id: 'seed-10', - slug: 'reset-password', - title: 'How to reset your password', - summary: 'Use the forgot password flow and verify securely with OTP.', - categoryKey: 'account-login', - category: 'Account & Login', - role: 'ALL', - tags: ['password', 'login'], - updatedAt, - content: 'Use Forgot Password on the login screen.\n\nVerify OTP and set a new secure password.', - }, - { - id: 'seed-11', - slug: 'otp-failure-reasons', - title: 'Why your login OTP might fail', - summary: 'Common OTP failures and quick retry guidance.', - categoryKey: 'account-login', - category: 'Account & Login', - role: 'ALL', - tags: ['otp', 'login'], - updatedAt, - content: 'OTP can fail due to expiry, wrong input, or network delays.\n\nRequest a fresh OTP and retry.', - }, - { - id: 'seed-12', - slug: 'update-email-phone', - title: 'Update email or phone on your account', - summary: 'How to request secure account contact updates.', - categoryKey: 'account-login', - category: 'Account & Login', - role: 'ALL', - tags: ['account', 'security'], - updatedAt, - content: 'Go to account settings and submit an update request.\n\nSome changes may require re-verification.', - }, - { - id: 'seed-13', - slug: 'high-conversion-profile', - title: 'How to complete a high-conversion profile', - summary: 'Role clarity, scope, portfolio proof, and pricing structure tips.', - categoryKey: 'profile-verification', - category: 'Profile & Verification', - role: 'professional', - tags: ['profile', 'conversion'], - updatedAt, - content: 'Add clear role scope, location, portfolio proof, and service details.\n\nClear profiles get better-fit responses.', - }, - { - id: 'seed-14', - slug: 'verification-checklist-before-submit', - title: 'Verification checklist before submission', - summary: 'Use this checklist to reduce correction loops.', - categoryKey: 'profile-verification', - category: 'Profile & Verification', - role: 'ALL', - tags: ['verification', 'checklist'], - updatedAt, - content: 'Check identity fields, category fit, proof quality, and profile completeness before submission.', - }, - { - id: 'seed-15', - slug: 'verification-timeline-24-48-hours', - title: 'Expected verification timeline (24-48 hours)', - summary: 'What is normal and what can delay review outcomes.', - categoryKey: 'profile-verification', - category: 'Profile & Verification', - role: 'ALL', - tags: ['verification', 'timeline'], - updatedAt, - content: 'Most profile reviews complete within 24-48 hours.\n\nComplex or incomplete submissions can take longer.', - }, - { - id: 'seed-16', - slug: 'verification-sent-back-correction', - title: 'Why verification was sent back for correction', - summary: 'Top reasons for correction requests and how to fix quickly.', - categoryKey: 'profile-verification', - category: 'Profile & Verification', - role: 'ALL', - tags: ['verification', 'correction'], - updatedAt, - content: 'Corrections happen when data is missing, unclear, mismatched, or unsupported.\n\nUpdate details and resubmit.', - }, - { - id: 'seed-17', - slug: 'companies-post-jobs-correctly', - title: 'How companies can post jobs correctly', - summary: 'Improve candidate quality with clear role details.', - categoryKey: 'jobs-applications', - category: 'Jobs & Applications', - role: 'company', - tags: ['jobs', 'company'], - updatedAt, - content: 'Use clear job titles, responsibilities, compensation, location, and timeline.\n\nGood clarity improves candidate relevance.', - }, - { - id: 'seed-18', - slug: 'track-application-status', - title: 'How job seekers can track application status', - summary: 'Understand submitted, viewed, shortlisted, and closed states.', - categoryKey: 'jobs-applications', - category: 'Jobs & Applications', - role: 'jobSeeker', - tags: ['application', 'job seeker'], - updatedAt, - content: 'Check your applications panel for status changes.\n\nKeep your profile updated to support faster responses.', - }, - { - id: 'seed-19', - slug: 'improve-application-quality', - title: 'How to improve job application quality', - summary: 'Simple ways to increase first-pass quality.', - categoryKey: 'jobs-applications', - category: 'Jobs & Applications', - role: 'jobSeeker', - tags: ['application', 'quality'], - updatedAt, - content: 'Tailor profile details to each role and add relevant work proof where possible.', - }, - { - id: 'seed-20', - slug: 'handle-duplicate-irrelevant-leads', - title: 'Handling duplicate or irrelevant leads', - summary: 'Reduce noise with better targeting and filters.', - categoryKey: 'leads-requests', - category: 'Leads & Requests', - role: 'professional', - tags: ['leads', 'filters'], - updatedAt, - content: 'Use lead filters and quick decline flows for low-fit requests.\n\nImprove profile targeting to reduce irrelevant leads.', - }, - { - id: 'seed-21', - slug: 'best-response-window-lead-conversion', - title: 'Best response window for higher lead conversion', - summary: 'Speed and clarity can improve first-contact outcomes.', - categoryKey: 'leads-requests', - category: 'Leads & Requests', - role: 'professional', - tags: ['conversion', 'response'], - updatedAt, - content: 'Respond early with clear scope and timeline.\n\nStructured replies usually perform better.', - }, - { - id: 'seed-22', - slug: 'organize-customer-requests', - title: 'How to organize incoming customer requests', - summary: 'Prioritization methods for faster handling.', - categoryKey: 'leads-requests', - category: 'Leads & Requests', - role: 'professional', - tags: ['requests', 'workflow'], - updatedAt, - content: 'Tag requests by urgency, scope, and budget.\n\nPrioritize high-fit requests first.', - }, - { - id: 'seed-23', - slug: 'what-are-tracecoins', - title: 'What are TraceCoins and where they are used', - summary: 'TraceCoins are platform credits used in selected flows.', - categoryKey: 'tracecoins-billing', - category: 'TraceCoins & Billing', - role: 'ALL', - tags: ['tracecoins', 'billing'], - updatedAt, - content: 'TraceCoins are credits for selected premium interactions and utilities inside the platform.', - }, - { - id: 'seed-24', - slug: 'view-invoices-payment-history', - title: 'How to view invoices and payment history', - summary: 'Where to find transaction records and billing references.', - categoryKey: 'tracecoins-billing', - category: 'TraceCoins & Billing', - role: 'ALL', - tags: ['invoice', 'payments'], - updatedAt, - content: 'Open billing settings to view purchase records and invoice history.', - }, - { - id: 'seed-25', - slug: 'refund-failed-payment-guidance', - title: 'Refund and failed payment guidance', - summary: 'What to do first when transactions fail or remain pending.', - categoryKey: 'tracecoins-billing', - category: 'TraceCoins & Billing', - role: 'ALL', - tags: ['refund', 'payment'], - updatedAt, - content: 'Check payment status first, then raise support with transaction reference if needed.', - }, - { - id: 'seed-26', - slug: 'report-suspicious-activity', - title: 'How to report suspicious activity', - summary: 'Raise reports quickly and include useful evidence.', - categoryKey: 'privacy-safety', - category: 'Privacy & Safety', - role: 'ALL', - tags: ['safety', 'report'], - updatedAt, - content: 'Use report actions on profiles or listings and include evidence to help faster review.', - }, - { - id: 'seed-27', - slug: 'manage-privacy-settings', - title: 'Managing privacy settings for your account', - summary: 'Control profile visibility and communication preferences.', - categoryKey: 'privacy-safety', - category: 'Privacy & Safety', - role: 'ALL', - tags: ['privacy', 'settings'], - updatedAt, - content: 'Adjust profile visibility and notification preferences from account settings.', - }, - { - id: 'seed-28', - slug: 'account-safety-best-practices', - title: 'Account safety best practices', - summary: 'Simple security habits to protect your account.', - categoryKey: 'privacy-safety', - category: 'Privacy & Safety', - role: 'ALL', - tags: ['security', 'account'], - updatedAt, - content: 'Use strong passwords, rotate credentials, and never share OTP codes.', - }, - { - id: 'seed-29', - slug: 'fix-profile-image-upload-issues', - title: 'Fix profile image or upload issues', - summary: 'Common upload fixes and file readiness checks.', - categoryKey: 'troubleshooting', - category: 'Troubleshooting', - role: 'ALL', - tags: ['upload', 'profile image'], - updatedAt, - content: 'Try supported formats, smaller file sizes, and stable network before retrying upload.', - }, - { - id: 'seed-30', - slug: 'dashboard-data-not-refreshing', - title: 'What to do if dashboard data does not refresh', - summary: 'Quick checks for stale status or delayed updates.', - categoryKey: 'troubleshooting', - category: 'Troubleshooting', - role: 'ALL', - tags: ['dashboard', 'refresh'], - updatedAt, - content: 'Refresh session, confirm active role context, and retry.\n\nIf issue persists, contact support with exact status details.', + content: masteringDashboardContent, }, ]; diff --git a/src/lib/help-center.ts b/src/lib/help-center.ts index 17803c9..270afa9 100644 --- a/src/lib/help-center.ts +++ b/src/lib/help-center.ts @@ -1,4 +1,5 @@ -import { HELP_CENTER_SEED_ARTICLES, HELP_CENTER_SEED_CATEGORIES } from '~/data/help-center-seed'; +import { HELP_CENTER_SEED_ARTICLES, HELP_CENTER_SEED_CATEGORIES } from "~/data/help-center-seed"; +import type { ContentBlock } from "~/data/help-center-seed"; export type HelpArticle = { id: string; @@ -7,10 +8,10 @@ export type HelpArticle = { summary: string; categoryKey: string; category: string; - role: 'ALL' | 'company' | 'jobSeeker' | 'professional' | 'customer' | 'platform'; + role: "ALL" | "company" | "jobSeeker" | "professional" | "customer" | "platform"; tags: string[]; updatedAt: string; - content: string; + content: ContentBlock[] | string; }; export type HelpCategory = { @@ -27,9 +28,9 @@ export async function fetchHelpCenterArticles(input: { q?: string; }): Promise { const params = new URLSearchParams(); - if (input.role && input.role !== 'ALL') params.set('role', input.role); - if (input.categoryKey) params.set('category', input.categoryKey); - if (input.q) params.set('q', input.q); + if (input.role && input.role !== "ALL") params.set("role", input.role); + if (input.categoryKey) params.set("category", input.categoryKey); + if (input.q) params.set("q", input.q); try { const res = await fetch(`/api/kb/articles?${params.toString()}`); @@ -41,7 +42,7 @@ export async function fetchHelpCenterArticles(input: { // Fallback: when backend search returns sparse/empty data, apply local filtering // so users can still find articles by simple keywords. if (input.q && items.length === 0) { - const allRes = await fetch('/api/kb/articles'); + const allRes = await fetch("/api/kb/articles"); if (allRes.ok) { const allData = await allRes.json(); const allRaw: any[] = Array.isArray(allData) ? allData : (allData.articles ?? []); @@ -61,7 +62,7 @@ export async function fetchHelpCenterArticles(input: { export async function fetchHelpCenterCategories(): Promise { try { - const res = await fetch('/api/kb/categories'); + const res = await fetch("/api/kb/categories"); if (!res.ok) return HELP_CENTER_SEED_CATEGORIES; const data = await res.json(); const raw: any[] = Array.isArray(data) ? data : (data.categories ?? []); @@ -79,7 +80,8 @@ export async function fetchHelpCenterCategories(): Promise { export async function fetchArticleBySlug(slug: string): Promise { try { const res = await fetch(`/api/kb/articles/${slug}`); - if (!res.ok) return (HELP_CENTER_SEED_ARTICLES as HelpArticle[]).find((a) => a.slug === slug) ?? null; + if (!res.ok) + return (HELP_CENTER_SEED_ARTICLES as HelpArticle[]).find((a) => a.slug === slug) ?? null; const data = await res.json(); const normalized = normalizeArticle(data); if (!normalized.slug) { @@ -96,12 +98,22 @@ export async function fetchRelatedArticles(input: { limit?: number; }): Promise { try { - const res = await fetch('/api/kb/articles'); - if (!res.ok) return pickRelated(HELP_CENTER_SEED_ARTICLES as HelpArticle[], input.article, input.limit ?? 4); + const res = await fetch("/api/kb/articles"); + if (!res.ok) + return pickRelated( + HELP_CENTER_SEED_ARTICLES as HelpArticle[], + input.article, + input.limit ?? 4 + ); const data = await res.json(); const raw: any[] = Array.isArray(data) ? data : (data.articles ?? []); const all = raw.map(normalizeArticle); - if (all.length === 0) return pickRelated(HELP_CENTER_SEED_ARTICLES as HelpArticle[], input.article, input.limit ?? 4); + if (all.length === 0) + return pickRelated( + HELP_CENTER_SEED_ARTICLES as HelpArticle[], + input.article, + input.limit ?? 4 + ); return pickRelated(all, input.article, input.limit ?? 4); } catch { return pickRelated(HELP_CENTER_SEED_ARTICLES as HelpArticle[], input.article, input.limit ?? 4); @@ -111,24 +123,37 @@ export async function fetchRelatedArticles(input: { // ── Normalizer ──────────────────────────────────────────────────────────────── function normalizeArticle(raw: any): HelpArticle { + const content = raw.content ?? raw.body ?? ""; + // Handle both string content (from API) and structured content blocks + const normalizedContent = + typeof content === "string" && content.startsWith("[") + ? JSON.parse(content) + : Array.isArray(content) + ? content + : [{ type: "paragraph", text: content }]; + return { - id: raw.id ?? '', - slug: raw.slug ?? '', - title: raw.title ?? '', - summary: raw.summary ?? raw.content?.slice(0, 160) ?? '', - categoryKey: raw.categoryKey ?? raw.category_key ?? raw.categorySlug ?? '', - category: raw.category ?? raw.categoryKey ?? '', - role: (raw.role ?? 'ALL') as HelpArticle['role'], + id: raw.id ?? "", + slug: raw.slug ?? "", + title: raw.title ?? "", + summary: raw.summary ?? (typeof content === "string" ? content.slice(0, 160) : "") ?? "", + categoryKey: raw.categoryKey ?? raw.category_key ?? raw.categorySlug ?? "", + category: raw.category ?? raw.categoryKey ?? "", + role: (raw.role ?? "ALL") as HelpArticle["role"], tags: Array.isArray(raw.tags) ? raw.tags : [], updatedAt: raw.updatedAt ?? raw.updated_at ?? new Date().toISOString(), - content: raw.content ?? raw.body ?? '', + content: normalizedContent, }; } // ── Legacy sync shims (kept for any remaining call sites) ───────────────────── // These return empty data synchronously — pages should use the async fetch* functions above. -export function listHelpCenterArticles(_input: { role?: string; categoryKey?: string; q?: string }): HelpArticle[] { +export function listHelpCenterArticles(_input: { + role?: string; + categoryKey?: string; + q?: string; +}): HelpArticle[] { return []; } @@ -141,21 +166,37 @@ export function getArticleBySlug(_slug: string): HelpArticle | null { } function articleMatchesQuery(article: HelpArticle, needle: string): boolean { - const haystack = [article.title, article.summary, article.content, article.category, article.categoryKey, article.tags.join(' ')].join(' ').toLowerCase(); + const haystack = [ + article.title, + article.summary, + article.content, + article.category, + article.categoryKey, + article.tags.join(" "), + ] + .join(" ") + .toLowerCase(); return haystack.includes(needle); } -function filterArticles(items: HelpArticle[], input: { role?: string; categoryKey?: string; q?: string }): HelpArticle[] { +function filterArticles( + items: HelpArticle[], + input: { role?: string; categoryKey?: string; q?: string } +): HelpArticle[] { let filtered = items; - if (input.role && input.role !== 'ALL') { + if (input.role && input.role !== "ALL") { const roleNeedle = input.role.toLowerCase(); - filtered = filtered.filter((a) => a.role === 'ALL' || String(a.role).toLowerCase() === roleNeedle); + filtered = filtered.filter( + (a) => a.role === "ALL" || String(a.role).toLowerCase() === roleNeedle + ); } if (input.categoryKey) { const needle = input.categoryKey.toLowerCase(); - filtered = filtered.filter((a) => [a.categoryKey, a.category].some((v) => (v || '').toLowerCase().includes(needle))); + filtered = filtered.filter((a) => + [a.categoryKey, a.category].some((v) => (v || "").toLowerCase().includes(needle)) + ); } if (input.q) { diff --git a/src/routes/help-center/article/[slug].tsx b/src/routes/help-center/article/[slug].tsx index 54cc03a..8115df5 100644 --- a/src/routes/help-center/article/[slug].tsx +++ b/src/routes/help-center/article/[slug].tsx @@ -1,22 +1,23 @@ -import { A, useParams } from '@solidjs/router'; -import { Meta, Title } from '@solidjs/meta'; -import { Show, For, createSignal, createResource, onCleanup, onMount, createMemo } from 'solid-js'; -import { fetchArticleBySlug, fetchRelatedArticles } from '~/lib/help-center'; -import PublicBackground from '~/components/PublicBackground'; -import PublicHeader from '~/components/PublicHeader'; -import PublicFooter from '~/components/PublicFooter'; +import { A, useParams } from "@solidjs/router"; +import { Meta, Title } from "@solidjs/meta"; +import { Show, For, createSignal, createResource, onCleanup, onMount, createMemo } from "solid-js"; +import { fetchArticleBySlug, fetchRelatedArticles } from "~/lib/help-center"; +import PublicBackground from "~/components/PublicBackground"; +import PublicHeader from "~/components/PublicHeader"; +import PublicFooter from "~/components/PublicFooter"; +import ArticleContent from "~/components/ArticleContent"; function categoryTitle(input: string) { return input - .split('-') + .split("-") .filter(Boolean) .map((chunk) => chunk[0].toUpperCase() + chunk.slice(1)) - .join(' '); + .join(" "); } export default function HelpCenterArticlePage() { const params = useParams(); - const slug = createMemo(() => String(params.slug || '').trim()); + const slug = createMemo(() => String(params.slug || "").trim()); const [scrollY, setScrollY] = createSignal(0); const [article] = createResource(() => params.slug, fetchArticleBySlug); @@ -24,21 +25,30 @@ export default function HelpCenterArticlePage() { () => article(), (item) => (item ? fetchRelatedArticles({ article: item, limit: 4 }) : []) ); - const canonical = createMemo(() => `https://test121.nxtgauge.com/help-center/article/${encodeURIComponent(slug())}`); + const canonical = createMemo( + () => `https://test121.nxtgauge.com/help-center/article/${encodeURIComponent(slug())}` + ); const pageTitle = createMemo(() => { const a = article(); - return a ? `${a.title} | Nxtgauge Help Center` : 'Help Center Article | Nxtgauge'; + return a ? `${a.title} | Nxtgauge Help Center` : "Help Center Article | Nxtgauge"; }); const pageDescription = createMemo(() => { const a = article(); - return a?.summary || 'Read support and product guidance from Nxtgauge Help Center.'; + return a?.summary || "Read support and product guidance from Nxtgauge Help Center."; }); onMount(() => { const onScroll = () => setScrollY(window.scrollY || 0); onScroll(); - window.addEventListener('scroll', onScroll, { passive: true }); - onCleanup(() => window.removeEventListener('scroll', onScroll)); + window.addEventListener("scroll", onScroll, { passive: true }); + onCleanup(() => window.removeEventListener("scroll", onScroll)); + }); + + const formattedDate = createMemo(() => { + const a = article(); + if (!a?.updatedAt) return ""; + const date = new Date(a.updatedAt); + return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); }); return ( @@ -58,82 +68,489 @@ export default function HelpCenterArticlePage() {
+ {/* Breadcrumb - Dark */} +
+
+ +
+
+ + {/* Loading State */}
-
-

Loading article…

-
-
-
- - -
-
-

Article not found

-

The requested Help Center article is unavailable.

-
- Back to Help Center +
+
+
+

Loading article...

+ {/* Not Found State */} + +
+
+
+
+ + + + + +
+

+ Article not found +

+

+ The requested Help Center article is unavailable or has been moved. +

+ + Back to Help Center + +
+
+
+
+ + {/* Article Content */} {(a) => ( <> -
-
-

{a().category || categoryTitle(a().categoryKey)}

-

{a().title}

-

{a().summary}

- - 0}> -