diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 00000000..1c41f9ab --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,36 @@ +User-agent: * +Allow: / +Allow: /terms +Allow: /policies +Allow: /privacy +Allow: /contact +Allow: /contact-sales +Allow: /pricing +Allow: /promotions +Allow: /templates +Disallow: /home +Disallow: /trees/ +Disallow: /my-trees +Disallow: /pilot/ +Disallow: /admin/ +Disallow: /account/ +Disallow: /script-builder +Disallow: /scripts +Disallow: /sessions +Disallow: /analytics +Disallow: /escalations +Disallow: /queue +Disallow: /review-queue +Disallow: /network-diagrams +Disallow: /kb-accelerator +Disallow: /step-library +Disallow: /tickets +Disallow: /shares +Disallow: /feedback +Disallow: /welcome +Disallow: /flow-assist +Disallow: /dev/ +Disallow: /flows/ +Disallow: /guides + +Sitemap: https://resolutionflow.com/sitemap.xml diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml new file mode 100644 index 00000000..c0db68a5 --- /dev/null +++ b/frontend/public/sitemap.xml @@ -0,0 +1,57 @@ + + + + https://resolutionflow.com/ + 2026-05-13 + weekly + 1.0 + + + https://resolutionflow.com/pricing + 2026-05-13 + monthly + 0.9 + + + https://resolutionflow.com/contact-sales + 2026-05-13 + monthly + 0.8 + + + https://resolutionflow.com/contact + 2026-05-13 + monthly + 0.7 + + + https://resolutionflow.com/templates + 2026-05-13 + weekly + 0.7 + + + https://resolutionflow.com/terms + 2026-05-13 + yearly + 0.4 + + + https://resolutionflow.com/privacy + 2026-05-13 + yearly + 0.4 + + + https://resolutionflow.com/policies + 2026-05-13 + yearly + 0.4 + + + https://resolutionflow.com/promotions + 2026-05-13 + monthly + 0.4 + + diff --git a/frontend/src/components/common/PageMeta.tsx b/frontend/src/components/common/PageMeta.tsx index 342aad6b..f2d8b962 100644 --- a/frontend/src/components/common/PageMeta.tsx +++ b/frontend/src/components/common/PageMeta.tsx @@ -5,6 +5,8 @@ interface PageMetaProps { description?: string ogImage?: string ogType?: string + /** Canonical/Open Graph URL. Defaults to `window.location.href` in the browser. */ + url?: string } const SITE_NAME = 'ResolutionFlow' @@ -20,8 +22,12 @@ export function PageMeta({ description = DEFAULT_DESCRIPTION, ogImage, ogType = 'website', + url, }: PageMetaProps) { const fullTitle = title ? `${title} | ${SITE_NAME}` : `${SITE_NAME} — ${DEFAULT_TAGLINE}` + const resolvedUrl = + url ?? (typeof window !== 'undefined' ? window.location.href : undefined) + const twitterCard = ogImage ? 'summary_large_image' : 'summary' return ( @@ -33,10 +39,11 @@ export function PageMeta({ + {resolvedUrl && } {ogImage && } {/* Twitter */} - + {ogImage && } diff --git a/frontend/src/components/dashboard/NextStepCard.tsx b/frontend/src/components/dashboard/NextStepCard.tsx index 9b158d5a..9dc1fc7f 100644 --- a/frontend/src/components/dashboard/NextStepCard.tsx +++ b/frontend/src/components/dashboard/NextStepCard.tsx @@ -79,7 +79,7 @@ export function pickNextStep( title: 'Run your first FlowPilot session', description: 'Paste a ticket or pick a flow to see ResolutionFlow in action.', ctaLabel: 'Start a session', - ctaPath: '/', + ctaPath: '/home', } } if (!status.connected_psa) { diff --git a/frontend/src/components/dashboard/SetupChecklist.tsx b/frontend/src/components/dashboard/SetupChecklist.tsx index 13ddcb1f..5ee7d726 100644 --- a/frontend/src/components/dashboard/SetupChecklist.tsx +++ b/frontend/src/components/dashboard/SetupChecklist.tsx @@ -51,7 +51,7 @@ export function buildChecklistItems( { key: 'ran_session', label: 'Run your first FlowPilot session', - path: '/', + path: '/home', done: status.ran_session, }, { diff --git a/frontend/src/components/layout/AppLayout.tsx b/frontend/src/components/layout/AppLayout.tsx index 5eaaf104..8366c699 100644 --- a/frontend/src/components/layout/AppLayout.tsx +++ b/frontend/src/components/layout/AppLayout.tsx @@ -58,7 +58,7 @@ export function AppLayout() { } const mobileNavItems = [ - { path: '/', label: 'Dashboard', icon: LayoutGrid }, + { path: '/home', label: 'Dashboard', icon: LayoutGrid }, { path: '/sessions', label: 'Session History', icon: Clock }, { path: '/escalations', label: 'Escalations', icon: AlertTriangle }, { path: '/trees', label: 'Guided Flows', icon: GitBranch }, @@ -106,7 +106,7 @@ export function AppLayout() { style={{ background: 'var(--color-bg-sidebar)', borderRight: '1px solid var(--color-border-default)' }} >
- + ResolutionFlow diff --git a/frontend/src/components/layout/CommandPalette.tsx b/frontend/src/components/layout/CommandPalette.tsx index 7bac283f..de411391 100644 --- a/frontend/src/components/layout/CommandPalette.tsx +++ b/frontend/src/components/layout/CommandPalette.tsx @@ -40,7 +40,7 @@ interface Group { } const PAGES: PaletteItem[] = [ - { id: 'page-dashboard', group: 'pages', title: 'Dashboard', path: '/', icon: 'page' }, + { id: 'page-dashboard', group: 'pages', title: 'Dashboard', path: '/home', icon: 'page' }, { id: 'page-flows', group: 'pages', title: 'All Flows', subtitle: 'Browse your flow library', path: '/trees', icon: 'page' }, { id: 'page-sessions', group: 'pages', title: 'Sessions', subtitle: 'View session history', path: '/sessions', icon: 'page' }, { id: 'page-flowpilot', group: 'pages', title: 'FlowPilot', subtitle: 'AI troubleshooting', path: '/pilot', icon: 'page' }, diff --git a/frontend/src/components/layout/ProtectedRoute.tsx b/frontend/src/components/layout/ProtectedRoute.tsx index 1c5d6140..73e2bb10 100644 --- a/frontend/src/components/layout/ProtectedRoute.tsx +++ b/frontend/src/components/layout/ProtectedRoute.tsx @@ -22,7 +22,7 @@ export function ProtectedRoute({ requiredRole, children }: ProtectedRouteProps) } if (!isAuthenticated) { - return + return } // Enforce must_change_password — redirect unless already on /change-password diff --git a/frontend/src/components/layout/TopBar.tsx b/frontend/src/components/layout/TopBar.tsx index 42e107df..91c2bf5b 100644 --- a/frontend/src/components/layout/TopBar.tsx +++ b/frontend/src/components/layout/TopBar.tsx @@ -63,7 +63,7 @@ export function TopBar() { > {/* Logo area */} diff --git a/frontend/src/components/layout/__tests__/AppLayout.test.tsx b/frontend/src/components/layout/__tests__/AppLayout.test.tsx index 26bf3fcd..0ada0458 100644 --- a/frontend/src/components/layout/__tests__/AppLayout.test.tsx +++ b/frontend/src/components/layout/__tests__/AppLayout.test.tsx @@ -71,11 +71,11 @@ const FROZEN_NOW = new Date('2026-05-06T00:00:00Z') function renderAppLayout() { return render( - + }> child route
} /> diff --git a/frontend/src/components/layout/__tests__/ProtectedRoute.test.tsx b/frontend/src/components/layout/__tests__/ProtectedRoute.test.tsx new file mode 100644 index 00000000..a87f3bf9 --- /dev/null +++ b/frontend/src/components/layout/__tests__/ProtectedRoute.test.tsx @@ -0,0 +1,59 @@ +import { describe, it, expect, beforeEach } from 'vitest' +import { render, screen } from '@testing-library/react' +import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom' + +import { ProtectedRoute } from '../ProtectedRoute' +import { useAuthStore } from '@/store/authStore' + +/** + * Probe component: surfaces the current pathname and `location.state.from` so + * the test can assert both the redirect target and that the original + * destination is preserved for post-login return. + */ +function LocationProbe() { + const loc = useLocation() + const from = + (loc.state as { from?: { pathname?: string } } | null)?.from?.pathname ?? '' + return ( + <> +
{loc.pathname}
+
{from}
+ + ) +} + +describe('ProtectedRoute — unauthenticated redirect', () => { + beforeEach(() => { + useAuthStore.setState({ + user: null, + token: null, + isAuthenticated: false, + isLoading: false, + }) + }) + + it('redirects unauthenticated visits to /home → / and preserves origin in state.from', () => { + render( + + + +
home
+ + } + /> + } /> +
+
, + ) + + // The protected page should not render. + expect(screen.queryByTestId('home-content')).not.toBeInTheDocument() + + // We landed on / (the public landing route), not /landing. + expect(screen.getByTestId('probe-pathname')).toHaveTextContent('/') + expect(screen.getByTestId('probe-from')).toHaveTextContent('/home') + }) +}) diff --git a/frontend/src/pages/AssistantChatPage.tsx b/frontend/src/pages/AssistantChatPage.tsx index 4e6f6fca..bcd214de 100644 --- a/frontend/src/pages/AssistantChatPage.tsx +++ b/frontend/src/pages/AssistantChatPage.tsx @@ -2416,7 +2416,7 @@ export default function AssistantChatPage() { setShowConclude(false) if (activeSessionStatus === 'escalated') { toast.info('Session escalated. Heading back to your dashboard.') - navigate('/') + navigate('/home') } }} onConclude={handleConclude} diff --git a/frontend/src/pages/ContactPage.tsx b/frontend/src/pages/ContactPage.tsx index e7d68adf..dade0f1a 100644 --- a/frontend/src/pages/ContactPage.tsx +++ b/frontend/src/pages/ContactPage.tsx @@ -7,7 +7,7 @@ export default function ContactPage() {
- ← Back to home + ← Back to home

Contact ResolutionFlow

We respond to customer inquiries Monday through Friday during U.S. business hours, excluding federal holidays. Email is the fastest path to a response. diff --git a/frontend/src/pages/OAuthCallbackPage.tsx b/frontend/src/pages/OAuthCallbackPage.tsx index b32dd080..e606ac27 100644 --- a/frontend/src/pages/OAuthCallbackPage.tsx +++ b/frontend/src/pages/OAuthCallbackPage.tsx @@ -112,10 +112,10 @@ export function OAuthCallbackPage() { // Invitee path lands on the dashboard with the teammate-welcome // marker; new self-serve owners go to the welcome wizard; returning - // users to /. - let dest = '/' + // users to /home. + let dest = '/home' if (decoded?.accountInviteCode) { - dest = '/?welcome=teammate' + dest = '/home?welcome=teammate' } else if (result.is_new_user) { dest = '/welcome' } diff --git a/frontend/src/pages/PoliciesPage.tsx b/frontend/src/pages/PoliciesPage.tsx index eaabb472..1da097cd 100644 --- a/frontend/src/pages/PoliciesPage.tsx +++ b/frontend/src/pages/PoliciesPage.tsx @@ -7,7 +7,7 @@ export default function PoliciesPage() {

- ← Back to home + ← Back to home

Customer Policies

Last updated: May 7, 2026

Operator: ResolutionFlow, LLC (the “Company”), operator of ResolutionFlow (“Service”).

diff --git a/frontend/src/pages/PrivacyPage.tsx b/frontend/src/pages/PrivacyPage.tsx index 1478bbca..9a6fc5f3 100644 --- a/frontend/src/pages/PrivacyPage.tsx +++ b/frontend/src/pages/PrivacyPage.tsx @@ -7,7 +7,7 @@ export default function PrivacyPage() {
- ← Back to home + ← Back to home

Privacy Policy

Last updated: March 21, 2026

diff --git a/frontend/src/pages/PromotionsPage.tsx b/frontend/src/pages/PromotionsPage.tsx index 132ad10b..f495f1dd 100644 --- a/frontend/src/pages/PromotionsPage.tsx +++ b/frontend/src/pages/PromotionsPage.tsx @@ -7,7 +7,7 @@ export default function PromotionsPage() {
- ← Back to home + ← Back to home

Promotions

Last updated: May 7, 2026

diff --git a/frontend/src/pages/PublicTemplatesPage.tsx b/frontend/src/pages/PublicTemplatesPage.tsx index ec12e088..137aa4a0 100644 --- a/frontend/src/pages/PublicTemplatesPage.tsx +++ b/frontend/src/pages/PublicTemplatesPage.tsx @@ -168,7 +168,7 @@ export default function PublicTemplatesPage() { {/* Header */}
- + Resolution @@ -406,7 +406,7 @@ export default function PublicTemplatesPage() {