Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions components/settings/SettingsOrganization.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Icon name="lucide:building-2" class="h-5 w-5" />
<span data-testid="organization-title">Organization Settings</span>
<span data-testid="organization-title">Workspace Settings</span>
</CardTitle>
<CardDescription>Manage your organization profile and URL namespace.</CardDescription>
<CardDescription>Manage your workspace profile and URL namespace. Your workspace is the shared space where your team collaborates.</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<div v-if="isLoading" class="space-y-3">
Expand All @@ -17,27 +17,27 @@
</div>

<div v-else-if="!organizationDetails" class="text-sm text-muted-foreground">
No active organization context is available.
No active workspace context is available.
</div>

<template v-else>
<div class="space-y-2">
<Label for="organization-name">Organization Name</Label>
<Label for="organization-name">Workspace Name</Label>
<Input
id="organization-name"
data-testid="organization-name-input"
v-model="organizationName"

Check warning on line 29 in components/settings/SettingsOrganization.vue

View workflow job for this annotation

GitHub Actions / Lint

Attribute "v-model" should go before "data-testid"
:disabled="!canEditOrganization || isSaving"
placeholder="Organization name"
/>
</div>

<div class="space-y-2">
<Label for="organization-slug">Organization Slug</Label>
<Label for="organization-slug">Workspace URL</Label>
<Input
id="organization-slug"
data-testid="organization-slug-input"
v-model="organizationSlug"

Check warning on line 40 in components/settings/SettingsOrganization.vue

View workflow job for this annotation

GitHub Actions / Lint

Attribute "v-model" should go before "data-testid"
:disabled="!canEditOrganization || isSaving"
placeholder="organization-slug"
/>
Expand All @@ -51,7 +51,7 @@
<Input
id="organization-logo"
data-testid="organization-logo-input"
v-model="organizationLogo"

Check warning on line 54 in components/settings/SettingsOrganization.vue

View workflow job for this annotation

GitHub Actions / Lint

Attribute "v-model" should go before "data-testid"
:disabled="!canEditOrganization || isSaving"
placeholder="https://example.com/logo.png"
/>
Expand All @@ -59,7 +59,7 @@

<div class="flex items-center justify-between gap-3">
<p v-if="!canEditOrganization" class="text-sm text-muted-foreground">
Only owners and admins can update organization details.
Only owners and admins can update workspace details.
</p>
<div class="ml-auto">
<Button
Expand All @@ -68,7 +68,7 @@
@click="updateOrganization"
>
<Icon v-if="isSaving" name="lucide:loader-2" class="h-4 w-4 mr-2 animate-spin" />
Save Organization
Save Changes
</Button>
</div>
</div>
Expand All @@ -85,7 +85,7 @@
<Icon name="lucide:users" class="h-5 w-5" />
Members
</CardTitle>
<CardDescription>Manage organization members and their roles.</CardDescription>
<CardDescription>Manage workspace members and their roles. All members have access to shared projects.</CardDescription>
</div>
<Button v-if="canManageMembers" size="sm" @click="showInviteDialog = true">
<Icon name="lucide:user-plus" class="h-4 w-4 mr-2" />
Expand Down Expand Up @@ -120,9 +120,9 @@
<span v-if="m.userId === currentUserId" class="text-xs text-muted-foreground">(you)</span>
</p>
<p class="text-xs text-muted-foreground truncate">{{ m.email }}</p>
<div v-if="m.teams && m.teams.length > 0" class="flex flex-wrap gap-1 mt-1">
<div v-if="nonDefaultTeams(m.teams).length > 0" class="flex flex-wrap gap-1 mt-1">
<Badge
v-for="t in m.teams"
v-for="t in nonDefaultTeams(m.teams)"
:key="t.id"
variant="outline"
class="text-xs"
Expand Down Expand Up @@ -229,19 +229,19 @@
<Icon name="lucide:alert-triangle" class="h-5 w-5" />
Danger Zone
</CardTitle>
<CardDescription>Permanently delete this organization and all related data.</CardDescription>
<CardDescription>Permanently delete this workspace and all related data.</CardDescription>
</CardHeader>
<CardContent>
<p v-if="!canDeleteOrganization" class="text-sm text-muted-foreground mb-3">
Only organization owners can delete the organization.
Only workspace owners can delete the workspace.
</p>
<Button
data-testid="organization-open-delete-dialog"
variant="destructive"
:disabled="!canDeleteOrganization"
@click="showDeleteDialog = true"
>
Delete Organization
Delete Workspace
</Button>
</CardContent>
</Card>
Expand All @@ -254,7 +254,7 @@
<DialogDescription>
Are you sure you want to remove
<strong>{{ memberToRemove?.name || memberToRemove?.email }}</strong>
from this organization? They will lose access to all organization resources.
from this workspace? They will lose access to all workspace resources.
</DialogDescription>
</DialogHeader>
<DialogFooter>
Expand All @@ -276,7 +276,7 @@
<DialogContent>
<DialogHeader>
<DialogTitle>Invite Member</DialogTitle>
<DialogDescription>Send an invitation to join this organization.</DialogDescription>
<DialogDescription>Send an invitation to join this workspace.</DialogDescription>
</DialogHeader>
<div class="space-y-4 py-4">
<div class="space-y-2">
Expand Down Expand Up @@ -316,7 +316,7 @@
<Dialog :open="showDeleteDialog" @update:open="showDeleteDialog = $event">
<DialogContent>
<DialogHeader>
<DialogTitle class="text-red-600">Delete Organization</DialogTitle>
<DialogTitle class="text-red-600">Delete Workspace</DialogTitle>
<DialogDescription>
Type <strong>{{ organizationDetails?.slug }}</strong> to confirm permanent deletion.
</DialogDescription>
Expand All @@ -337,7 +337,7 @@
@click="deleteOrganization"
>
<Icon v-if="isDeleting" name="lucide:loader-2" class="h-4 w-4 mr-2 animate-spin" />
Delete Organization
Delete Workspace
</Button>
</DialogFooter>
</DialogContent>
Expand Down Expand Up @@ -445,7 +445,7 @@
}
} catch (error) {
console.error('Error loading organization settings:', error)
toast.error('Failed to load organization settings')
toast.error('Failed to load workspace settings')
} finally {
this.isLoading = false
}
Expand All @@ -458,6 +458,11 @@
this.organizationLogo = this.organizationDetails.logo || ''
},

