feat: implement CommandPalette for enhanced navigation and recent pages tracking
This commit is contained in:
@@ -8,9 +8,11 @@ import {DefaultSeo} from 'next-seo';
|
||||
import {NuqsAdapter} from 'nuqs/adapters/next/pages';
|
||||
import {Loader} from '@plunk/ui';
|
||||
import {ActiveProjectProvider} from '../lib/contexts/ActiveProjectProvider';
|
||||
import {CommandPalette} from '../components/CommandPalette';
|
||||
import {useProjects} from '../lib/hooks/useProject';
|
||||
import {useUser} from '../lib/hooks/useUser';
|
||||
import {network} from '../lib/network';
|
||||
import {addRecentPage, getFallbackLabel} from '../lib/recentPages';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
||||
@@ -135,6 +137,31 @@ function Root(props: AppProps) {
|
||||
route => router.pathname === route || router.pathname.startsWith(`${route}/`),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChange = () => {
|
||||
const pathname = window.location.pathname;
|
||||
|
||||
// Pass 1: record immediately with a route-pattern label so detail pages
|
||||
// are always captured even before their data loads.
|
||||
const fallback = getFallbackLabel(pathname);
|
||||
if (fallback) {
|
||||
addRecentPage({label: fallback, href: pathname});
|
||||
}
|
||||
|
||||
// Pass 2: after data loads, replace the label with the page's own title
|
||||
// (e.g. contact email set by NextSeo after the API response arrives).
|
||||
setTimeout(() => {
|
||||
const titleLabel = document.title.split(' | ')[0]?.trim();
|
||||
if (titleLabel && titleLabel !== 'Plunk' && titleLabel !== fallback) {
|
||||
addRecentPage({label: titleLabel, href: pathname});
|
||||
}
|
||||
}, 800);
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => router.events.off('routeChangeComplete', handleRouteChange);
|
||||
}, [router.events]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toaster position={'top-right'} />
|
||||
@@ -145,6 +172,7 @@ function Root(props: AppProps) {
|
||||
<App {...props} />
|
||||
) : (
|
||||
<ProjectGuard>
|
||||
<CommandPalette />
|
||||
<App {...props} />
|
||||
</ProjectGuard>
|
||||
)}
|
||||
|
||||
@@ -59,6 +59,7 @@ import {useRouter} from 'next/router';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {toast} from 'sonner';
|
||||
import useSWR from 'swr';
|
||||
import {NextSeo} from 'next-seo';
|
||||
import {useActiveProject} from '../../lib/contexts/ActiveProjectProvider';
|
||||
|
||||
interface CampaignStats {
|
||||
@@ -338,6 +339,7 @@ export default function CampaignDetailsPage() {
|
||||
if (isEditMode) {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<NextSeo title={campaign.data.name} />
|
||||
<form onSubmit={handleSave} className={`space-y-6 ${hasChanges ? 'pb-32' : ''}`}>
|
||||
{/* Header */}
|
||||
<div className="space-y-4">
|
||||
@@ -842,6 +844,7 @@ export default function CampaignDetailsPage() {
|
||||
// Render stats view for sent/scheduled campaigns
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<NextSeo title={campaign.data.name} />
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="space-y-4">
|
||||
|
||||
@@ -22,6 +22,7 @@ import {useRouter} from 'next/router';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {toast} from 'sonner';
|
||||
import useSWR from 'swr';
|
||||
import {NextSeo} from 'next-seo';
|
||||
import type {FilterCondition} from '@plunk/types';
|
||||
import {SegmentSchemas} from '@plunk/shared';
|
||||
import {SegmentFilterBuilder} from '../../components/SegmentFilterBuilder';
|
||||
@@ -210,6 +211,7 @@ export default function SegmentDetailPage() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<NextSeo title={segment.name} />
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -19,6 +19,7 @@ import {network} from '../../lib/network';
|
||||
import {useChangeTracking} from '../../lib/hooks/useChangeTracking';
|
||||
import {ArrowLeft, Save, Trash2, TriangleAlert} from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import {NextSeo} from 'next-seo';
|
||||
import {useRouter} from 'next/router';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {toast} from 'sonner';
|
||||
@@ -126,6 +127,7 @@ export default function TemplateEditorPage() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<NextSeo title={template.name} />
|
||||
<form onSubmit={handleSave} className={`max-w-5xl mx-auto space-y-6 ${hasChanges ? 'pb-32' : ''}`}>
|
||||
{/* Header */}
|
||||
<div className="space-y-4">
|
||||
|
||||
@@ -62,6 +62,7 @@ import {useRouter} from 'next/router';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {toast} from 'sonner';
|
||||
import useSWR from 'swr';
|
||||
import {NextSeo} from 'next-seo';
|
||||
import {WorkflowBuilder} from '../../components/WorkflowBuilder';
|
||||
import {KeyValueEditor} from '../../components/KeyValueEditor';
|
||||
import {TemplateSearchPicker} from '../../components/TemplateSearchPicker';
|
||||
@@ -440,6 +441,7 @@ export default function WorkflowEditorPage() {
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<NextSeo title={workflow.name} />
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3 sm:gap-4">
|
||||
|
||||
Reference in New Issue
Block a user