immersive2/src/react/pages/createDiagramModal.tsx
Michael Mainguy 1c50dd5c84 Implement three-state feature flag system with upgrade badges
Feature States:
- 'on': Feature fully accessible
- 'off': Feature hidden from menus
- 'coming-soon': Visible with "Coming Soon!" badge, not clickable
- 'basic': Visible with "Sign Up for Free" badge, triggers Auth0 login
- 'pro': Visible with "Upgrade to Pro" badge (for future upgrade flow)

Changes:
- Update FeatureState type to support 5 states (on/off/coming-soon/basic/pro)
- Consolidate GUEST_FEATURE_CONFIG as DEFAULT_FEATURE_CONFIG
- Create ComingSoonBadge component for coming-soon features
- Create UpgradeBadge component for basic/pro tier requirements
- Update VR Experience hamburger menu to maintain open/closed state
- Make menu default to open, persist state in localStorage
- Make 'basic' features clickable to trigger Auth0 sign-in
- Update createDiagramModal to show appropriate badges
- Fix camera initial position to match VR rig (prevent flip on load)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 14:33:11 -06:00

141 lines
6.2 KiB
TypeScript

import {Anchor, Button, Checkbox, Group, Modal, Pill, Stack, Textarea, TextInput} from "@mantine/core";
import {usePouch} from "use-pouchdb";
import {useState} from "react";
import {v4} from "uuid";
import log from "loglevel";
import {useFeatureState} from "../hooks/useFeatures";
import ComingSoonBadge from "../components/ComingSoonBadge";
import UpgradeBadge from "../components/UpgradeBadge";
import {useAuth0} from "@auth0/auth0-react";
export default function CreateDiagramModal({createOpened, closeCreate}) {
const logger = log.getLogger('createDiagramModal');
const db = usePouch();
const { loginWithRedirect } = useAuth0();
// Feature flags
const privateDesignsState = useFeatureState('privateDesigns');
const encryptedDesignsState = useFeatureState('encryptedDesigns');
const shareCollaborateState = useFeatureState('shareCollaborate');
const privateDesignsEnabled = privateDesignsState === 'on';
const encryptedDesignsEnabled = encryptedDesignsState === 'on';
const shareCollaborateEnabled = shareCollaborateState === 'on';
const handleSignUp = () => {
loginWithRedirect({
appState: { returnTo: window.location.pathname }
});
};
const [diagram, setDiagram] = useState({
name: '',
description: '',
private: false,
encrypted: false,
invite: false
});
const createDiagram = async () => {
let doc = null;
try {
doc = await db.get('directory')
} catch (err) {
logger.warn('cannot find directory', err);
}
const id = 'diagram-' + v4();
const newDiagram = {...diagram, _id: id, type: 'diagram'};
if (!doc) {
await db.put({_id: 'directory', diagrams: [newDiagram], type: 'directory'});
} else {
if (doc.diagrams) {
doc.diagrams.push(newDiagram);
} else {
doc.diagrams = [newDiagram];
}
logger.debug('new directory', doc);
await db.put(doc);
}
closeCreate();
}
return (
<Modal opened={createOpened} onClose={closeCreate}>
<Stack>
<TextInput key="name"
label="Name"
placeholder="Enter diagram name"
value={diagram.name}
onChange={(e) => {
setDiagram({...diagram, name: e.currentTarget.value})
}}
required/>
<Textarea key="description"
label="Description"
value={diagram.description}
onChange={(e) => {
setDiagram({...diagram, description: e.currentTarget.value})
}}
placeholder="Enter diagram description"/>
<Group>
<Checkbox w={250}
key="private"
label="Private"
checked={diagram.private}
onChange={(e) => {
if (privateDesignsState === 'basic') {
handleSignUp();
} else {
setDiagram({...diagram, private: e.currentTarget.checked})
}
}}
disabled={!privateDesignsEnabled && privateDesignsState !== 'basic'}/>
{privateDesignsState === 'coming-soon' && <ComingSoonBadge />}
{privateDesignsState === 'basic' && <UpgradeBadge tier="basic" onClick={handleSignUp} />}
{privateDesignsState === 'pro' && <UpgradeBadge tier="pro" />}
</Group>
<Group>
<Checkbox w={250}
key="encrypted"
label="Encrypted"
checked={diagram.encrypted}
onChange={(e) => {
if (encryptedDesignsState === 'basic') {
handleSignUp();
} else {
setDiagram({...diagram, encrypted: e.currentTarget.checked})
}
}}
disabled={!encryptedDesignsEnabled && encryptedDesignsState !== 'basic'}/>
{encryptedDesignsState === 'coming-soon' && <ComingSoonBadge />}
{encryptedDesignsState === 'basic' && <UpgradeBadge tier="basic" onClick={handleSignUp} />}
{encryptedDesignsState === 'pro' && <UpgradeBadge tier="pro" />}
</Group>
<Group>
<Checkbox w={250}
key="invite"
label="Invite Collaborators"
checked={diagram.invite}
onChange={(e) => {
if (shareCollaborateState === 'basic') {
handleSignUp();
} else {
setDiagram({...diagram, invite: e.currentTarget.checked})
}
}}
disabled={!shareCollaborateEnabled && shareCollaborateState !== 'basic'}/>
{shareCollaborateState === 'coming-soon' && <ComingSoonBadge />}
{shareCollaborateState === 'basic' && <UpgradeBadge tier="basic" onClick={handleSignUp} />}
{shareCollaborateState === 'pro' && <UpgradeBadge tier="pro" />}
</Group>
<Group>
<Button key="create" onClick={createDiagram}>Create</Button>
<Anchor p={5} size="sm" key="cancel" onClick={(e) => {
e.preventDefault();
closeCreate()
}}>Cancel</Anchor>
</Group>
</Stack>
</Modal>
)
}