@contractspec/example.saas-boilerplate 3.7.5 → 3.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -8
- package/AGENTS.md +50 -27
- package/CHANGELOG.md +16 -0
- package/README.md +64 -144
- package/dist/billing/billing.event.js +1 -1
- package/dist/billing/index.d.ts +6 -6
- package/dist/billing/index.js +1 -1
- package/dist/browser/billing/billing.event.js +1 -1
- package/dist/browser/billing/index.js +1 -1
- package/dist/browser/index.js +931 -932
- package/dist/browser/project/index.js +209 -209
- package/dist/browser/project/project.event.js +1 -1
- package/dist/browser/ui/SaasDashboard.js +45 -45
- package/dist/browser/ui/SaasProjectList.js +7 -7
- package/dist/browser/ui/SaasSettingsPanel.js +12 -12
- package/dist/browser/ui/hooks/index.js +2 -2
- package/dist/browser/ui/hooks/useProjectList.js +1 -1
- package/dist/browser/ui/hooks/useProjectMutations.js +1 -1
- package/dist/browser/ui/index.js +483 -484
- package/dist/browser/ui/modals/CreateProjectModal.js +10 -10
- package/dist/browser/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/browser/ui/modals/index.js +23 -23
- package/dist/browser/ui/renderers/index.js +112 -112
- package/dist/browser/ui/renderers/project-list.renderer.js +7 -7
- package/dist/handlers/index.d.ts +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +931 -932
- package/dist/node/billing/billing.event.js +1 -1
- package/dist/node/billing/index.js +1 -1
- package/dist/node/index.js +931 -932
- package/dist/node/project/index.js +209 -209
- package/dist/node/project/project.event.js +1 -1
- package/dist/node/ui/SaasDashboard.js +45 -45
- package/dist/node/ui/SaasProjectList.js +7 -7
- package/dist/node/ui/SaasSettingsPanel.js +12 -12
- package/dist/node/ui/hooks/index.js +2 -2
- package/dist/node/ui/hooks/useProjectList.js +1 -1
- package/dist/node/ui/hooks/useProjectMutations.js +1 -1
- package/dist/node/ui/index.js +483 -484
- package/dist/node/ui/modals/CreateProjectModal.js +10 -10
- package/dist/node/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/node/ui/modals/index.js +23 -23
- package/dist/node/ui/renderers/index.js +112 -112
- package/dist/node/ui/renderers/project-list.renderer.js +7 -7
- package/dist/presentations/index.d.ts +1 -1
- package/dist/project/index.d.ts +7 -7
- package/dist/project/index.js +209 -209
- package/dist/project/project.event.js +1 -1
- package/dist/settings/index.d.ts +1 -1
- package/dist/ui/SaasDashboard.js +45 -45
- package/dist/ui/SaasProjectList.js +7 -7
- package/dist/ui/SaasSettingsPanel.js +12 -12
- package/dist/ui/hooks/index.d.ts +2 -2
- package/dist/ui/hooks/index.js +2 -2
- package/dist/ui/hooks/useProjectList.d.ts +5 -0
- package/dist/ui/hooks/useProjectList.js +1 -1
- package/dist/ui/hooks/useProjectMutations.d.ts +8 -0
- package/dist/ui/hooks/useProjectMutations.js +1 -1
- package/dist/ui/index.d.ts +4 -4
- package/dist/ui/index.js +483 -484
- package/dist/ui/modals/CreateProjectModal.js +10 -10
- package/dist/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/ui/modals/index.js +23 -23
- package/dist/ui/renderers/index.d.ts +1 -1
- package/dist/ui/renderers/index.js +112 -112
- package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
- package/dist/ui/renderers/project-list.renderer.js +7 -7
- package/package.json +14 -14
- package/src/billing/billing.entity.ts +132 -132
- package/src/billing/billing.enum.ts +9 -9
- package/src/billing/billing.event.ts +71 -71
- package/src/billing/billing.handler.ts +87 -87
- package/src/billing/billing.operations.ts +158 -158
- package/src/billing/billing.presentation.ts +45 -45
- package/src/billing/billing.schema.ts +76 -76
- package/src/billing/index.ts +43 -48
- package/src/dashboard/dashboard.presentation.ts +45 -45
- package/src/dashboard/index.ts +2 -2
- package/src/docs/saas-boilerplate.docblock.ts +43 -43
- package/src/example.ts +32 -32
- package/src/handlers/index.ts +9 -9
- package/src/handlers/saas.handlers.ts +250 -249
- package/src/index.ts +40 -41
- package/src/presentations/index.ts +18 -20
- package/src/project/index.ts +45 -50
- package/src/project/project.entity.ts +68 -68
- package/src/project/project.enum.ts +8 -8
- package/src/project/project.event.ts +79 -79
- package/src/project/project.handler.ts +103 -103
- package/src/project/project.operations.ts +236 -236
- package/src/project/project.presentation.ts +46 -46
- package/src/project/project.schema.ts +90 -90
- package/src/saas-boilerplate.feature.ts +100 -100
- package/src/seeders/index.ts +20 -20
- package/src/settings/index.ts +2 -3
- package/src/settings/settings.entity.ts +65 -65
- package/src/settings/settings.enum.ts +4 -4
- package/src/shared/mock-data.ts +92 -92
- package/src/shared/overlay-types.ts +23 -23
- package/src/tests/operations.test-spec.ts +96 -96
- package/src/ui/SaasDashboard.tsx +270 -270
- package/src/ui/SaasProjectList.tsx +90 -90
- package/src/ui/SaasSettingsPanel.tsx +84 -84
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useProjectList.ts +69 -68
- package/src/ui/hooks/useProjectMutations.ts +144 -143
- package/src/ui/index.ts +8 -12
- package/src/ui/modals/CreateProjectModal.tsx +154 -154
- package/src/ui/modals/ProjectActionsModal.tsx +321 -321
- package/src/ui/overlays/demo-overlays.ts +49 -49
- package/src/ui/renderers/index.ts +5 -4
- package/src/ui/renderers/project-list.markdown.ts +204 -204
- package/src/ui/renderers/project-list.renderer.tsx +14 -13
- package/tsconfig.json +7 -8
- package/tsdown.config.js +7 -3
package/src/ui/SaasDashboard.tsx
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
EmptyState,
|
|
6
|
+
EntityCard,
|
|
7
|
+
ErrorState,
|
|
8
|
+
LoaderBlock,
|
|
9
|
+
StatCard,
|
|
10
|
+
StatCardGroup,
|
|
11
|
+
StatusChip,
|
|
12
|
+
} from '@contractspec/lib.design-system';
|
|
3
13
|
/**
|
|
4
14
|
* SaaS Dashboard
|
|
5
15
|
*
|
|
@@ -11,21 +21,11 @@
|
|
|
11
21
|
* - UpdateProjectContract -> Edit project via modal
|
|
12
22
|
* - DeleteProjectContract -> Delete project via modal
|
|
13
23
|
*/
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
StatCard,
|
|
17
|
-
StatCardGroup,
|
|
18
|
-
StatusChip,
|
|
19
|
-
EntityCard,
|
|
20
|
-
EmptyState,
|
|
21
|
-
LoaderBlock,
|
|
22
|
-
ErrorState,
|
|
23
|
-
Button,
|
|
24
|
-
} from '@contractspec/lib.design-system';
|
|
24
|
+
import { useCallback, useState } from 'react';
|
|
25
25
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
type Project,
|
|
27
|
+
type Subscription,
|
|
28
|
+
useProjectList,
|
|
29
29
|
} from './hooks/useProjectList';
|
|
30
30
|
import { useProjectMutations } from './hooks/useProjectMutations';
|
|
31
31
|
import { CreateProjectModal } from './modals/CreateProjectModal';
|
|
@@ -34,292 +34,292 @@ import { ProjectActionsModal } from './modals/ProjectActionsModal';
|
|
|
34
34
|
type Tab = 'projects' | 'billing' | 'settings';
|
|
35
35
|
|
|
36
36
|
function getStatusTone(
|
|
37
|
-
|
|
37
|
+
status: Project['status']
|
|
38
38
|
): 'success' | 'warning' | 'neutral' | 'danger' {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
switch (status) {
|
|
40
|
+
case 'ACTIVE':
|
|
41
|
+
return 'success';
|
|
42
|
+
case 'DRAFT':
|
|
43
|
+
return 'neutral';
|
|
44
|
+
case 'ARCHIVED':
|
|
45
|
+
return 'warning';
|
|
46
|
+
default:
|
|
47
|
+
return 'neutral';
|
|
48
|
+
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function SaasDashboard() {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
const [activeTab, setActiveTab] = useState<Tab>('projects');
|
|
53
|
+
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
|
54
|
+
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
|
55
|
+
const [isProjectActionsOpen, setIsProjectActionsOpen] = useState(false);
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const { data, subscription, loading, error, stats, refetch } =
|
|
58
|
+
useProjectList();
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
const mutations = useProjectMutations({
|
|
61
|
+
onSuccess: () => {
|
|
62
|
+
refetch();
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
const handleProjectClick = useCallback((project: Project) => {
|
|
67
|
+
setSelectedProject(project);
|
|
68
|
+
setIsProjectActionsOpen(true);
|
|
69
|
+
}, []);
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
const tabs: { id: Tab; label: string; icon: string }[] = [
|
|
72
|
+
{ id: 'projects', label: 'Projects', icon: '📁' },
|
|
73
|
+
{ id: 'billing', label: 'Billing', icon: '💳' },
|
|
74
|
+
{ id: 'settings', label: 'Settings', icon: '⚙️' },
|
|
75
|
+
];
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
if (loading && !data) {
|
|
78
|
+
return <LoaderBlock label="Loading dashboard..." />;
|
|
79
|
+
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
if (error) {
|
|
82
|
+
return (
|
|
83
|
+
<ErrorState
|
|
84
|
+
title="Failed to load dashboard"
|
|
85
|
+
description={error.message}
|
|
86
|
+
onRetry={refetch}
|
|
87
|
+
retryLabel="Retry"
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
return (
|
|
93
|
+
<div className="space-y-6">
|
|
94
|
+
{/* Header */}
|
|
95
|
+
<div className="flex items-center justify-between">
|
|
96
|
+
<h2 className="font-bold text-2xl">SaaS Dashboard</h2>
|
|
97
|
+
{activeTab === 'projects' && (
|
|
98
|
+
<Button onPress={() => setIsCreateModalOpen(true)}>
|
|
99
|
+
<span className="mr-2">+</span> New Project
|
|
100
|
+
</Button>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
104
|
+
{/* Stats Row */}
|
|
105
|
+
{stats && subscription && (
|
|
106
|
+
<StatCardGroup>
|
|
107
|
+
<StatCard label="Projects" value={stats.total.toString()} />
|
|
108
|
+
<StatCard label="Active" value={stats.activeCount.toString()} />
|
|
109
|
+
<StatCard label="Draft" value={stats.draftCount.toString()} />
|
|
110
|
+
<StatCard
|
|
111
|
+
label="Plan"
|
|
112
|
+
value={subscription.plan}
|
|
113
|
+
hint={subscription.status}
|
|
114
|
+
/>
|
|
115
|
+
</StatCardGroup>
|
|
116
|
+
)}
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
118
|
+
{/* Navigation Tabs */}
|
|
119
|
+
<nav className="flex gap-1 rounded-lg bg-muted p-1" role="tablist">
|
|
120
|
+
{tabs.map((tab) => (
|
|
121
|
+
<button
|
|
122
|
+
key={tab.id}
|
|
123
|
+
type="button"
|
|
124
|
+
role="tab"
|
|
125
|
+
aria-selected={activeTab === tab.id}
|
|
126
|
+
onClick={() => setActiveTab(tab.id)}
|
|
127
|
+
className={`flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 font-medium text-sm transition-colors ${
|
|
128
|
+
activeTab === tab.id
|
|
129
|
+
? 'bg-background text-foreground shadow-sm'
|
|
130
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
131
|
+
}`}
|
|
132
|
+
>
|
|
133
|
+
<span>{tab.icon}</span>
|
|
134
|
+
{tab.label}
|
|
135
|
+
</button>
|
|
136
|
+
))}
|
|
137
|
+
</nav>
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
{/* Tab Content */}
|
|
140
|
+
<div className="min-h-[400px]" role="tabpanel">
|
|
141
|
+
{activeTab === 'projects' && (
|
|
142
|
+
<ProjectsTab data={data} onProjectClick={handleProjectClick} />
|
|
143
|
+
)}
|
|
144
|
+
{activeTab === 'billing' && <BillingTab subscription={subscription} />}
|
|
145
|
+
{activeTab === 'settings' && <SettingsTab />}
|
|
146
|
+
</div>
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
148
|
+
{/* Create Project Modal */}
|
|
149
|
+
<CreateProjectModal
|
|
150
|
+
isOpen={isCreateModalOpen}
|
|
151
|
+
onClose={() => setIsCreateModalOpen(false)}
|
|
152
|
+
onSubmit={async (input) => {
|
|
153
|
+
await mutations.createProject(input);
|
|
154
|
+
}}
|
|
155
|
+
isLoading={mutations.createState.loading}
|
|
156
|
+
/>
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
158
|
+
{/* Project Actions Modal */}
|
|
159
|
+
<ProjectActionsModal
|
|
160
|
+
isOpen={isProjectActionsOpen}
|
|
161
|
+
project={selectedProject}
|
|
162
|
+
onClose={() => {
|
|
163
|
+
setIsProjectActionsOpen(false);
|
|
164
|
+
setSelectedProject(null);
|
|
165
|
+
}}
|
|
166
|
+
onUpdate={async (input) => {
|
|
167
|
+
await mutations.updateProject(input);
|
|
168
|
+
}}
|
|
169
|
+
onArchive={async (projectId) => {
|
|
170
|
+
await mutations.archiveProject(projectId);
|
|
171
|
+
}}
|
|
172
|
+
onActivate={async (projectId) => {
|
|
173
|
+
await mutations.activateProject(projectId);
|
|
174
|
+
}}
|
|
175
|
+
onDelete={async (projectId) => {
|
|
176
|
+
await mutations.deleteProject(projectId);
|
|
177
|
+
}}
|
|
178
|
+
isLoading={mutations.isLoading}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
interface ProjectsTabProps {
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
data: ReturnType<typeof useProjectList>['data'];
|
|
186
|
+
onProjectClick?: (project: Project) => void;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function ProjectsTab({ data, onProjectClick }: ProjectsTabProps) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
if (!data?.items.length) {
|
|
191
|
+
return (
|
|
192
|
+
<EmptyState
|
|
193
|
+
title="No projects yet"
|
|
194
|
+
description="Create your first project to get started."
|
|
195
|
+
/>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
199
|
+
return (
|
|
200
|
+
<div className="space-y-4">
|
|
201
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
202
|
+
{data.items.map((project: Project) => (
|
|
203
|
+
<EntityCard
|
|
204
|
+
key={project.id}
|
|
205
|
+
cardTitle={project.name}
|
|
206
|
+
cardSubtitle={project.tier}
|
|
207
|
+
meta={
|
|
208
|
+
<p className="text-muted-foreground text-sm">
|
|
209
|
+
{project.description}
|
|
210
|
+
</p>
|
|
211
|
+
}
|
|
212
|
+
chips={
|
|
213
|
+
<StatusChip
|
|
214
|
+
tone={getStatusTone(project.status)}
|
|
215
|
+
label={project.status}
|
|
216
|
+
/>
|
|
217
|
+
}
|
|
218
|
+
footer={
|
|
219
|
+
<div className="flex w-full items-center justify-between">
|
|
220
|
+
<span className="text-muted-foreground text-xs">
|
|
221
|
+
{project.updatedAt.toLocaleDateString()}
|
|
222
|
+
</span>
|
|
223
|
+
<Button
|
|
224
|
+
variant="ghost"
|
|
225
|
+
size="sm"
|
|
226
|
+
onPress={() => onProjectClick?.(project)}
|
|
227
|
+
>
|
|
228
|
+
Actions
|
|
229
|
+
</Button>
|
|
230
|
+
</div>
|
|
231
|
+
}
|
|
232
|
+
/>
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
function BillingTab({ subscription }: { subscription: Subscription | null }) {
|
|
240
|
-
|
|
240
|
+
if (!subscription) return null;
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
242
|
+
return (
|
|
243
|
+
<div className="space-y-6">
|
|
244
|
+
<div className="rounded-xl border border-border bg-card p-6">
|
|
245
|
+
<div className="flex items-start justify-between">
|
|
246
|
+
<div>
|
|
247
|
+
<h3 className="font-semibold text-lg">{subscription.plan} Plan</h3>
|
|
248
|
+
<p className="text-muted-foreground text-sm">
|
|
249
|
+
Current period:{' '}
|
|
250
|
+
{subscription.currentPeriodStart.toLocaleDateString()} -{' '}
|
|
251
|
+
{subscription.currentPeriodEnd.toLocaleDateString()}
|
|
252
|
+
</p>
|
|
253
|
+
<p className="text-muted-foreground text-sm">
|
|
254
|
+
Billing cycle: {subscription.billingCycle}
|
|
255
|
+
</p>
|
|
256
|
+
</div>
|
|
257
|
+
<StatusChip tone="success" label={subscription.status} />
|
|
258
|
+
</div>
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
260
|
+
<div className="mt-4 flex gap-3">
|
|
261
|
+
<Button variant="outline" onPress={() => alert('Upgrade clicked!')}>
|
|
262
|
+
Upgrade Plan
|
|
263
|
+
</Button>
|
|
264
|
+
<Button
|
|
265
|
+
variant="ghost"
|
|
266
|
+
onPress={() => alert('Manage Billing clicked!')}
|
|
267
|
+
>
|
|
268
|
+
Manage Billing
|
|
269
|
+
</Button>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
272
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
273
|
+
{subscription.cancelAtPeriodEnd && (
|
|
274
|
+
<div className="rounded-xl border border-border bg-destructive/10 p-4 text-destructive">
|
|
275
|
+
<p className="font-medium text-sm">
|
|
276
|
+
⚠️ Your subscription will be cancelled at the end of the current
|
|
277
|
+
period.
|
|
278
|
+
</p>
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
function SettingsTab() {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
286
|
+
return (
|
|
287
|
+
<div className="space-y-6">
|
|
288
|
+
<div className="rounded-xl border border-border bg-card p-6">
|
|
289
|
+
<h3 className="mb-4 font-semibold text-lg">Organization Settings</h3>
|
|
290
|
+
<div className="space-y-4">
|
|
291
|
+
<div>
|
|
292
|
+
<label htmlFor="org-name" className="font-medium text-sm">
|
|
293
|
+
Organization Name
|
|
294
|
+
</label>
|
|
295
|
+
<input
|
|
296
|
+
id="org-name"
|
|
297
|
+
type="text"
|
|
298
|
+
defaultValue="Demo Organization"
|
|
299
|
+
className="mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
|
|
300
|
+
/>
|
|
301
|
+
</div>
|
|
302
|
+
<div>
|
|
303
|
+
<label htmlFor="timezone" className="font-medium text-sm">
|
|
304
|
+
Default Timezone
|
|
305
|
+
</label>
|
|
306
|
+
<select
|
|
307
|
+
id="timezone"
|
|
308
|
+
className="mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
|
|
309
|
+
>
|
|
310
|
+
<option>UTC</option>
|
|
311
|
+
<option>America/New_York</option>
|
|
312
|
+
<option>Europe/London</option>
|
|
313
|
+
<option>Asia/Tokyo</option>
|
|
314
|
+
</select>
|
|
315
|
+
</div>
|
|
316
|
+
<div className="pt-2">
|
|
317
|
+
<Button onPress={() => alert('Settings saved!')}>
|
|
318
|
+
Save Settings
|
|
319
|
+
</Button>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
325
|
}
|