From 0f7e644f17237338d8f60ee415bfce599085ef34 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 8 Feb 2026 21:21:59 +0100 Subject: [PATCH 01/10] fix: fetch full profile after login so header shows display name without refresh Co-authored-by: Cursor --- lib/auth/context.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/auth/context.tsx b/lib/auth/context.tsx index bf4bcd4..0a8245f 100644 --- a/lib/auth/context.tsx +++ b/lib/auth/context.tsx @@ -53,6 +53,20 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { localStorage.setItem('user', JSON.stringify(userData)) setUser(userData) router.refresh() + // * Fetch full profile (including display_name) so header shows correct name without page refresh + apiRequest('/auth/user/me') + .then((fullProfile) => { + setUser((prev) => { + const merged = { + ...fullProfile, + org_id: prev?.org_id ?? fullProfile.org_id, + role: prev?.role ?? fullProfile.role, + } + localStorage.setItem('user', JSON.stringify(merged)) + return merged + }) + }) + .catch((e) => console.error('Failed to fetch full profile after login', e)) } const logout = useCallback(async () => { From ff456ef4d68f7726677fd2db650dfe5f072d1f5a Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 8 Feb 2026 21:27:42 +0100 Subject: [PATCH 02/10] refactor: remove Tools page and associated link from layout, streamlining navigation --- app/layout-content.tsx | 8 -------- app/tools/page.tsx | 15 --------------- 2 files changed, 23 deletions(-) delete mode 100644 app/tools/page.tsx diff --git a/app/layout-content.tsx b/app/layout-content.tsx index 2e9098a..320c367 100644 --- a/app/layout-content.tsx +++ b/app/layout-content.tsx @@ -73,14 +73,6 @@ export default function LayoutContent({ children }: { children: React.ReactNode Features )} - {auth.user && ( - - Tools - - )} } /> diff --git a/app/tools/page.tsx b/app/tools/page.tsx deleted file mode 100644 index e33bf6d..0000000 --- a/app/tools/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client' - -import UtmBuilder from '@/components/tools/UtmBuilder' - -export default function ToolsPage() { - return ( -
-

Tools

-
-

UTM Campaign Builder

- -
-
- ) -} From 3e8edd188a1e6a7f0634982beee1d1259ed78d37 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 8 Feb 2026 21:36:42 +0100 Subject: [PATCH 03/10] fix: fetch full user profile after login in auth callback and welcome page to ensure correct display name --- app/auth/callback/page.tsx | 20 +++++++++++++++++--- app/welcome/page.tsx | 19 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index 631d19e..4891587 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState, Suspense, useRef, useCallback } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { useAuth } from '@/lib/auth/context' -import { AUTH_URL } from '@/lib/api/client' +import { AUTH_URL, default as apiRequest } from '@/lib/api/client' import { exchangeAuthCode, setSessionAction } from '@/app/actions/auth' import { authMessageFromErrorType, type AuthErrorType } from '@/lib/utils/authErrors' import { LoadingOverlay } from '@ciphera-net/ui' @@ -23,7 +23,14 @@ function AuthCallbackContent() { if (!code || !codeVerifier) return const result = await exchangeAuthCode(code, codeVerifier, redirectUri) if (result.success && result.user) { - login(result.user) + // * Fetch full profile (including display_name) before navigating so header shows correct name on first paint + try { + const fullProfile = await apiRequest<{ id: string; email: string; display_name?: string; totp_enabled: boolean; org_id?: string; role?: string }>('/auth/user/me') + const merged = { ...fullProfile, org_id: result.user.org_id ?? fullProfile.org_id, role: result.user.role ?? fullProfile.role } + login(merged) + } catch { + login(result.user) + } localStorage.removeItem('oauth_state') localStorage.removeItem('oauth_code_verifier') if (localStorage.getItem('pulse_pending_checkout')) { @@ -51,7 +58,14 @@ function AuthCallbackContent() { const handleDirectTokens = async () => { const result = await setSessionAction(token, refreshToken) if (result.success && result.user) { - login(result.user) + // * Fetch full profile (including display_name) before navigating so header shows correct name on first paint + try { + const fullProfile = await apiRequest<{ id: string; email: string; display_name?: string; totp_enabled: boolean; org_id?: string; role?: string }>('/auth/user/me') + const merged = { ...fullProfile, org_id: result.user.org_id ?? fullProfile.org_id, role: result.user.role ?? fullProfile.role } + login(merged) + } catch { + login(result.user) + } if (typeof window !== 'undefined' && localStorage.getItem('pulse_pending_checkout')) { router.push('/welcome') } else { diff --git a/app/welcome/page.tsx b/app/welcome/page.tsx index 1c3f64f..c16dff5 100644 --- a/app/welcome/page.tsx +++ b/app/welcome/page.tsx @@ -20,6 +20,7 @@ import { createCheckoutSession } from '@/lib/api/billing' import { createSite, type Site } from '@/lib/api/sites' import { setSessionAction } from '@/app/actions/auth' import { useAuth } from '@/lib/auth/context' +import apiRequest from '@/lib/api/client' import { getAuthErrorMessage } from '@/lib/utils/authErrors' import { trackWelcomeStepView, @@ -147,13 +148,19 @@ function WelcomeContent() { const { access_token } = await switchContext(org.organization_id) const result = await setSessionAction(access_token) if (result.success && result.user) { - login(result.user) + try { + const fullProfile = await apiRequest<{ id: string; email: string; display_name?: string; totp_enabled: boolean; org_id?: string; role?: string }>('/auth/user/me') + const merged = { ...fullProfile, org_id: result.user.org_id ?? fullProfile.org_id, role: result.user.role ?? fullProfile.role } + login(merged) + } catch { + login(result.user) + } router.refresh() trackWelcomeWorkspaceSelected() setStep(3) } } catch (err) { - toast.error(getAuthErrorMessage(err) || 'Failed to switch workspace') + toast.error(getAuthErrorMessage(err) || 'Failed to switch organization') } finally { setSwitchingOrgId(null) } @@ -178,7 +185,13 @@ function WelcomeContent() { const { access_token } = await switchContext(org.id) const result = await setSessionAction(access_token) if (result.success && result.user) { - login(result.user) + try { + const fullProfile = await apiRequest<{ id: string; email: string; display_name?: string; totp_enabled: boolean; org_id?: string; role?: string }>('/auth/user/me') + const merged = { ...fullProfile, org_id: result.user.org_id ?? fullProfile.org_id, role: result.user.role ?? fullProfile.role } + login(merged) + } catch { + login(result.user) + } router.refresh() } trackWelcomeWorkspaceCreated(!!(typeof window !== 'undefined' && localStorage.getItem('pulse_pending_checkout'))) From 2f42f4eb98954e34d3bd6900e053071d8be16d66 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 8 Feb 2026 21:38:46 +0100 Subject: [PATCH 04/10] feat: enhance loading state and organization selection UI on welcome page --- app/welcome/page.tsx | 95 +++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/app/welcome/page.tsx b/app/welcome/page.tsx index c16dff5..666b541 100644 --- a/app/welcome/page.tsx +++ b/app/welcome/page.tsx @@ -368,50 +368,73 @@ function WelcomeContent() { className={cardClass} > {orgsLoading ? ( -
-

Loading your workspaces...

+
+
+

Loading your organizations...

) : organizations && organizations.length > 0 ? ( <> -
-
- +
+
+
-

- Choose your workspace +

+ Choose your organization

-

- Continue with an existing workspace or create a new one. +

+ Continue with an existing one or create a new organization.

-
- {organizations.map((org) => ( - - ))} +
+ {organizations.map((org, index) => { + const isCurrent = user?.org_id === org.organization_id + const initial = (org.organization_name || 'O').charAt(0).toUpperCase() + return ( + handleSelectWorkspace(org)} + disabled={!!switchingOrgId} + initial={{ opacity: 0, y: 8 }} + animate={{ opacity: 1, y: 0 }} + transition={{ delay: index * 0.04, duration: 0.2 }} + className={`w-full flex items-center gap-3 rounded-xl border px-4 py-3.5 text-left transition-all duration-200 disabled:opacity-60 ${ + isCurrent + ? 'border-brand-orange/60 bg-brand-orange/5 dark:bg-brand-orange/10 shadow-sm' + : 'border-neutral-200 dark:border-neutral-700 bg-neutral-50/80 dark:bg-neutral-800/50 hover:bg-neutral-100 dark:hover:bg-neutral-800 hover:border-neutral-300 dark:hover:border-neutral-600 hover:shadow-sm' + }`} + > +
+ {initial} +
+ + {org.organization_name || 'Organization'} + + {isCurrent && ( + Current + )} + +
+ ) + })} +
+
+
- ) : (
From 7a544283343018d5b50e775d8e71905d50ff9b1e Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Mon, 9 Feb 2026 08:21:36 +0100 Subject: [PATCH 05/10] fix: improve loading state handling and enhance organization selection UI on welcome page --- app/integrations/nextjs/page.tsx | 129 ++++++++++++++++++++++++++++ app/integrations/react/page.tsx | 119 +++++++++++++++++++++++++ app/integrations/vue/page.tsx | 113 ++++++++++++++++++++++++ app/integrations/wordpress/page.tsx | 81 +++++++++++++++++ 4 files changed, 442 insertions(+) create mode 100644 app/integrations/nextjs/page.tsx create mode 100644 app/integrations/react/page.tsx create mode 100644 app/integrations/vue/page.tsx create mode 100644 app/integrations/wordpress/page.tsx diff --git a/app/integrations/nextjs/page.tsx b/app/integrations/nextjs/page.tsx new file mode 100644 index 0000000..12ec2c4 --- /dev/null +++ b/app/integrations/nextjs/page.tsx @@ -0,0 +1,129 @@ +'use client' + +import Link from 'next/link' +import { ArrowLeftIcon } from '@ciphera-net/ui' + +export default function NextJsIntegrationPage() { + return ( +
+ {/* * --- ATMOSPHERE (Background) --- */} +
+
+
+
+
+ +
+ + + Back to Integrations + + +
+
+ + + +
+

+ Next.js Integration +

+
+ +
+

+ The best way to add Pulse to your Next.js application is using the built-in next/script component. +

+ +
+ +

Using App Router (Recommended)

+

+ Add the script to your root layout file (usually app/layout.tsx or app/layout.js). +

+ +
+
+ app/layout.tsx +
+
+
+{`import Script from 'next/script'
+
+export default function RootLayout({
+  children,
+}: {
+  children: React.ReactNode
+}) {
+  return (
+    
+      
+        
+    
+    My React App
+  
+  
+    
+ +`} +
+
+
+ +

Method 2: Programmatic Injection

+

+ If you need to load the script dynamically (e.g., only in production), you can use a useEffect hook in your main App component. +

+ +
+
+ src/App.tsx +
+
+
+{`import { useEffect } from 'react'
+
+function App() {
+  useEffect(() => {
+    // Only load in production
+    if (process.env.NODE_ENV === 'production') {
+      const script = document.createElement('script')
+      script.defer = true
+      script.setAttribute('data-domain', 'your-site.com')
+      script.src = 'https://pulse.ciphera.net/script.js'
+      document.head.appendChild(script)
+    }
+  }, [])
+
+  return (
+    
+

Hello World

+
+ ) +}`} +
+
+
+
+
+
+ ) +} diff --git a/app/integrations/vue/page.tsx b/app/integrations/vue/page.tsx new file mode 100644 index 0000000..40adb04 --- /dev/null +++ b/app/integrations/vue/page.tsx @@ -0,0 +1,113 @@ +'use client' + +import Link from 'next/link' +import { ArrowLeftIcon } from '@ciphera-net/ui' + +export default function VueIntegrationPage() { + return ( +
+ {/* * --- ATMOSPHERE (Background) --- */} +
+
+
+
+
+ +
+ + + Back to Integrations + + +
+
+ + + + +
+

+ Vue.js Integration +

+
+ +
+

+ Integrating Pulse with Vue.js is straightforward. You can add the script to your index.html file. +

+ +
+ +

Method 1: index.html (Recommended)

+

+ Add the script tag to the <head> section of your index.html file. This works for both Vue 2 and Vue 3 projects created with Vue CLI or Vite. +

+ +
+
+ index.html +
+
+
+{`
+
+  
+    
+    
+    
+    
+    
+    
+    My Vue App
+  
+  
+    
+ + +`} +
+
+
+ +

Method 2: Nuxt.js

+

+ For Nuxt.js applications, you should add the script to your nuxt.config.js or nuxt.config.ts file. +

+ +
+
+ nuxt.config.ts +
+
+
+{`export default defineNuxtConfig({
+  app: {
+    head: {
+      script: [
+        {
+          src: 'https://pulse.ciphera.net/script.js',
+          defer: true,
+          'data-domain': 'your-site.com'
+        }
+      ]
+    }
+  }
+})`}
+              
+
+
+
+
+
+ ) +} diff --git a/app/integrations/wordpress/page.tsx b/app/integrations/wordpress/page.tsx new file mode 100644 index 0000000..a1db8ef --- /dev/null +++ b/app/integrations/wordpress/page.tsx @@ -0,0 +1,81 @@ +'use client' + +import Link from 'next/link' +import { ArrowLeftIcon } from '@ciphera-net/ui' + +export default function WordPressIntegrationPage() { + return ( +
+ {/* * --- ATMOSPHERE (Background) --- */} +
+
+
+
+
+ +
+ + + Back to Integrations + + +
+
+ + + +
+

+ WordPress Integration +

+
+ +
+

+ You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin. +

+ +
+ +

Method 1: Using a Plugin (Easiest)

+
    +
  1. Install a plugin like "Insert Headers and Footers" (WPCode).
  2. +
  3. Go to the plugin settings and find the "Scripts in Header" section.
  4. +
  5. Paste the following code snippet:
  6. +
+ +
+
+ Header Script +
+
+
+{``}
+              
+
+
+ +

Method 2: Edit Theme Files (Advanced)

+

+ If you are comfortable editing your theme files, you can add the script directly to your header.php file. +

+
    +
  1. Go to Appearance > Theme File Editor.
  2. +
  3. Select header.php from the right sidebar.
  4. +
  5. Paste the script tag just before the closing </head> tag.
  6. +
+
+
+
+ ) +} From a3e9bac119edfbdf673f99dbe95de6ed5d2e9ee2 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Mon, 9 Feb 2026 09:15:55 +0100 Subject: [PATCH 06/10] fix: reset processing state on retry in auth callback and update loading overlay title in welcome page --- app/auth/callback/page.tsx | 1 + app/welcome/page.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index 4891587..e364b31 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -105,6 +105,7 @@ function AuthCallbackContent() { const handleRetry = () => { setError(null) + processedRef.current = false setIsRetrying(true) } diff --git a/app/welcome/page.tsx b/app/welcome/page.tsx index 666b541..6112548 100644 --- a/app/welcome/page.tsx +++ b/app/welcome/page.tsx @@ -317,7 +317,7 @@ function WelcomeContent() { } if (switchingOrgId) { - return + return } if (redirectingCheckout || (planLoading && step === 3)) { From ead1e006dce8f11c7e02ea2f4b6ed23a8a7f1935 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Mon, 9 Feb 2026 09:38:44 +0100 Subject: [PATCH 07/10] fix: validate returnTo parameter in auth callback and update default organization name in welcome page --- app/auth/callback/page.tsx | 5 +++-- app/welcome/page.tsx | 4 ++-- lib/auth/context.tsx | 15 +++++++++------ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index e364b31..21be4cb 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -69,8 +69,9 @@ function AuthCallbackContent() { if (typeof window !== 'undefined' && localStorage.getItem('pulse_pending_checkout')) { router.push('/welcome') } else { - const returnTo = searchParams.get('returnTo') || '/' - router.push(returnTo) + const raw = searchParams.get('returnTo') || '/' + const safe = (typeof raw === 'string' && raw.startsWith('/') && !raw.startsWith('//')) ? raw : '/' + router.push(safe) } } else { setError(authMessageFromErrorType('invalid')) diff --git a/app/welcome/page.tsx b/app/welcome/page.tsx index 6112548..67f3fbd 100644 --- a/app/welcome/page.tsx +++ b/app/welcome/page.tsx @@ -47,12 +47,12 @@ import Link from 'next/link' import ScriptSetupBlock from '@/components/sites/ScriptSetupBlock' const TOTAL_STEPS = 5 -const DEFAULT_ORG_NAME = 'My workspace' +const DEFAULT_ORG_NAME = 'My organization' const SITE_DRAFT_KEY = 'pulse_welcome_site_draft' const WELCOME_COMPLETED_KEY = 'pulse_welcome_completed' function slugFromName(name: string): string { - return name.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'my-workspace' + return name.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'my-organization' } function suggestSlugVariant(slug: string): string { diff --git a/lib/auth/context.tsx b/lib/auth/context.tsx index 0a8245f..61e2b45 100644 --- a/lib/auth/context.tsx +++ b/lib/auth/context.tsx @@ -167,12 +167,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { // * Update session cookie const result = await setSessionAction(access_token) if (result.success && result.user) { - setUser(result.user) - localStorage.setItem('user', JSON.stringify(result.user)) - - // * Force hard reload to ensure browser sends new cookie to backend - // * router.refresh() is not enough for Client Components fetching data immediately - // window.location.reload() + try { + const fullProfile = await apiRequest<{ id: string; email: string; display_name?: string; totp_enabled: boolean; org_id?: string; role?: string }>('/auth/user/me') + const merged = { ...fullProfile, org_id: result.user.org_id ?? fullProfile.org_id, role: result.user.role ?? fullProfile.role } + setUser(merged) + localStorage.setItem('user', JSON.stringify(merged)) + } catch { + setUser(result.user) + localStorage.setItem('user', JSON.stringify(result.user)) + } router.refresh() } } catch (e) { From 309ba53ec7fee6633cc1729c25c5041d95d4df27 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Mon, 9 Feb 2026 09:46:43 +0100 Subject: [PATCH 08/10] refactor: update terminology in welcome page for consistency, changing 'workspace' to 'organization' --- app/welcome/page.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/welcome/page.tsx b/app/welcome/page.tsx index 67f3fbd..4116fb3 100644 --- a/app/welcome/page.tsx +++ b/app/welcome/page.tsx @@ -142,7 +142,7 @@ function WelcomeContent() { } }, [user, step]) - const handleSelectWorkspace = async (org: OrganizationMember) => { + const handleSelectOrganization = async (org: OrganizationMember) => { setSwitchingOrgId(org.organization_id) try { const { access_token } = await switchContext(org.organization_id) @@ -166,7 +166,7 @@ function WelcomeContent() { } } - const handleCreateNewWorkspace = () => setStep(2) + const handleCreateNewOrganization = () => setStep(2) const handleNameChange = (e: React.ChangeEvent) => { const val = e.target.value @@ -313,7 +313,7 @@ function WelcomeContent() { }, [step, siteName, siteDomain]) if (orgLoading && step === 2) { - return + return } if (switchingOrgId) { @@ -393,7 +393,7 @@ function WelcomeContent() { handleSelectWorkspace(org)} + onClick={() => handleSelectOrganization(org)} disabled={!!switchingOrgId} initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} @@ -429,7 +429,7 @@ function WelcomeContent() { type="button" variant="secondary" className="w-full border border-dashed border-neutral-300 dark:border-neutral-600 hover:border-brand-orange/50 hover:bg-brand-orange/5 dark:hover:bg-brand-orange/10" - onClick={handleCreateNewWorkspace} + onClick={handleCreateNewOrganization} > Create a new organization From 03fcfba1803d213773a31f2f25ecab93091583eb Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Mon, 9 Feb 2026 09:51:19 +0100 Subject: [PATCH 09/10] chore: update @ciphera-net/ui dependency to version 0.0.49 and refactor terminology in layout content for consistency --- app/auth/callback/page.tsx | 4 +++- app/layout-content.tsx | 6 +++--- app/welcome/page.tsx | 10 ++++++++-- package-lock.json | 8 ++++---- package.json | 2 +- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index 21be4cb..c3fe5ac 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -36,7 +36,9 @@ function AuthCallbackContent() { if (localStorage.getItem('pulse_pending_checkout')) { router.push('/welcome') } else { - router.push('/') + const raw = searchParams.get('returnTo') || '/' + const safe = (typeof raw === 'string' && raw.startsWith('/') && !raw.startsWith('//')) ? raw : '/' + router.push(safe) } } else { setError(authMessageFromErrorType(result.error as AuthErrorType)) diff --git a/app/layout-content.tsx b/app/layout-content.tsx index 320c367..abb2838 100644 --- a/app/layout-content.tsx +++ b/app/layout-content.tsx @@ -26,7 +26,7 @@ export default function LayoutContent({ children }: { children: React.ReactNode } }, [auth.user]) - const handleSwitchWorkspace = async (orgId: string | null) => { + const handleSwitchOrganization = async (orgId: string | null) => { if (!orgId) return // Pulse doesn't support personal workspace try { const { access_token } = await switchContext(orgId) @@ -56,9 +56,9 @@ export default function LayoutContent({ children }: { children: React.ReactNode appName="Pulse" orgs={orgs} activeOrgId={auth.user?.org_id} - onSwitchWorkspace={handleSwitchWorkspace} + onSwitchOrganization={handleSwitchOrganization} onCreateOrganization={handleCreateOrganization} - allowPersonalWorkspace={false} + allowPersonalOrganization={false} showFaq={false} showSecurity={false} showPricing={true} diff --git a/app/welcome/page.tsx b/app/welcome/page.tsx index 4116fb3..e56efa8 100644 --- a/app/welcome/page.tsx +++ b/app/welcome/page.tsx @@ -686,9 +686,15 @@ function WelcomeContent() { variant="primary" className="flex-1" disabled={siteLoading || !siteName.trim() || !siteDomain.trim()} - isLoading={siteLoading} > - Add site + {siteLoading ? ( + <> + + Adding... + + ) : ( + 'Add site' + )}

- Name your workspace + Name your organization

You can change this later in settings. @@ -493,7 +493,7 @@ function WelcomeContent() {

- Used in your workspace URL. + Used in your organization URL.

{orgError && ( @@ -545,7 +545,7 @@ function WelcomeContent() { type="button" onClick={() => setStep(2)} className="flex items-center gap-1.5 text-sm text-neutral-500 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-300 mb-6" - aria-label="Back to workspace" + aria-label="Back to organization" > Back