diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/stores/outfits.svelte.ts b/apps/mana/apps/web/src/lib/modules/wardrobe/stores/outfits.svelte.ts index 4ebf28d6e..5ccb9efe1 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/stores/outfits.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/stores/outfits.svelte.ts @@ -9,6 +9,13 @@ import { encryptRecord } from '$lib/data/crypto'; import { emitDomainEvent } from '$lib/data/events'; +import { getActiveSpace } from '$lib/data/scope'; +import { getEffectiveUserId } from '$lib/data/current-user'; +import { + defaultVisibilityFor, + generateUnlistedToken, + type VisibilityLevel, +} from '@mana/shared-privacy'; import { wardrobeOutfitsTable } from '../collections'; import { toOutfit } from '../types'; import type { @@ -43,6 +50,7 @@ export const wardrobeOutfitsStore = { season: input.season, tags: input.tags ?? [], isFavorite: input.isFavorite ?? false, + visibility: defaultVisibilityFor(getActiveSpace()?.type), }; const snapshot = toOutfit({ ...newLocal }); await encryptRecord('wardrobeOutfits', newLocal); @@ -122,4 +130,37 @@ export const wardrobeOutfitsStore = { outfitId: id, }); }, + + /** + * Flip an outfit's visibility. Enables the style-portfolio use + * case — mark curated outfits 'public' so they appear in the + * wardrobe.outfits embed on the owner's website. + */ + async setVisibility(id: string, next: VisibilityLevel): Promise { + const existing = await wardrobeOutfitsTable.get(id); + if (!existing) throw new Error(`Outfit ${id} not found`); + const before: VisibilityLevel = existing.visibility ?? 'space'; + if (before === next) return; + + const now = new Date().toISOString(); + const patch: Partial = { + visibility: next, + visibilityChangedAt: now, + visibilityChangedBy: getEffectiveUserId(), + updatedAt: now, + }; + if (next === 'unlisted' && !existing.unlistedToken) { + patch.unlistedToken = generateUnlistedToken(); + } else if (next !== 'unlisted' && existing.unlistedToken) { + patch.unlistedToken = undefined; + } + await wardrobeOutfitsTable.update(id, patch); + + emitDomainEvent('VisibilityChanged', 'wardrobe', 'wardrobeOutfits', id, { + recordId: id, + collection: 'wardrobeOutfits', + before, + after: next, + }); + }, }; diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts b/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts index c8387a948..feb0d635c 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts @@ -16,6 +16,7 @@ */ import type { BaseRecord } from '@mana/local-store'; +import type { VisibilityLevel } from '@mana/shared-privacy'; // ─── Garment ────────────────────────────────────────────────────── @@ -173,6 +174,10 @@ export interface LocalWardrobeOutfit extends BaseRecord { isArchived?: boolean; lastTryOn?: OutfitTryOn | null; lastWornAt?: string | null; + visibility?: VisibilityLevel; + visibilityChangedAt?: string; + visibilityChangedBy?: string; + unlistedToken?: string; } export interface Outfit { @@ -187,6 +192,7 @@ export interface Outfit { isArchived?: boolean; lastTryOn?: OutfitTryOn; lastWornAt?: string; + visibility: VisibilityLevel; createdAt: string; updatedAt: string; } @@ -204,6 +210,7 @@ export function toOutfit(local: LocalWardrobeOutfit): Outfit { isArchived: local.isArchived, lastTryOn: local.lastTryOn ?? undefined, lastWornAt: local.lastWornAt ?? undefined, + visibility: local.visibility ?? 'space', createdAt: local.createdAt ?? '', updatedAt: local.updatedAt ?? '', }; diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailOutfitView.svelte b/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailOutfitView.svelte index 589af39ea..c109d5d21 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailOutfitView.svelte +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailOutfitView.svelte @@ -13,6 +13,7 @@
@@ -172,7 +178,12 @@ {/if}
-
+
+