nonDefaultTeams(teams) {
if (!teams || !Array.isArray(teams)) return []
return teams.filter((t) => t.name !== 'Default')
},

getInitials(name) {
if (!name) return '?'
return name
Expand Down Expand Up @@ -593,7 +598,7 @@
this.organizationDetails.memberCount--
this.showRemoveMemberDialog = false
this.memberToRemove = null
toast.success('Member removed from organization')
toast.success('Member removed from workspace')
} catch (error) {
console.error('Error removing member:', error)
toast.error(error?.data?.error?.message || 'Failed to remove member')
Expand Down Expand Up @@ -653,10 +658,10 @@
})
this.organizationDetails = response.data
this.setFormValues()
toast.success('Organization updated')
toast.success('Workspace updated')
} catch (error) {
console.error('Error updating organization:', error)
toast.error(error?.data?.error?.message || error?.statusMessage || 'Failed to update organization')
toast.error(error?.data?.error?.message || error?.statusMessage || 'Failed to update workspace')
} finally {
this.isSaving = false
}
Expand All @@ -675,11 +680,11 @@
this.showDeleteDialog = false
this.deleteConfirmation = ''
this.organizationDetails = null
toast.success('Organization deleted')
toast.success('Workspace deleted')
await navigateTo('/settings#team')
} catch (error) {
console.error('Error deleting organization:', error)
toast.error(error?.data?.error?.message || error?.statusMessage || 'Failed to delete organization')
toast.error(error?.data?.error?.message || error?.statusMessage || 'Failed to delete workspace')
} finally {
this.isDeleting = false
}
Expand Down
51 changes: 35 additions & 16 deletions components/settings/SettingsTeam.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
<template>
<div class="space-y-6">
<!-- Your Teams -->
<!-- Explanation -->
<Card>
<CardHeader>
<div class="flex items-center justify-between">
<div>
<CardTitle class="flex items-center gap-2">
<Icon name="lucide:users" class="h-5 w-5" />
<span data-testid="team-title">Your Teams</span>
<span data-testid="team-title">Teams</span>
</CardTitle>
<CardDescription>All teams you belong to across organizations.</CardDescription>
<CardDescription>
Your workspace operates as a single shared space by default. Create additional teams to organize different projects or departments separately.
</CardDescription>
</div>
<Button data-testid="team-open-create-dialog" size="sm" @click="showCreateTeamDialog = true">
<Icon name="lucide:plus" class="h-4 w-4 mr-2" />
Expand All @@ -23,16 +25,25 @@
<Skeleton class="h-14 w-full" />
</div>

