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:
26
admin-spa/src/components/DocLink.tsx
Normal file
26
admin-spa/src/components/DocLink.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
58
admin-spa/src/config/docRoutes.ts
Normal file
58
admin-spa/src/config/docRoutes.ts
Normal 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;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user