velxio/frontend/src/store/useProjectStore.ts

46 lines
1.5 KiB
TypeScript
Raw Normal View History

import { create } from 'zustand';
feat(share-modal): D1.4 — 3-level visibility picker (public/unlisted/private) Phase 1 D1.4 — replaces the binary public/private toggle in ShareModal with three radio-button-styled options. Optimistic UI: every option renders for every user; the backend's 403 (with structured visibility_not_allowed detail) redirects to /pricing?from=visibility_X so the pricing page can lead the right pitch. Why optimistic-then-redirect instead of hiding/locking options: 1. Discovery — Free / Maker users SEE Pro unlocks Private. That's the exact conversion signal the pricing page is trying to surface. 2. Discovery without surprise — the locked click goes to /pricing with a hint, not a dead modal. 3. Less plan-coupling — this upstream component doesn't need to know about the pro overlay's plan store. Backend is the only source of truth for what's allowed. Touched: - ShareModal.tsx: full rewrite as a 3-option picker with badges (Maker / Pro) on the gated options. - projectService.ts: ProjectResponse / ProjectSaveData now declare `visibility?: 'public' | 'unlisted' | 'private'`. is_public stays declared for backward compat with old callers. - useProjectStore.ts: CurrentProject gains `visibility?`; setVisibility accepts EITHER the legacy boolean OR the new enum and keeps both fields coherent. - common.json (4 locales): new editor.share.visibility.{publicLabel, publicHint, unlistedLabel, unlistedHint, privateLabel, privateHint} + editor.share.updateFailed. Backend gating + DB migration are in the velxio-prod pro overlay (commit referencing this submodule pointer). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 10:49:03 +07:00
import type { ProjectVisibility } from '../services/projectService';
interface CurrentProject {
id: string;
slug: string;
ownerUsername: string;
isPublic: boolean;
feat(share-modal): D1.4 — 3-level visibility picker (public/unlisted/private) Phase 1 D1.4 — replaces the binary public/private toggle in ShareModal with three radio-button-styled options. Optimistic UI: every option renders for every user; the backend's 403 (with structured visibility_not_allowed detail) redirects to /pricing?from=visibility_X so the pricing page can lead the right pitch. Why optimistic-then-redirect instead of hiding/locking options: 1. Discovery — Free / Maker users SEE Pro unlocks Private. That's the exact conversion signal the pricing page is trying to surface. 2. Discovery without surprise — the locked click goes to /pricing with a hint, not a dead modal. 3. Less plan-coupling — this upstream component doesn't need to know about the pro overlay's plan store. Backend is the only source of truth for what's allowed. Touched: - ShareModal.tsx: full rewrite as a 3-option picker with badges (Maker / Pro) on the gated options. - projectService.ts: ProjectResponse / ProjectSaveData now declare `visibility?: 'public' | 'unlisted' | 'private'`. is_public stays declared for backward compat with old callers. - useProjectStore.ts: CurrentProject gains `visibility?`; setVisibility accepts EITHER the legacy boolean OR the new enum and keeps both fields coherent. - common.json (4 locales): new editor.share.visibility.{publicLabel, publicHint, unlistedLabel, unlistedHint, privateLabel, privateHint} + editor.share.updateFailed. Backend gating + DB migration are in the velxio-prod pro overlay (commit referencing this submodule pointer). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 10:49:03 +07:00
// Phase 1 D1.3 — three-level visibility. Kept in sync with isPublic
// (which legacy callers still read) by setVisibility().
visibility?: ProjectVisibility;
}
interface ProjectState {
currentProject: CurrentProject | null;
setCurrentProject: (project: CurrentProject) => void;
clearCurrentProject: () => void;
feat(share-modal): D1.4 — 3-level visibility picker (public/unlisted/private) Phase 1 D1.4 — replaces the binary public/private toggle in ShareModal with three radio-button-styled options. Optimistic UI: every option renders for every user; the backend's 403 (with structured visibility_not_allowed detail) redirects to /pricing?from=visibility_X so the pricing page can lead the right pitch. Why optimistic-then-redirect instead of hiding/locking options: 1. Discovery — Free / Maker users SEE Pro unlocks Private. That's the exact conversion signal the pricing page is trying to surface. 2. Discovery without surprise — the locked click goes to /pricing with a hint, not a dead modal. 3. Less plan-coupling — this upstream component doesn't need to know about the pro overlay's plan store. Backend is the only source of truth for what's allowed. Touched: - ShareModal.tsx: full rewrite as a 3-option picker with badges (Maker / Pro) on the gated options. - projectService.ts: ProjectResponse / ProjectSaveData now declare `visibility?: 'public' | 'unlisted' | 'private'`. is_public stays declared for backward compat with old callers. - useProjectStore.ts: CurrentProject gains `visibility?`; setVisibility accepts EITHER the legacy boolean OR the new enum and keeps both fields coherent. - common.json (4 locales): new editor.share.visibility.{publicLabel, publicHint, unlistedLabel, unlistedHint, privateLabel, privateHint} + editor.share.updateFailed. Backend gating + DB migration are in the velxio-prod pro overlay (commit referencing this submodule pointer). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 10:49:03 +07:00
// Updated to accept either the legacy boolean OR the new enum so the
// ShareModal callsite and any older callers keep working uniformly.
setVisibility: (next: boolean | ProjectVisibility) => void;
}
export const useProjectStore = create<ProjectState>((set) => ({
currentProject: null,
setCurrentProject: (project) => set({ currentProject: project }),
clearCurrentProject: () => set({ currentProject: null }),
feat(share-modal): D1.4 — 3-level visibility picker (public/unlisted/private) Phase 1 D1.4 — replaces the binary public/private toggle in ShareModal with three radio-button-styled options. Optimistic UI: every option renders for every user; the backend's 403 (with structured visibility_not_allowed detail) redirects to /pricing?from=visibility_X so the pricing page can lead the right pitch. Why optimistic-then-redirect instead of hiding/locking options: 1. Discovery — Free / Maker users SEE Pro unlocks Private. That's the exact conversion signal the pricing page is trying to surface. 2. Discovery without surprise — the locked click goes to /pricing with a hint, not a dead modal. 3. Less plan-coupling — this upstream component doesn't need to know about the pro overlay's plan store. Backend is the only source of truth for what's allowed. Touched: - ShareModal.tsx: full rewrite as a 3-option picker with badges (Maker / Pro) on the gated options. - projectService.ts: ProjectResponse / ProjectSaveData now declare `visibility?: 'public' | 'unlisted' | 'private'`. is_public stays declared for backward compat with old callers. - useProjectStore.ts: CurrentProject gains `visibility?`; setVisibility accepts EITHER the legacy boolean OR the new enum and keeps both fields coherent. - common.json (4 locales): new editor.share.visibility.{publicLabel, publicHint, unlistedLabel, unlistedHint, privateLabel, privateHint} + editor.share.updateFailed. Backend gating + DB migration are in the velxio-prod pro overlay (commit referencing this submodule pointer). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 10:49:03 +07:00
setVisibility: (next) =>
set((s) => {
if (!s.currentProject) return s;
// Translate boolean → enum and vice versa so both fields are
// always coherent.
let isPublic: boolean;
let visibility: ProjectVisibility;
if (typeof next === 'boolean') {
isPublic = next;
visibility = next ? 'public' : 'private';
} else {
visibility = next;
isPublic = next === 'public';
}
return {
currentProject: { ...s.currentProject, isPublic, visibility },
};
}),
}));