From 3974a2a620addda86beb573af1c537b7b3af5fed Mon Sep 17 00:00:00 2001 From: armaan Date: Mon, 29 Jun 2026 22:15:33 +0000 Subject: [PATCH 1/7] Add back buttons to account settings and purchase pages Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../(main)/purchase/[code]/page-client.tsx | 24 +++++++++++- .../account-settings/index.tsx | 28 ++++++++++++- .../account-settings/sidebar-layout.tsx | 39 ++++++++++++++++--- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx b/apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx index d3937e69d..0e0a8163a 100644 --- a/apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx +++ b/apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx @@ -9,7 +9,7 @@ import { DesignAlert } from "@/components/design-components/alert"; import { DesignCard } from "@/components/design-components/card"; import { Skeleton, Typography } from "@/components/ui"; import { getPublicEnvVar } from "@/lib/env"; -import { XCircleIcon } from "@phosphor-icons/react"; +import { ArrowLeftIcon, XCircleIcon } from "@phosphor-icons/react"; import { inlineProductSchema } from "@hexclave/shared/dist/schema-fields"; import { throwErr } from "@hexclave/shared/dist/utils/errors"; import { typedEntries } from "@hexclave/shared/dist/utils/objects"; @@ -169,6 +169,17 @@ export default function PageClient({ code }: { code: string }) { if (showInvalidPurchaseCode) { return (
+ {returnUrl && ( +
+ + + Back + +
+ )}
@@ -190,6 +201,17 @@ export default function PageClient({ code }: { code: string }) { return (
+ {returnUrl && ( + + )}
{/* Left Panel: Product & Pricing Selection */}
diff --git a/apps/hosted-components/src/hosted-components/account-settings/index.tsx b/apps/hosted-components/src/hosted-components/account-settings/index.tsx index 6a4ac5c1d..2b5548289 100644 --- a/apps/hosted-components/src/hosted-components/account-settings/index.tsx +++ b/apps/hosted-components/src/hosted-components/account-settings/index.tsx @@ -1,6 +1,6 @@ import { Skeleton, cn } from "~/components/ui"; import { Bell, Contact, CreditCard, Key, Monitor, PlusCircle, Settings, ShieldCheck } from "lucide-react"; -import React, { Suspense, useEffect, useMemo, useRef, useState } from "react"; +import React, { Suspense, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react"; import { Team, useStackApp, useUser } from "@hexclave/react"; import { HostedFullPage } from "./hosted-full-page"; import { SidebarLayout } from './sidebar-layout'; @@ -27,6 +27,30 @@ const Icon = ({ name }: { name: keyof typeof iconMap }) => { const emptyTeams: Team[] = []; +function getReferrerSnapshot(): string { + if (typeof document === "undefined") return ""; + return document.referrer; +} + +function subscribeToReferrer() { + // document.referrer never changes after page load, so no-op subscription + return () => {}; +} + +function useExternalBackUrl(): string | null { + const referrer = useSyncExternalStore(subscribeToReferrer, getReferrerSnapshot, () => ""); + return useMemo(() => { + if (!referrer) return null; + try { + const referrerOrigin = new URL(referrer).origin; + if (referrerOrigin === window.location.origin) return null; + return referrer; + } catch { + return null; + } + }, [referrer]); +} + const EmailsAndAuthPage = React.lazy(async () => ({ default: (await import("./email-and-auth/email-and-auth-page")).EmailsAndAuthPage, })); @@ -86,6 +110,7 @@ export function HostedAccountSettings(props: { }, }>, }) { + const backUrl = useExternalBackUrl(); const userFromHook = useUser({ or: props.mockUser ? 'return-null' : 'redirect' }); const stackApp = useStackApp(); const projectFromHook = stackApp.useProject(); @@ -267,6 +292,7 @@ export function HostedAccountSettings(props: { ); diff --git a/apps/hosted-components/src/hosted-components/account-settings/sidebar-layout.tsx b/apps/hosted-components/src/hosted-components/account-settings/sidebar-layout.tsx index 05516a8a6..61ffa1e8a 100644 --- a/apps/hosted-components/src/hosted-components/account-settings/sidebar-layout.tsx +++ b/apps/hosted-components/src/hosted-components/account-settings/sidebar-layout.tsx @@ -1,6 +1,6 @@ import { Button, cn } from "~/components/ui"; import { useHash } from '@hexclave/shared/dist/hooks/use-hash'; -import { XIcon } from 'lucide-react'; +import { ArrowLeft, XIcon } from 'lucide-react'; import React, { ReactNode } from 'react'; export type SidebarItem = { @@ -13,16 +13,16 @@ export type SidebarItem = { contentTitle?: React.ReactNode, } -export function SidebarLayout(props: { items: SidebarItem[], title?: ReactNode, className?: string }) { +export function SidebarLayout(props: { items: SidebarItem[], title?: ReactNode, className?: string, backUrl?: string | null }) { const hash = useHash(); const selectedIndex = props.items.findIndex(item => item.id && (item.id === hash)); return ( <>
- +
- +
); @@ -65,7 +65,19 @@ function Items(props: { items: SidebarItem[], selectedIndex: number }) { )); } -function DesktopLayout(props: { items: SidebarItem[], title?: ReactNode, selectedIndex: number }) { +function BackButton({ url }: { url: string }) { + return ( + + + Back + + ); +} + +function DesktopLayout(props: { items: SidebarItem[], title?: ReactNode, selectedIndex: number, backUrl?: string | null }) { const selectedItem = props.items[props.selectedIndex === -1 ? 0 : props.selectedIndex]; return ( @@ -74,6 +86,11 @@ function DesktopLayout(props: { items: SidebarItem[], title?: ReactNode, selecte pinned while the page scrolls with the document. Slightly darker than the page in light mode, slightly lighter in dark mode, so it reads as a distinct surface. */}