feat: integrate contextual links and fix coupons navigation

- Added DocLink component and mapped routes
- Fixed Coupons nav link to /marketing/coupons
- Updated Settings pages to show inline documentation links
This commit is contained in:
Dwindi Ramadhana
2026-02-05 22:51:44 +07:00
parent 5f08c18ec7
commit 7da4f0a167
11 changed files with 221 additions and 22 deletions

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { useLocation } from 'react-router-dom';
import { BookOpen } from 'lucide-react';
import { getDocUrl } from '@/config/docRoutes';
import { Button } from '@/components/ui/button';
export function DocLink() {
const location = useLocation();
const docUrl = getDocUrl(location.pathname);
if (!docUrl) return null;
return (
<Button
variant="ghost"
size="icon"
className="h-8 w-8 ml-2 text-muted-foreground hover:text-primary"
asChild
>
<a href={docUrl} target="_blank" rel="noopener noreferrer" title="View Documentation">
<BookOpen className="h-4 w-4" />
<span className="sr-only">View Documentation</span>
</a>
</Button>
);
}

View File

@@ -7,6 +7,8 @@ interface PageHeaderProps {
hideOnDesktop?: boolean;
}
import { DocLink } from '@/components/DocLink';
export function PageHeader({ fullscreen = false, hideOnDesktop = false }: PageHeaderProps) {
const { title, action } = usePageHeader();
const location = useLocation();
@@ -24,8 +26,9 @@ export function PageHeader({ fullscreen = false, hideOnDesktop = false }: PageHe
return (
<div className={`sticky top-0 z-20 border-b bg-background ${hideOnDesktop ? 'md:hidden' : ''}`}>
<div className={`${containerClass} px-4 py-3 flex items-center justify-between min-w-0`}>
<div className="min-w-0 flex-1">
<div className="min-w-0 flex-1 flex items-center">
<h1 className="text-lg font-semibold truncate">{title}</h1>
<DocLink />
</div>
{action && <div className="flex-shrink-0 ml-4">{action}</div>}
</div>

View File

@@ -0,0 +1,58 @@
/**
* docRoutes.ts
*
* Maps Admin SPA routes to external documentation URLs.
* Used by the DocLink component to provide contextual help.
*/
export const docRoutes: Record<string, string> = {
// Marketing Suite
// '/marketing': 'https://docs.woonoow.com/docs/marketing', // No general marketing doc yet
'/marketing/coupons': 'https://docs.woonoow.com/docs/marketing/coupons',
'/marketing/newsletter': 'https://docs.woonoow.com/docs/marketing/newsletter',
'/marketing/wishlist': 'https://docs.woonoow.com/docs/marketing/wishlist',
// Settings - Modules
'/settings/modules/wishlist': 'https://docs.woonoow.com/docs/marketing/wishlist',
'/settings/modules/newsletter': 'https://docs.woonoow.com/docs/marketing/newsletter',
// Builder
'/appearance/header': 'https://docs.woonoow.com/docs/builder/header-footer#header',
'/appearance/footer': 'https://docs.woonoow.com/docs/builder/header-footer#footer',
// Store Management
'/products': 'https://docs.woonoow.com/docs/store/products',
'/orders': 'https://docs.woonoow.com/docs/store/orders',
'/customers': 'https://docs.woonoow.com/docs/store/customers',
// Configuration
'/settings': 'https://docs.woonoow.com/docs/configuration/general',
'/settings/store': 'https://docs.woonoow.com/docs/configuration/general',
'/settings/payments': 'https://docs.woonoow.com/docs/configuration/payment-shipping',
'/settings/shipping': 'https://docs.woonoow.com/docs/configuration/payment-shipping',
'/settings/tax': 'https://docs.woonoow.com/docs/configuration/general', // Fallback
'/settings/customers': 'https://docs.woonoow.com/docs/store/customers',
'/settings/security': 'https://docs.woonoow.com/docs/configuration/security',
'/settings/notifications': 'https://docs.woonoow.com/docs/configuration/email',
'/settings/modules': 'https://docs.woonoow.com/docs/configuration/modules',
'/appearance/themes': 'https://docs.woonoow.com/docs/configuration/appearance',
};
/**
* Helper to get doc URL for a specific path
*
* Can be enhanced with regex matching if needed
*/
export const getDocUrl = (path: string): string | null => {
// 1. Direct match
if (docRoutes[path]) return docRoutes[path];
// 2. Partial match (longest match first)
const sortedKeys = Object.keys(docRoutes).sort((a, b) => b.length - a.length);
for (const key of sortedKeys) {
if (path.startsWith(key)) {
return docRoutes[key];
}
}
return null;
};

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { usePageHeader } from '@/contexts/PageHeaderContext';
import { Link, useNavigate } from 'react-router-dom';
import { __ } from '@/lib/i18n';
import { CouponsApi, type Coupon } from '@/lib/api/coupons';
@@ -36,6 +37,12 @@ export default function CouponsIndex() {
// Configure FAB to navigate to new coupon page
useFABConfig('coupons');
// Set page header for contextual link
const { setPageHeader } = usePageHeader();
React.useEffect(() => {
setPageHeader(__('Coupons'));
}, [setPageHeader]);
// Count active filters
const activeFiltersCount = discountType && discountType !== 'all' ? 1 : 0;

View File

@@ -6,6 +6,8 @@ import { __ } from '@/lib/i18n';
import { useModules } from '@/hooks/useModules';
import { cn } from '@/lib/utils'; // Assuming cn exists, widely used in ShadCN
import { DocLink } from '@/components/DocLink';
export default function NewsletterLayout() {
const navigate = useNavigate();
const location = useLocation();
@@ -16,7 +18,10 @@ export default function NewsletterLayout() {
return (
<div className="w-full space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight">{__('Newsletter')}</h1>
<div className="flex items-center">
<h1 className="text-2xl font-bold tracking-tight">{__('Newsletter')}</h1>
<DocLink />
</div>
<p className="text-muted-foreground mt-2">{__('Newsletter module is disabled')}</p>
</div>
<div className="bg-yellow-50 dark:bg-yellow-950/20 border border-yellow-200 dark:border-yellow-900 rounded-lg p-6 text-center">
@@ -53,7 +58,10 @@ export default function NewsletterLayout() {
return (
<div className="w-full space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight">{__('Newsletter')}</h1>
<div className="flex items-center">
<h1 className="text-2xl font-bold tracking-tight">{__('Newsletter')}</h1>
<DocLink />
</div>
<p className="text-muted-foreground mt-2">{__('Manage subscribers and send email campaigns')}</p>
</div>

View File

@@ -24,13 +24,18 @@ const cards: MarketingCard[] = [
},
];
import { DocLink } from '@/components/DocLink';
export default function Marketing() {
const navigate = useNavigate();
return (
<div className="w-full space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight">{__('Marketing')}</h1>
<div className="flex items-center">
<h1 className="text-2xl font-bold tracking-tight">{__('Marketing')}</h1>
<DocLink />
</div>
<p className="text-muted-foreground mt-2">{__('Newsletter, campaigns, and promotions')}</p>
</div>

View File

@@ -3,6 +3,8 @@ import { Button } from '@/components/ui/button';
import { Loader2 } from 'lucide-react';
import { usePageHeader } from '@/contexts/PageHeaderContext';
import { DocLink } from '@/components/DocLink';
interface SettingsLayoutProps {
title: string | React.ReactNode;
description?: string;
@@ -40,7 +42,7 @@ export function SettingsLayout({
useEffect(() => {
// Extract string title if it's a ReactNode
const titleString = typeof title === 'string' ? title : '';
if (onSave) {
// Combine custom action with save button
const headerAction = (
@@ -84,7 +86,10 @@ export function SettingsLayout({
<div className="mb-8">
<div className="flex items-start justify-between gap-4 min-w-0">
<div className="min-w-0 flex-1">
<h1 className="text-2xl font-bold tracking-tight">{title}</h1>
<div className="flex items-center">
<h1 className="text-2xl font-bold tracking-tight">{title}</h1>
<DocLink />
</div>
{description && (
<p className="text-muted-foreground mt-2">{description}</p>
)}