diff --git a/app/sites/[id]/settings/page.tsx b/app/sites/[id]/settings/page.tsx
index 8ea491f..32935e2 100644
--- a/app/sites/[id]/settings/page.tsx
+++ b/app/sites/[id]/settings/page.tsx
@@ -8,9 +8,10 @@ import { toast } from '@ciphera-net/ui'
import { getAuthErrorMessage } from '@/lib/utils/authErrors'
import { LoadingOverlay } from '@ciphera-net/ui'
import VerificationModal from '@/components/sites/VerificationModal'
+import ScriptSetupBlock from '@/components/sites/ScriptSetupBlock'
import { PasswordInput } from '@ciphera-net/ui'
import { Select, Modal, Button } from '@ciphera-net/ui'
-import { APP_URL, API_URL } from '@/lib/api/client'
+import { APP_URL } from '@/lib/api/client'
import { generatePrivacySnippet } from '@/lib/utils/privacySnippet'
import { motion, AnimatePresence } from 'framer-motion'
import { useAuth } from '@/lib/auth/context'
@@ -69,7 +70,6 @@ export default function SiteSettingsPage() {
// Bot and noise filtering
filter_bots: true
})
- const [scriptCopied, setScriptCopied] = useState(false)
const [linkCopied, setLinkCopied] = useState(false)
const [snippetCopied, setSnippetCopied] = useState(false)
const [showVerificationModal, setShowVerificationModal] = useState(false)
@@ -266,14 +266,6 @@ export default function SiteSettingsPage() {
}
}
- const copyScript = () => {
- const script = ``
- navigator.clipboard.writeText(script)
- setScriptCopied(true)
- toast.success('Script copied to clipboard')
- setTimeout(() => setScriptCopied(false), 2000)
- }
-
const copyLink = () => {
const link = `${APP_URL}/share/${siteId}`
navigator.clipboard.writeText(link)
@@ -443,23 +435,15 @@ export default function SiteSettingsPage() {
Tracking Script
- Add this script to your website to start tracking visitors.
+ Add this script to your website to start tracking visitors. Choose your framework for setup instructions.
-
-
- {``}
-
-
- {scriptCopied ? : }
-
-
+
-
+
setShowVerificationModal(true)}
diff --git a/app/sites/new/layout.tsx b/app/sites/new/layout.tsx
new file mode 100644
index 0000000..283f564
--- /dev/null
+++ b/app/sites/new/layout.tsx
@@ -0,0 +1,14 @@
+import type { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: 'Create site | Pulse',
+ description: 'Add a new site to start collecting privacy-friendly analytics.',
+}
+
+export default function NewSiteLayout({
+ children,
+}: {
+ children: React.ReactNode
+}) {
+ return children
+}
diff --git a/app/sites/new/page.tsx b/app/sites/new/page.tsx
index b4f0522..0ca6617 100644
--- a/app/sites/new/page.tsx
+++ b/app/sites/new/page.tsx
@@ -1,18 +1,19 @@
'use client'
-import { useState, useEffect, useCallback } from 'react'
+import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
-import { createSite, listSites, type Site } from '@/lib/api/sites'
+import { createSite, listSites, getSite, type Site } from '@/lib/api/sites'
import { getSubscription } from '@/lib/api/billing'
-import { API_URL, APP_URL } from '@/lib/api/client'
-import { integrations, getIntegration } from '@/lib/integrations'
+import { trackSiteCreatedFromDashboard, trackSiteCreatedScriptCopied } from '@/lib/welcomeAnalytics'
import { toast } from '@ciphera-net/ui'
import { getAuthErrorMessage } from '@/lib/utils/authErrors'
import { Button, Input } from '@ciphera-net/ui'
-import { CheckCircleIcon, CheckIcon } from '@ciphera-net/ui'
+import { CheckCircleIcon } from '@ciphera-net/ui'
+import ScriptSetupBlock from '@/components/sites/ScriptSetupBlock'
+import VerificationModal from '@/components/sites/VerificationModal'
-const popularIntegrations = integrations.filter((i) => i.category === 'framework').slice(0, 10)
+const LAST_CREATED_SITE_KEY = 'pulse_last_created_site'
export default function NewSitePage() {
const router = useRouter()
@@ -22,8 +23,30 @@ export default function NewSitePage() {
domain: '',
})
const [createdSite, setCreatedSite] = useState(null)
- const [selectedIntegrationSlug, setSelectedIntegrationSlug] = useState(null)
- const [scriptCopied, setScriptCopied] = useState(false)
+ const [showVerificationModal, setShowVerificationModal] = useState(false)
+ const [atLimit, setAtLimit] = useState(false)
+ const [limitsChecked, setLimitsChecked] = useState(false)
+
+ // * Restore step 2 from sessionStorage after refresh (e.g. pulse_last_created_site = { id } )
+ useEffect(() => {
+ if (createdSite || typeof window === 'undefined') return
+ try {
+ const raw = sessionStorage.getItem(LAST_CREATED_SITE_KEY)
+ if (!raw) return
+ const { id } = JSON.parse(raw) as { id?: string }
+ if (!id) return
+ getSite(id)
+ .then((site) => {
+ setCreatedSite(site)
+ setFormData({ name: site.name, domain: site.domain })
+ })
+ .catch(() => {
+ sessionStorage.removeItem(LAST_CREATED_SITE_KEY)
+ })
+ } catch {
+ sessionStorage.removeItem(LAST_CREATED_SITE_KEY)
+ }
+ }, [createdSite])
// * Check for plan limits on mount
useEffect(() => {
@@ -35,27 +58,20 @@ export default function NewSitePage() {
])
if (subscription?.plan_id === 'solo' && sites.length >= 1) {
+ setAtLimit(true)
toast.error('Solo plan limit reached (1 site). Please upgrade to add more sites.')
router.replace('/')
}
} catch (error) {
- // Ignore errors here, let the backend handle the hard check on submit
console.error('Failed to check limits', error)
+ } finally {
+ setLimitsChecked(true)
}
}
checkLimits()
}, [router])
- const copyScript = useCallback(() => {
- if (!createdSite) return
- const script = ``
- navigator.clipboard.writeText(script)
- setScriptCopied(true)
- toast.success('Script copied to clipboard')
- setTimeout(() => setScriptCopied(false), 2000)
- }, [createdSite])
-
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
@@ -64,6 +80,10 @@ export default function NewSitePage() {
const site = await createSite(formData)
toast.success('Site created successfully')
setCreatedSite(site)
+ trackSiteCreatedFromDashboard()
+ if (typeof window !== 'undefined') {
+ sessionStorage.setItem(LAST_CREATED_SITE_KEY, JSON.stringify({ id: site.id }))
+ }
} catch (error: unknown) {
toast.error(getAuthErrorMessage(error) || 'Failed to create site: ' + ((error as Error)?.message || 'Unknown error'))
} finally {
@@ -71,7 +91,17 @@ export default function NewSitePage() {
}
}
- // * Step 2: Show framework picker + script (same as /welcome after adding first site)
+ const handleBackToForm = () => {
+ setCreatedSite(null)
+ if (typeof window !== 'undefined') sessionStorage.removeItem(LAST_CREATED_SITE_KEY)
+ }
+
+ const goToDashboard = () => {
+ router.refresh()
+ router.push('/')
+ }
+
+ // * Step 2: Framework picker + script (same as /welcome after adding first site)
if (createdSite) {
return (
@@ -89,74 +119,38 @@ export default function NewSitePage() {
-
- Add the script to your site
-
-
- Choose your framework for setup instructions.
-
-
- {popularIntegrations.map((int) => (
- setSelectedIntegrationSlug(selectedIntegrationSlug === int.id ? null : int.id)}
- className={`flex items-center gap-2 rounded-lg border px-3 py-2.5 text-left text-sm transition-colors ${
- selectedIntegrationSlug === int.id
- ? 'border-brand-orange bg-brand-orange/10 text-brand-orange'
- : 'border-neutral-200 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800/50 hover:bg-neutral-100 dark:hover:bg-neutral-800 text-neutral-700 dark:text-neutral-300'
- }`}
- >
-
- {int.icon}
-
- {int.name}
-
- ))}
-
-
-
- View all integrations →
-
-
+
+
-
-
- {``}
-
-
- {scriptCopied ? (
-
- ) : (
-
-
-
-
- )}
-
-
- {selectedIntegrationSlug && getIntegration(selectedIntegrationSlug) && (
-
-
- See full {getIntegration(selectedIntegrationSlug)!.name} guide →
-
-
- )}
+
+
setShowVerificationModal(true)}
+ className="flex items-center gap-2 px-4 py-2 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 text-neutral-700 dark:text-neutral-300 rounded-xl hover:bg-neutral-50 dark:hover:bg-neutral-700 transition-all text-sm font-medium focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-offset-2"
+ >
+ Verify installation
+
+
+ Check if your site is sending data correctly.
+
+
+
+
+
+ Edit site details
+
- router.push('/')} className="min-w-[160px]">
+
Back to dashboard
router.push(`/sites/${createdSite.id}`)} className="min-w-[160px]">
@@ -164,6 +158,12 @@ export default function NewSitePage() {
+
+
setShowVerificationModal(false)}
+ site={createdSite}
+ />
)
}
@@ -175,6 +175,12 @@ export default function NewSitePage() {
Create New Site
+ {atLimit && limitsChecked && (
+
+ Plan limit reached. Upgrade to add more sites.
+
+ )}
+