@@ -141,6 +156,29 @@ export default {
}
return false
},
+
+ additionalTeams() {
+ return this.teams.filter((t) => t.name !== 'Default')
+ },
+
+ isOnDefaultTeam() {
+ if (!this.activeTeam) return true
+ return this.activeTeam.name === 'Default'
+ },
+
+ displayName() {
+ if (!this.activeTeam || this.activeTeam.name === 'Default') {
+ return this.activeOrganization?.name || 'Workspace'
+ }
+ return this.activeTeam.name
+ },
+
+ displaySubtitle() {
+ if (!this.activeTeam || this.activeTeam.name === 'Default') {
+ return this.additionalTeams.length > 0 ? 'All projects' : 'Workspace'
+ }
+ return this.activeOrganization?.name || ''
+ },
},
async mounted() {
@@ -231,6 +269,12 @@ export default {
}
},
+ async switchToDefaultTeam() {
+ const defaultTeam = this.teams.find((t) => t.name === 'Default')
+ if (!defaultTeam || defaultTeam.id === this.activeTeam?.id) return
+ await this.setActiveTeam(defaultTeam)
+ },
+
async setActiveTeam(team) {
if (team.id === this.activeTeam?.id) return
diff --git a/middleware/auth.global.ts b/middleware/auth.global.ts
index f10c6c6..1b9f48b 100644
--- a/middleware/auth.global.ts
+++ b/middleware/auth.global.ts
@@ -9,7 +9,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
// On team subdomains, all routes are public. Redirect app routes to team root.
const teamSubdomain = useState('teamSubdomain')
if (teamSubdomain.value) {
- const appRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products', '/login', '/signup', '/auth']
+ const appRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products', '/login', '/signup', '/auth', '/onboarding']
if (appRoutes.some((r) => to.path.startsWith(r))) {
return navigateTo('/')
}
@@ -20,7 +20,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
const { data: session } = await authClient.useSession(useFetch)
// Protected routes that require authentication
- const protectedRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products']
+ const protectedRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products', '/onboarding']
// Auth routes that should redirect to dashboard if user is already logged in
const authRoutes = ['/login', '/signup', '/auth']
@@ -29,6 +29,8 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
const isAuthRoute = authRoutes.some((route) => to.path.startsWith(route))
+ const isOnboardingRoute = to.path.startsWith('/onboarding')
+
if (isProtectedRoute && !session.value?.user) {
// Redirect to login page if trying to access protected route without being logged in
return navigateTo('/login')
@@ -38,6 +40,29 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
if (isAuthRoute && session.value?.user) {
return navigateTo('/dashboard')
}
+
+ // Onboarding check: redirect users without organizations to onboarding
+ if (session.value?.user && isProtectedRoute && !isOnboardingRoute) {
+ const onboardingChecked = useState('onboarding-checked', () => false)
+ const needsOnboarding = useState('needs-onboarding', () => false)
+
+ if (!onboardingChecked.value) {
+ try {
+ const orgsResponse = await $fetch('/api/auth/organization/list')
+ const orgs = Array.isArray(orgsResponse) ? orgsResponse : (orgsResponse as any)?.data || []
+ needsOnboarding.value = orgs.length === 0
+ onboardingChecked.value = true
+ } catch (_error) {
+ // If check fails, don't block navigation
+ onboardingChecked.value = true
+ needsOnboarding.value = false
+ }
+ }
+
+ if (needsOnboarding.value) {
+ return navigateTo('/onboarding')
+ }
+ }
} catch (error) {
// If there's an error checking session, allow access but log the error
console.error('Auth middleware error:', error)
diff --git a/pages/login/index.vue b/pages/login/index.vue
index 7a21f47..b5e22d1 100644
--- a/pages/login/index.vue
+++ b/pages/login/index.vue
@@ -129,7 +129,17 @@ export default {
if (result.error) {
this.error = result.error.message || 'Sign in failed'
} else {
- // Success - redirect to dashboard
+ // Check if user has an organization; if not, redirect to onboarding
+ try {
+ const orgs = await authClient.organization.list()
+ const orgList = Array.isArray(orgs.data) ? orgs.data : []
+ if (orgList.length === 0) {
+ await navigateTo('/onboarding')
+ return
+ }
+ } catch (_e) {
+ // If check fails, proceed to dashboard
+ }
await navigateTo('/dashboard')
}
} catch (err) {
diff --git a/pages/onboarding/index.vue b/pages/onboarding/index.vue
new file mode 100644
index 0000000..8cecd42
--- /dev/null
+++ b/pages/onboarding/index.vue
@@ -0,0 +1,406 @@
+
+
+
+
+
+
+
+
+
+ {{ index + 1 }}
+
+
+
+
+
+
+
+
+
+ Create your workspace
+
+ A workspace is where your team collaborates on feedback and projects. You can think of it as your company or organization.
+
+
+
+
+
+
+
+
+
+
+
+ Invite your team
+
+ Veerify works best with your team. Add members now or invite them later from settings.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add another
+
+
+
+ {{ inviteError }}
+
+
+
+ {{ inviteSuccess }}
+
+
+
+
+
+ {{ isInviting ? 'Sending invitations...' : 'Send invitations' }}
+
+
+ Skip for now
+
+
+
+
+
+
+
+
+
+
+ You're all set!
+
+ Your workspace {{ createdOrgName }} is ready to go.
+
+
+
+
+
+
+
+
+
+
+
+
Collect feedback
+
Gather feature requests, bug reports, and ideas from your users.
+
+
+
+
+
+
+
+
Organize with projects
+
Create projects for different products or areas. Need more separation? Create additional teams later.
+
+
+
+
+
+
+
+
Collaborate with your team
+
Everyone in your workspace has access. Invite more members anytime from settings.
+
+
+
+
+
+ Go to dashboard
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/settings/index.vue b/pages/settings/index.vue
index cfad44f..884dfa0 100644
--- a/pages/settings/index.vue
+++ b/pages/settings/index.vue
@@ -52,8 +52,8 @@ const allTabs = [
{ key: 'profile', label: 'Profile', icon: 'lucide:user' },
{ key: 'security', label: 'Security', icon: 'lucide:shield' },
{ key: 'notifications', label: 'Notifications', icon: 'lucide:bell' },
- { key: 'organization', label: 'Organization', icon: 'lucide:building-2' },
- { key: 'team', label: 'Team', icon: 'lucide:users' },
+ { key: 'organization', label: 'Workspace', icon: 'lucide:building-2' },
+ { key: 'team', label: 'Teams', icon: 'lucide:users' },
{ key: 'billing', label: 'Billing', icon: 'lucide:credit-card' },
{ key: 'appearance', label: 'Appearance', icon: 'lucide:palette' },
]
diff --git a/pages/signup/index.vue b/pages/signup/index.vue
index 9be0048..e6bc22a 100644
--- a/pages/signup/index.vue
+++ b/pages/signup/index.vue
@@ -140,8 +140,8 @@ export default {
if (result.error) {
this.error = result.error.message || 'Sign up failed'
} else {
- // Success - redirect to dashboard
- await navigateTo('/dashboard')
+ // Success - redirect to onboarding to set up workspace
+ await navigateTo('/onboarding')
}
} catch (err) {
this.error = 'An unexpected error occurred'
From 95262bcd60ae6782056ea73119c4582c517a1951 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 15 Feb 2026 23:59:33 +0000
Subject: [PATCH 2/3] feat: add personal account layer with optional workspace
onboarding
Users now land on a personal dashboard after signup/login instead of
being forced through onboarding. The dashboard shows submission stats
and recent feedback when no workspace exists, with a CTA to create one.
- Add personal dashboard state for users without organizations
- Create /submissions page and GET /api/user/submissions endpoint
- Add always-visible Personal section to sidebar (My Submissions, Notifications)
- Remove forced onboarding redirect from middleware; onboarding is now CTA-accessible
- Add submissionMode column to project schema (anonymous vs account_required)
- Simplify login/signup to always redirect to /dashboard
https://claude.ai/code/session_014ACXuHTJ4gc7oH6woD9dXo
---
components/sidebar/AppSidebar.vue | 31 ++
middleware/auth.global.ts | 32 +-
pages/dashboard/index.vue | 455 +++++++++++++++++++++--------
pages/login/index.vue | 11 -
pages/onboarding/index.vue | 27 --
pages/signup/index.vue | 3 +-
pages/submissions/index.vue | 218 ++++++++++++++
server/api/user/submissions.get.ts | 59 ++++
server/database/schema/feedback.ts | 4 +
9 files changed, 645 insertions(+), 195 deletions(-)
create mode 100644 pages/submissions/index.vue
create mode 100644 server/api/user/submissions.get.ts
diff --git a/components/sidebar/AppSidebar.vue b/components/sidebar/AppSidebar.vue
index 2f6b8e8..515b3e8 100644
--- a/components/sidebar/AppSidebar.vue
+++ b/components/sidebar/AppSidebar.vue
@@ -20,6 +20,20 @@ const props = withDefaults(defineProps(), {
collapsible: 'icon',
})
+// Always-visible personal items
+const personalItems: SidebarNavItem[] = [
+ {
+ title: 'My Submissions',
+ url: '/submissions',
+ icon: 'lucide:list',
+ },
+ {
+ title: 'Notifications',
+ url: '/settings#notifications',
+ icon: 'lucide:bell',
+ },
+]
+
// Define navigation items
const feedbackItems: SidebarNavItem[] = [
{
@@ -80,6 +94,23 @@ const supportItems = [
+
+
+ Personal
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
Feedback
diff --git a/middleware/auth.global.ts b/middleware/auth.global.ts
index 1b9f48b..63d201a 100644
--- a/middleware/auth.global.ts
+++ b/middleware/auth.global.ts
@@ -9,7 +9,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
// On team subdomains, all routes are public. Redirect app routes to team root.
const teamSubdomain = useState('teamSubdomain')
if (teamSubdomain.value) {
- const appRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products', '/login', '/signup', '/auth', '/onboarding']
+ const appRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products', '/login', '/signup', '/auth', '/onboarding', '/submissions']
if (appRoutes.some((r) => to.path.startsWith(r))) {
return navigateTo('/')
}
@@ -20,7 +20,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
const { data: session } = await authClient.useSession(useFetch)
// Protected routes that require authentication
- const protectedRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products', '/onboarding']
+ const protectedRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products', '/onboarding', '/submissions']
// Auth routes that should redirect to dashboard if user is already logged in
const authRoutes = ['/login', '/signup', '/auth']
@@ -29,42 +29,14 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
const isAuthRoute = authRoutes.some((route) => to.path.startsWith(route))
- const isOnboardingRoute = to.path.startsWith('/onboarding')
-
if (isProtectedRoute && !session.value?.user) {
- // Redirect to login page if trying to access protected route without being logged in
return navigateTo('/login')
}
- // If user is logged in and trying to access auth routes, redirect to dashboard
if (isAuthRoute && session.value?.user) {
return navigateTo('/dashboard')
}
-
- // Onboarding check: redirect users without organizations to onboarding
- if (session.value?.user && isProtectedRoute && !isOnboardingRoute) {
- const onboardingChecked = useState('onboarding-checked', () => false)
- const needsOnboarding = useState('needs-onboarding', () => false)
-
- if (!onboardingChecked.value) {
- try {
- const orgsResponse = await $fetch('/api/auth/organization/list')
- const orgs = Array.isArray(orgsResponse) ? orgsResponse : (orgsResponse as any)?.data || []
- needsOnboarding.value = orgs.length === 0
- onboardingChecked.value = true
- } catch (_error) {
- // If check fails, don't block navigation
- onboardingChecked.value = true
- needsOnboarding.value = false
- }
- }
-
- if (needsOnboarding.value) {
- return navigateTo('/onboarding')
- }
- }
} catch (error) {
- // If there's an error checking session, allow access but log the error
console.error('Auth middleware error:', error)
}
})
diff --git a/pages/dashboard/index.vue b/pages/dashboard/index.vue
index 5ccad22..221e39c 100644
--- a/pages/dashboard/index.vue
+++ b/pages/dashboard/index.vue
@@ -1,170 +1,375 @@
-
-
Dashboard
-
Welcome to your Veerify feedback management dashboard
+
+
-
-
-
-
+
+
+
+
Welcome, {{ userName }}
+
This is your personal dashboard. Track your feedback submissions and manage your account.
-
-
-
-
-
Active Users
-
1,284
+
+
+
+
+
+
+
+
+
+
+
+
My Submissions
+
{{ submissionCount }}
+
+
+
+
+
+
+
+
+
+
Completed
+
{{ completedCount }}
+
+
+
+
+
+
+
+
+
+
Total Votes
+
{{ totalVotes }}
+
+
+
-
-
-
-
-
-
-
Features Shipped
-
18
-
+
+
+
+
+
+ Recent Submissions
+ Your latest feedback across all projects
+
+
+ View all
+
+
+
+
+
+
+
No submissions yet
+
+ Submit feedback on public boards to see your activity here.
+
+
+
+
+
+
{{ item.title }}
+
+ {{ item.projectName || 'Unknown project' }} ยท {{ formatDate(item.createdAt) }}
+
+
+
+
+ {{ formatStatus(item.status) }}
+
+
+
+ {{ item.voteCount }}
+
+
+
+
+
+
-
-
-
-
-
-
User Satisfaction
-
94%
-
+
+
+
+
+
+
+
+
+ Want to collect feedback?
+
+ Create a workspace to set up your own feedback boards, manage projects, and collaborate with your team.
+
+
+
+
+
+ Create a workspace
+
+
+
+
+
+
+
+ Quick Actions
+
+
+
+
+ View all submissions
+
+
+
+ Account settings
+
+
+
-
+
-
-
-
-
Recent Activity
+
+
+
+
Dashboard
+
Welcome to your Veerify feedback management dashboard
-
-
-
-
-
-
-
-
New feature request submitted
-
2 hours ago
+
+
+
+
+
-
-
-
-
-
-
Feature marked as completed
-
4 hours ago
+
+
+
+
-
-
-
-
-
-
New user registered
-
1 day ago
+
+
+
+
-
-
-
-
-
-
Feature received 10+ upvotes
-
2 days ago
+
+
+
+
+
+
+
User Satisfaction
+
94%
-
-
-
-
-
Top Feature Requests
-
-
-
-
-
-
-
+
+
+
+
Recent Activity
+
+
+
+
+
+
-
Dark mode support
-
Requested by 45 users
+
New feature request submitted
+
2 hours ago
-
-
- In Progress
-
- 45 votes
-
-
-
-
-
-
-
+
+
+
-
Mobile app
-
Requested by 32 users
+
Feature marked as completed
+
4 hours ago
-
-
- Planned
-
- 32 votes
-
-
-
-
-
-
-
+
+
+
-
Email notifications
-
Requested by 28 users
+
New user registered
+
1 day ago
-
-
- Under Review
-
-
28 votes
+
+
+
+
+
+
Feature received 10+ upvotes
+
2 days ago
+
-
+
+
+
diff --git a/pages/login/index.vue b/pages/login/index.vue
index b5e22d1..57437d3 100644
--- a/pages/login/index.vue
+++ b/pages/login/index.vue
@@ -129,17 +129,6 @@ export default {
if (result.error) {
this.error = result.error.message || 'Sign in failed'
} else {
- // Check if user has an organization; if not, redirect to onboarding
- try {
- const orgs = await authClient.organization.list()
- const orgList = Array.isArray(orgs.data) ? orgs.data : []
- if (orgList.length === 0) {
- await navigateTo('/onboarding')
- return
- }
- } catch (_e) {
- // If check fails, proceed to dashboard
- }
await navigateTo('/dashboard')
}
} catch (err) {
diff --git a/pages/onboarding/index.vue b/pages/onboarding/index.vue
index 8cecd42..4cbe347 100644
--- a/pages/onboarding/index.vue
+++ b/pages/onboarding/index.vue
@@ -245,8 +245,6 @@ export default {
inviteError: '',
inviteSuccess: '',
- // Page state
- isCheckingOrgs: true,
}
},
@@ -257,27 +255,7 @@ export default {
},
},
- async mounted() {
- await this.checkExistingOrganizations()
- },
-
methods: {
- async checkExistingOrganizations() {
- try {
- this.isCheckingOrgs = true
- const { data } = await authClient.organization.list()
- const orgs = Array.isArray(data) ? data : []
- if (orgs.length > 0) {
- // User already has an org, skip onboarding
- await navigateTo('/dashboard')
- }
- } catch (_error) {
- // If check fails, show onboarding anyway
- } finally {
- this.isCheckingOrgs = false
- }
- },
-
generateSlug() {
this.orgSlug = this.orgName
.toLowerCase()
@@ -394,11 +372,6 @@ export default {
},
async goToDashboard() {
- // Clear the onboarding check cache so middleware doesn't redirect back
- if (import.meta.client) {
- useState('onboarding-checked').value = true
- useState('needs-onboarding').value = false
- }
await navigateTo('/dashboard')
},
},
diff --git a/pages/signup/index.vue b/pages/signup/index.vue
index e6bc22a..6730d16 100644
--- a/pages/signup/index.vue
+++ b/pages/signup/index.vue
@@ -140,8 +140,7 @@ export default {
if (result.error) {
this.error = result.error.message || 'Sign up failed'
} else {
- // Success - redirect to onboarding to set up workspace
- await navigateTo('/onboarding')
+ await navigateTo('/dashboard')
}
} catch (err) {
this.error = 'An unexpected error occurred'
diff --git a/pages/submissions/index.vue b/pages/submissions/index.vue
new file mode 100644
index 0000000..b89e03a
--- /dev/null
+++ b/pages/submissions/index.vue
@@ -0,0 +1,218 @@
+
+
+
+
+
My Submissions
+
All feedback you've submitted across projects
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Failed to load submissions
+ {{ error }}
+
+
+ Try again
+
+
+
+
+
+
+
+
+ No submissions yet
+
+ When you submit feedback on public boards, your submissions will appear here so you can track their progress.
+
+
+
+
+
+
+
+
+
+ {{ filter.label }}
+
+ {{ filter.count }}
+
+
+
+
+
+
+
+
+
{{ item.title }}
+
+ {{ item.body }}
+
+
+
+
+ {{ item.projectName || 'Unknown project' }}
+
+ {{ formatDate(item.createdAt) }}
+
+
+ {{ item.commentCount }}
+
+
+
+
+
+ {{ formatStatus(item.status) }}
+
+
+
+ {{ item.voteCount }}
+
+
+
+
+
+
+
+
+ Page {{ page }} of {{ totalPages }} ({{ totalCount }} submissions)
+
+
+
+ Previous
+
+
+ Next
+
+
+
+
+
+
+
+
+
diff --git a/server/api/user/submissions.get.ts b/server/api/user/submissions.get.ts
new file mode 100644
index 0000000..51108af
--- /dev/null
+++ b/server/api/user/submissions.get.ts
@@ -0,0 +1,59 @@
+import { auth } from '~/lib/auth'
+import { db } from '~/server/database/drizzle'
+import { feedback, project } from '~/server/database/schema/feedback'
+import { eq, desc, sql } from 'drizzle-orm'
+
+export default defineEventHandler(async (event) => {
+ const session = await auth.api.getSession({
+ headers: event.node.req.headers as any,
+ })
+ if (!session?.user) {
+ throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
+ }
+
+ const query = getQuery(event)
+ const page = Math.max(1, parseInt(String(query.page || '1'), 10))
+ const limit = Math.min(50, Math.max(1, parseInt(String(query.limit || '20'), 10)))
+ const offset = (page - 1) * limit
+
+ // Get all feedback submitted by this user across all projects
+ const [items, countResult] = await Promise.all([
+ db
+ .select({
+ id: feedback.id,
+ title: feedback.title,
+ body: feedback.body,
+ status: feedback.status,
+ voteCount: feedback.voteCount,
+ commentCount: feedback.commentCount,
+ createdAt: feedback.createdAt,
+ updatedAt: feedback.updatedAt,
+ projectId: feedback.projectId,
+ projectName: project.name,
+ projectSlug: project.slug,
+ })
+ .from(feedback)
+ .leftJoin(project, eq(feedback.projectId, project.id))
+ .where(eq(feedback.authorUserId, session.user.id))
+ .orderBy(desc(feedback.createdAt))
+ .limit(limit)
+ .offset(offset),
+ db
+ .select({ count: sql
`count(*)::int` })
+ .from(feedback)
+ .where(eq(feedback.authorUserId, session.user.id)),
+ ])
+
+ const totalCount = countResult[0]?.count || 0
+
+ return {
+ success: true,
+ data: items,
+ pagination: {
+ page,
+ limit,
+ totalCount,
+ totalPages: Math.ceil(totalCount / limit),
+ },
+ }
+})
diff --git a/server/database/schema/feedback.ts b/server/database/schema/feedback.ts
index 0ab4a98..bd292ee 100644
--- a/server/database/schema/feedback.ts
+++ b/server/database/schema/feedback.ts
@@ -19,6 +19,10 @@ export const project = pgTable(
isPublic: boolean('is_public')
.$defaultFn(() => false)
.notNull(),
+ // Controls who can submit feedback: 'anonymous' allows anonymous + optional email, 'account_required' requires login
+ submissionMode: text('submission_mode')
+ .$defaultFn(() => 'anonymous')
+ .notNull(),
customDomain: text('custom_domain'),
settings: jsonb('settings').$type>(),
createdAt: timestamp('created_at')
From 33383b238e5e645c5fc1b0adecc5f04df8e3f88b Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 16 Feb 2026 09:30:26 +0000
Subject: [PATCH 3/3] feat: add /get-started route and context-aware sidebar
for personal users
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add a public /get-started page for external links (e.g., marketing site)
that routes authenticated users to onboarding and shows a signup CTA for
visitors. Signup and login now support a `redirect` query param so the
flow chains: /get-started โ /signup?redirect=/onboarding โ /onboarding.
The sidebar and settings page are now context-aware:
- Sidebar shows a "Create workspace" CTA in the header for personal users
instead of the TeamSwitcher, and hides Feedback/Management sections
- Settings page hides Workspace, Teams, and Billing tabs when user has no org
- Middleware honors redirect param on auth routes for logged-in users
- Dashboard link moved to always-visible Personal section
https://claude.ai/code/session_014ACXuHTJ4gc7oH6woD9dXo
---
components/sidebar/AppSidebar.vue | 112 +++++++++++++++++-----------
components/sidebar/TeamSwitcher.vue | 5 ++
middleware/auth.global.ts | 6 +-
pages/get-started/index.vue | 101 +++++++++++++++++++++++++
pages/login/index.vue | 11 ++-
pages/settings/index.vue | 10 ++-
pages/signup/index.vue | 11 ++-
7 files changed, 206 insertions(+), 50 deletions(-)
create mode 100644 pages/get-started/index.vue
diff --git a/components/sidebar/AppSidebar.vue b/components/sidebar/AppSidebar.vue
index 515b3e8..a6ff298 100644
--- a/components/sidebar/AppSidebar.vue
+++ b/components/sidebar/AppSidebar.vue
@@ -20,8 +20,16 @@ const props = withDefaults(defineProps(), {
collapsible: 'icon',
})
+// Shared state set by TeamSwitcher โ defaults to true to avoid flash of missing content
+const hasActiveOrganization = useState('hasActiveOrganization', () => true)
+
// Always-visible personal items
const personalItems: SidebarNavItem[] = [
+ {
+ title: 'Dashboard',
+ url: '/dashboard',
+ icon: 'lucide:layout-dashboard',
+ },
{
title: 'My Submissions',
url: '/submissions',
@@ -34,13 +42,8 @@ const personalItems: SidebarNavItem[] = [
},
]
-// Define navigation items
+// Workspace navigation items (only shown when org is active)
const feedbackItems: SidebarNavItem[] = [
- {
- title: 'Dashboard',
- url: '/dashboard',
- icon: 'lucide:layout-dashboard',
- },
{
title: 'Feedback',
url: '/feedback',
@@ -60,7 +63,7 @@ const feedbackItems: SidebarNavItem[] = [
},
]
-const managementItems = [
+const managementItems: SidebarNavItem[] = [
{
title: 'Products',
url: '/products',
@@ -73,7 +76,7 @@ const managementItems = [
},
]
-const supportItems = [
+const supportItems: SidebarNavItem[] = [
{
title: 'Help Center',
url: '/help',
@@ -90,7 +93,25 @@ const supportItems = [
-
+
+
+
+
+
+
+
+
+ Create workspace
+ Get started
+
+
+
+
+
@@ -111,45 +132,48 @@ const supportItems = [
-
-
- Feedback
-
-
-
-
-
- {{ item.title }}
-
-
-
+
+
+
+
+ Feedback
+
+
+
+
{{ item.title }}
-
-
-
-
-
-
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
-
-
- Management
-
-
-
-
-
-
- {{ item.title }}
-
-
-
-
-
-
+
+
+ Management
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
-
+
Support
diff --git a/components/sidebar/TeamSwitcher.vue b/components/sidebar/TeamSwitcher.vue
index 522aa6a..76e598a 100644
--- a/components/sidebar/TeamSwitcher.vue
+++ b/components/sidebar/TeamSwitcher.vue
@@ -191,6 +191,11 @@ export default {
this.activeTeam = teamSwitcherCache.activeTeam
this.activeOrganization = teamSwitcherCache.activeOrganization
this.isLoadingTeams = false
+
+ // Share org state with the sidebar and other components
+ if (import.meta.client) {
+ useState('hasActiveOrganization').value = !!this.activeOrganization
+ }
},
async fetchTeamContext() {
diff --git a/middleware/auth.global.ts b/middleware/auth.global.ts
index 63d201a..8f312b0 100644
--- a/middleware/auth.global.ts
+++ b/middleware/auth.global.ts
@@ -9,7 +9,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
// On team subdomains, all routes are public. Redirect app routes to team root.
const teamSubdomain = useState('teamSubdomain')
if (teamSubdomain.value) {
- const appRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products', '/login', '/signup', '/auth', '/onboarding', '/submissions']
+ const appRoutes = ['/dashboard', '/settings', '/reports', '/feedback', '/help', '/products', '/login', '/signup', '/auth', '/onboarding', '/submissions', '/get-started']
if (appRoutes.some((r) => to.path.startsWith(r))) {
return navigateTo('/')
}
@@ -34,6 +34,10 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
}
if (isAuthRoute && session.value?.user) {
+ const redirect = to.query.redirect
+ if (redirect && typeof redirect === 'string' && redirect.startsWith('/')) {
+ return navigateTo(redirect)
+ }
return navigateTo('/dashboard')
}
} catch (error) {
diff --git a/pages/get-started/index.vue b/pages/get-started/index.vue
new file mode 100644
index 0000000..560c89b
--- /dev/null
+++ b/pages/get-started/index.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Start collecting feedback
+
+ Create a workspace to set up feedback boards, manage projects, and collaborate with your team.
+
+
+
+
+
+
+
Public feedback boards for your users
+
+
+
+
Upvoting and prioritization
+
+
+
+
Team collaboration and project management
+
+
+
+
+ Create your workspace
+
+
+
+
+ Already have an account?
+
+ Sign in
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/login/index.vue b/pages/login/index.vue
index 57437d3..48cc680 100644
--- a/pages/login/index.vue
+++ b/pages/login/index.vue
@@ -70,7 +70,7 @@
Don't have an account?
- Sign up
+ Sign up
@@ -110,6 +110,12 @@ export default {
error: '',
}
},
+ computed: {
+ signupLink() {
+ const redirect = this.$route.query.redirect
+ return redirect ? `/signup?redirect=${encodeURIComponent(redirect)}` : '/signup'
+ },
+ },
methods: {
async handleSubmit() {
if (!this.email || !this.password) {
@@ -129,7 +135,8 @@ export default {
if (result.error) {
this.error = result.error.message || 'Sign in failed'
} else {
- await navigateTo('/dashboard')
+ const redirect = this.$route.query.redirect
+ await navigateTo(redirect && typeof redirect === 'string' && redirect.startsWith('/') ? redirect : '/dashboard')
}
} catch (err) {
this.error = 'An unexpected error occurred'
diff --git a/pages/settings/index.vue b/pages/settings/index.vue
index 884dfa0..f5984ea 100644
--- a/pages/settings/index.vue
+++ b/pages/settings/index.vue
@@ -83,11 +83,16 @@ export default {
return {
activeTab: 'profile',
currentOrgRole: null,
+ hasOrganization: false,
}
},
computed: {
availableTabs() {
return allTabs.filter((tab) => {
+ // Workspace-only tabs: hide when user has no org
+ if (['organization', 'team', 'billing'].includes(tab.key) && !this.hasOrganization) {
+ return false
+ }
if (tab.key === 'billing') {
return this.currentOrgRole === 'owner'
}
@@ -105,7 +110,7 @@ export default {
const validKeys = this.availableTabs.map((t) => t.key)
if (hash && validKeys.includes(hash)) {
this.activeTab = hash
- } else if (hash === 'billing' && !validKeys.includes('billing')) {
+ } else if (hash && !validKeys.includes(hash)) {
this.activeTab = 'profile'
window.history.replaceState(null, null, '#profile')
}
@@ -116,9 +121,12 @@ export default {
const activeOrg = await $fetch('/api/auth/organization/get-full-organization').catch(() => null)
if (!activeOrg?.id && !activeOrg?.name) {
this.currentOrgRole = null
+ this.hasOrganization = false
return
}
+ this.hasOrganization = true
+
let slug = activeOrg.slug || null
if (!slug) {
const listedOrgs = await $fetch('/api/auth/organization/list').catch(() => null)
diff --git a/pages/signup/index.vue b/pages/signup/index.vue
index 6730d16..7341590 100644
--- a/pages/signup/index.vue
+++ b/pages/signup/index.vue
@@ -75,7 +75,7 @@
Already have an account?
- Sign in
+ Sign in
@@ -115,6 +115,12 @@ export default {
error: '',
}
},
+ computed: {
+ loginLink() {
+ const redirect = this.$route.query.redirect
+ return redirect ? `/login?redirect=${encodeURIComponent(redirect)}` : '/login'
+ },
+ },
methods: {
async handleSubmit() {
if (!this.name || !this.email || !this.password) {
@@ -140,7 +146,8 @@ export default {
if (result.error) {
this.error = result.error.message || 'Sign up failed'
} else {
- await navigateTo('/dashboard')
+ const redirect = this.$route.query.redirect
+ await navigateTo(redirect && typeof redirect === 'string' && redirect.startsWith('/') ? redirect : '/dashboard')
}
} catch (err) {
this.error = 'An unexpected error occurred'