diff --git a/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs b/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs index f53441f..5d690f1 100644 --- a/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs +++ b/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs @@ -247,14 +247,12 @@ public static class DevelopmentSeedExtensions { Id = OrganizationId, Name = string.Empty, - Slug = string.Empty, CreatedAt = DateTimeOffset.UtcNow, }; dbContext.Organizations.Add(organization); } organization.Name = "Northstar Collective"; - organization.Slug = "northstar-collective"; organization.OwnerUserId = managerUserId; await UpsertOrganizationMembershipAsync( diff --git a/backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.Designer.cs b/backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.Designer.cs index c0e3d53..fc0c652 100644 --- a/backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.Designer.cs +++ b/backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.Designer.cs @@ -1225,18 +1225,10 @@ namespace Socialize.Api.Migrations b.Property("OwnerUserId") .HasColumnType("uuid"); - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - b.HasKey("Id"); b.HasIndex("OwnerUserId"); - b.HasIndex("Slug") - .IsUnique(); - b.ToTable("Organizations", (string)null); }); diff --git a/backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.cs b/backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.cs index 3ce2930..deb5dd1 100644 --- a/backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.cs +++ b/backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.cs @@ -24,7 +24,6 @@ namespace Socialize.Api.Migrations { Id = table.Column(type: "uuid", nullable: false), Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - Slug = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), OwnerUserId = table.Column(type: "uuid", nullable: false), CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP") }, @@ -56,8 +55,8 @@ namespace Socialize.Api.Migrations migrationBuilder.Sql( """ - INSERT INTO "Organizations" ("Id", "Name", "Slug", "OwnerUserId", "CreatedAt") - VALUES ('99999999-9999-9999-9999-999999999999', 'Northstar Collective', 'northstar-collective', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', CURRENT_TIMESTAMP); + INSERT INTO "Organizations" ("Id", "Name", "OwnerUserId", "CreatedAt") + VALUES ('99999999-9999-9999-9999-999999999999', 'Northstar Collective', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', CURRENT_TIMESTAMP); UPDATE "Workspaces" SET "OrganizationId" = '99999999-9999-9999-9999-999999999999' @@ -93,12 +92,6 @@ namespace Socialize.Api.Migrations table: "Organizations", column: "OwnerUserId"); - migrationBuilder.CreateIndex( - name: "IX_Organizations_Slug", - table: "Organizations", - column: "Slug", - unique: true); - migrationBuilder.AddForeignKey( name: "FK_Workspaces_Organizations_OrganizationId", table: "Workspaces", diff --git a/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs b/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs index fe3b110..1633138 100644 --- a/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs +++ b/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs @@ -1222,18 +1222,10 @@ namespace Socialize.Api.Migrations b.Property("OwnerUserId") .HasColumnType("uuid"); - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - b.HasKey("Id"); b.HasIndex("OwnerUserId"); - b.HasIndex("Slug") - .IsUnique(); - b.ToTable("Organizations", (string)null); }); diff --git a/backend/src/Socialize.Api/Modules/Organizations/Data/Organization.cs b/backend/src/Socialize.Api/Modules/Organizations/Data/Organization.cs index be3ec35..cab2b17 100644 --- a/backend/src/Socialize.Api/Modules/Organizations/Data/Organization.cs +++ b/backend/src/Socialize.Api/Modules/Organizations/Data/Organization.cs @@ -4,7 +4,6 @@ public class Organization { public Guid Id { get; init; } public required string Name { get; set; } - public required string Slug { get; set; } public Guid OwnerUserId { get; set; } public DateTimeOffset CreatedAt { get; init; } } diff --git a/backend/src/Socialize.Api/Modules/Organizations/Data/OrganizationModelConfiguration.cs b/backend/src/Socialize.Api/Modules/Organizations/Data/OrganizationModelConfiguration.cs index e622838..e1a0f68 100644 --- a/backend/src/Socialize.Api/Modules/Organizations/Data/OrganizationModelConfiguration.cs +++ b/backend/src/Socialize.Api/Modules/Organizations/Data/OrganizationModelConfiguration.cs @@ -11,11 +11,9 @@ public static class OrganizationModelConfiguration organization.ToTable("Organizations"); organization.HasKey(x => x.Id); organization.Property(x => x.Name).HasMaxLength(256).IsRequired(); - organization.Property(x => x.Slug).HasMaxLength(128).IsRequired(); organization.Property(x => x.CreatedAt) .ValueGeneratedOnAdd() .HasDefaultValueSql("CURRENT_TIMESTAMP"); - organization.HasIndex(x => x.Slug).IsUnique(); organization.HasIndex(x => x.OwnerUserId); }); diff --git a/backend/src/Socialize.Api/Modules/Organizations/Handlers/OrganizationDtos.cs b/backend/src/Socialize.Api/Modules/Organizations/Handlers/OrganizationDtos.cs index 1d42960..382ccb7 100644 --- a/backend/src/Socialize.Api/Modules/Organizations/Handlers/OrganizationDtos.cs +++ b/backend/src/Socialize.Api/Modules/Organizations/Handlers/OrganizationDtos.cs @@ -15,7 +15,6 @@ public record OrganizationMemberDto( public record OrganizationDto( Guid Id, string Name, - string Slug, Guid OwnerUserId, IReadOnlyCollection CurrentUserPermissions, IReadOnlyCollection Members, @@ -31,7 +30,6 @@ public record OrganizationDto( return new OrganizationDto( organization.Id, organization.Name, - organization.Slug, organization.OwnerUserId, currentUserPermissions, members ?? [], diff --git a/docs/TASKS/organizations/001-organization-domain-foundation.md b/docs/TASKS/organizations/001-organization-domain-foundation.md index 4a602b1..9b89324 100644 --- a/docs/TASKS/organizations/001-organization-domain-foundation.md +++ b/docs/TASKS/organizations/001-organization-domain-foundation.md @@ -17,7 +17,7 @@ Existing local data does not need to be preserved. ## Scope - Add an `Organizations` backend module or follow the existing ownership pattern if organization code belongs with `Workspaces`. -- Add an organization persistence model with `Id`, `Name`, `Slug`, `OwnerUserId`, and `CreatedAt`, matching local conventions. +- Add an organization persistence model with `Id`, `Name`, `OwnerUserId`, and `CreatedAt`, matching local conventions. - Require every workspace to belong to exactly one organization. - Update workspace create/list/detail APIs to include organization ownership. - Add current-user organization read APIs: @@ -46,7 +46,6 @@ Existing local data does not need to be preserved. - The first implementation may grant organization access through `Organization.OwnerUserId == currentUserId` and existing manager/administrator access. Full organization membership belongs to task 002. - `CreateWorkspaceRequest` should require `OrganizationId`; reject creation when the user cannot manage that organization. - `WorkspaceDto` should include `OrganizationId`. -- Slugs should keep the existing lowercase kebab-case validation used for workspaces. - Use tests for unauthorized organization detail access and workspace creation under an inaccessible organization. ## Likely Files diff --git a/frontend/src/api/schema.d.ts b/frontend/src/api/schema.d.ts index 81d0874..68160da 100644 --- a/frontend/src/api/schema.d.ts +++ b/frontend/src/api/schema.d.ts @@ -993,7 +993,6 @@ export interface components { /** Format: guid */ id?: string; name?: string; - slug?: string; /** Format: guid */ ownerUserId?: string; currentUserPermissions?: string[]; diff --git a/frontend/src/features/organizations/views/OrganizationSettingsView.vue b/frontend/src/features/organizations/views/OrganizationSettingsView.vue index 45bea32..25defd3 100644 --- a/frontend/src/features/organizations/views/OrganizationSettingsView.vue +++ b/frontend/src/features/organizations/views/OrganizationSettingsView.vue @@ -1,5 +1,5 @@ @@ -233,12 +244,6 @@ @apply mx-auto flex w-full max-w-6xl flex-col gap-6 px-5 py-8 md:px-8; } - .settings-hero { - @apply grid gap-4 rounded-[1.25rem] border p-6 md:grid-cols-[minmax(0,1fr)_auto] md:items-end md:p-8; - background: rgba(255, 255, 255, 0.94); - border-color: rgba(23, 32, 51, 0.08); - } - .eyebrow { @apply text-xs font-bold uppercase tracking-[0.2em]; color: #c2410c; @@ -250,7 +255,6 @@ } .settings-hero p, - .settings-summary span, .section-heading p, .detail-list span, .table-row span, @@ -260,45 +264,53 @@ color: #526178; } - .settings-summary { - @apply flex flex-col gap-1 rounded-[1rem] px-4 py-3; - background: rgba(23, 32, 51, 0.05); + .settings-page { + @apply flex flex-col gap-5; } - .settings-summary strong { - @apply text-sm; - color: #172033; + .settings-tabs { + @apply flex flex-wrap gap-2 border-b pb-3; + border-color: rgba(23, 32, 51, 0.1); } - .settings-grid { - @apply grid gap-4 lg:grid-cols-2; + .settings-tab { + @apply inline-flex h-10 items-center gap-2 rounded-[0.75rem] px-3 text-sm font-semibold transition-colors; + color: #526178; } - .settings-section { - @apply flex flex-col gap-5 rounded-[1.25rem] border p-5; - background: rgba(255, 255, 255, 0.94); - border-color: rgba(23, 32, 51, 0.08); - } - - .permissions-section { - @apply lg:col-span-2; - } - - .section-heading { - @apply flex gap-4; - } - - .section-icon { - @apply flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-[1rem]; + .settings-tab:hover { background: rgba(23, 32, 51, 0.06); color: #172033; } + .settings-tab-active { + background: #172033; + color: #fffaf2; + } + + .settings-tab :deep(.v-icon) { + @apply text-lg; + } + + .settings-content { + @apply flex flex-col gap-4; + } + + .section-heading { + @apply flex flex-col gap-1; + } + .section-heading h2 { - @apply text-xl font-black; + @apply text-2xl font-black; color: #172033; } + .content-card { + @apply rounded-[0.75rem] border p-5; + background: rgba(255, 255, 255, 0.94); + border-color: rgba(23, 32, 51, 0.08); + } + .detail-list, .table-list { @apply flex flex-col gap-2; @@ -306,7 +318,7 @@ .detail-list div, .table-row { - @apply flex items-center justify-between gap-4 rounded-[1rem] px-4 py-3; + @apply flex items-center justify-between gap-4 rounded-[0.75rem] px-4 py-3; background: rgba(23, 32, 51, 0.04); } @@ -338,12 +350,17 @@ color: #c2410c; } + .permissions-panel, .placeholder-panel, .empty-state { - @apply rounded-[1rem] px-4 py-4; + @apply rounded-[0.75rem] px-4 py-4; background: rgba(23, 32, 51, 0.04); } + .permissions-panel { + @apply flex-col items-start gap-3; + } + .placeholder-panel { @apply flex flex-col gap-1; } diff --git a/frontend/src/layouts/main/WorkspaceSelector.vue b/frontend/src/layouts/main/WorkspaceSelector.vue index 9a171b8..727a94d 100644 --- a/frontend/src/layouts/main/WorkspaceSelector.vue +++ b/frontend/src/layouts/main/WorkspaceSelector.vue @@ -171,7 +171,7 @@ {{ organization.name.slice(0, 1).toUpperCase() }} {{ organization.name }} - {{ organization.slug }} + {{ t('workspaceSelector.organizationLabel') }} diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index f2c604d..4db4d74 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -72,10 +72,8 @@ "title": "Organization settings", "description": "Manage the SaaS account boundary for members, billing access, connections, and owned workspaces.", "loading": "Loading organization settings...", - "slugLabel": "Account slug", "fields": { "name": "Name", - "slug": "Slug", "createdAt": "Created" }, "sections": { diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 5c328e3..ae7c5fe 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -72,10 +72,8 @@ "title": "Parametres de l'organisation", "description": "Gerez le compte SaaS pour les membres, la facturation, les connexions et les espaces detenus.", "loading": "Chargement des parametres de l'organisation...", - "slugLabel": "Slug du compte", "fields": { "name": "Nom", - "slug": "Slug", "createdAt": "Cree" }, "sections": { diff --git a/shared/openapi/openapi.json b/shared/openapi/openapi.json index 7f855b8..2d3ef3e 100644 --- a/shared/openapi/openapi.json +++ b/shared/openapi/openapi.json @@ -3225,9 +3225,6 @@ "name": { "type": "string" }, - "slug": { - "type": "string" - }, "ownerUserId": { "type": "string", "format": "guid"