<div v-else-if="allTeams.length === 0" class="text-center py-6">
<p class="text-muted-foreground">No teams found. Create a team to get started.</p>
<Button data-testid="team-open-create-dialog" class="mt-3" @click="showCreateTeamDialog = true">
Create Team
<div v-else-if="additionalTeams.length === 0" class="text-center py-8">
<div class="flex justify-center mb-4">
<div class="flex items-center justify-center w-12 h-12 rounded-full bg-muted">
<Icon name="lucide:users" class="w-6 h-6 text-muted-foreground" />
</div>
</div>
<p class="font-medium mb-1">No additional teams</p>
<p class="text-sm text-muted-foreground max-w-sm mx-auto">
All workspace members share access to projects. Create a team when you need separate groups for different projects or departments.
</p>
<Button data-testid="team-open-create-dialog" class="mt-4" variant="outline" @click="showCreateTeamDialog = true">
<Icon name="lucide:plus" class="h-4 w-4 mr-2" />
Create your first team
</Button>
</div>

<div v-else class="space-y-2">
<button
v-for="team in allTeams"
v-for="team in additionalTeams"
:key="team.id"
:data-testid="`team-list-item-${team.id}`"
class="w-full flex items-center justify-between rounded-lg border p-3 text-left transition-colors hover:bg-accent/50"
Expand All @@ -58,7 +69,7 @@
</Card>

<!-- Active Team Settings -->
<Card v-if="currentTeam">
<Card v-if="currentTeam && !isDefaultTeam(currentTeam)">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Icon name="lucide:settings" class="h-5 w-5" />
Expand Down Expand Up @@ -88,7 +99,7 @@
</Card>

<!-- Team Members -->
<Card v-if="currentTeam">
<Card v-if="currentTeam && !isDefaultTeam(currentTeam)">
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Icon name="lucide:user-plus" class="h-5 w-5" />
Expand Down Expand Up @@ -143,7 +154,7 @@
</Card>

<!-- Danger Zone -->
<Card v-if="currentTeam" class="border-red-200">
<Card v-if="currentTeam && !isDefaultTeam(currentTeam)" class="border-red-200">
<CardHeader>
<CardTitle class="flex items-center gap-2 text-red-600">
<Icon name="lucide:alert-triangle" class="h-5 w-5" />
Expand All @@ -160,7 +171,7 @@
<DialogContent>
<DialogHeader>
<DialogTitle>Create Team</DialogTitle>
<DialogDescription>Create a new operational team in your current organization.</DialogDescription>
<DialogDescription>Create a new team to organize a separate group of projects. Team members will have their own workspace context.</DialogDescription>
</DialogHeader>
<div class="space-y-4 py-4">
<div class="space-y-2">
Expand Down Expand Up @@ -194,7 +205,7 @@
<DialogContent>
<DialogHeader>
<DialogTitle>Invite to Team</DialogTitle>
<DialogDescription>Invite a user directly into this team workspace.</DialogDescription>
<DialogDescription>Invite a user directly into this team.</DialogDescription>
</DialogHeader>
<div class="space-y-4 py-4">
<div class="space-y-2">
Expand Down Expand Up @@ -289,6 +300,9 @@ export default {
(this.teamSlug.trim() && this.teamSlug.trim() !== this.currentTeam?.slug)
)
},
additionalTeams() {
return this.allTeams.filter((t) => !this.isDefaultTeam(t))
},
},

async mounted() {
Expand All @@ -306,6 +320,11 @@ export default {
},

methods: {
isDefaultTeam(team) {
if (!team) return false
return team.name === 'Default'
},

async fetchActiveTeam() {
try {
const response = await $fetch('/api/teams/active')
Expand Down Expand Up @@ -414,7 +433,7 @@ export default {
},

async loadTeamMembers() {
if (!this.currentTeam?.id) {
if (!this.currentTeam?.id || this.isDefaultTeam(this.currentTeam)) {
this.teamMembers = []
return
}
Expand Down Expand Up @@ -543,7 +562,7 @@ export default {

const organizationId = await this.resolveOrganizationId()
if (!organizationId) {
toast.error('No active organization context')
toast.error('No active workspace context')
return
}

Expand Down Expand Up @@ -596,7 +615,7 @@ export default {

const organizationId = await this.resolveOrganizationId()
if (!organizationId) {
toast.error('No active organization context')
toast.error('No active workspace context')
return
}

Expand Down
Loading
Loading