inicontent 1.0.0
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/.github/pull_request.yml +18 -0
- package/CONTRIBUTING.md +67 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/app/app.vue +106 -0
- package/app/assets/main.css +974 -0
- package/app/components/Api/EndpointCard.vue +169 -0
- package/app/components/Api/SchemaDocsTable.vue +208 -0
- package/app/components/Asset/Card.vue +1363 -0
- package/app/components/Asset/Grid.vue +295 -0
- package/app/components/Asset/Icon.vue +38 -0
- package/app/components/Asset/Preview.vue +486 -0
- package/app/components/Asset/Scanner.vue +700 -0
- package/app/components/Asset/Thumb.vue +708 -0
- package/app/components/Auth/Reset.vue +99 -0
- package/app/components/Auth/index.vue +346 -0
- package/app/components/ChatInterface.vue +2326 -0
- package/app/components/ChatPagePreview.vue +34 -0
- package/app/components/Column/Array.vue +29 -0
- package/app/components/Column/Asset.vue +21 -0
- package/app/components/Column/Boolean.vue +12 -0
- package/app/components/Column/Color.vue +9 -0
- package/app/components/Column/Date.vue +57 -0
- package/app/components/Column/Edit.vue +215 -0
- package/app/components/Column/Email.vue +9 -0
- package/app/components/Column/Html.vue +27 -0
- package/app/components/Column/Id.vue +20 -0
- package/app/components/Column/Locale.vue +32 -0
- package/app/components/Column/Object.vue +18 -0
- package/app/components/Column/Password.vue +7 -0
- package/app/components/Column/Role.vue +15 -0
- package/app/components/Column/S.vue +14 -0
- package/app/components/Column/Table/Single.vue +49 -0
- package/app/components/Column/Table/index.vue +42 -0
- package/app/components/Column/Tags.vue +31 -0
- package/app/components/Column/Text.vue +9 -0
- package/app/components/Column/Url.vue +9 -0
- package/app/components/Column/index.vue +41 -0
- package/app/components/Dashboard/Editor.vue +221 -0
- package/app/components/Dashboard/Grid.vue +270 -0
- package/app/components/Dashboard/View.vue +131 -0
- package/app/components/Dashboard/Widget/BarChart.vue +73 -0
- package/app/components/Dashboard/Widget/Counter.vue +51 -0
- package/app/components/Dashboard/Widget/LineChart.vue +76 -0
- package/app/components/Dashboard/Widget/PieChart.vue +75 -0
- package/app/components/Dashboard/Widget/Table.vue +94 -0
- package/app/components/Dashboard/WidgetEditor.vue +274 -0
- package/app/components/Data/Array.vue +102 -0
- package/app/components/Data/Html.vue +9 -0
- package/app/components/Data/Icon.vue +9 -0
- package/app/components/Data/Object.vue +10 -0
- package/app/components/Data/S.vue +16 -0
- package/app/components/Data/Table.vue +53 -0
- package/app/components/Data/Value.vue +34 -0
- package/app/components/Data/index.vue +14 -0
- package/app/components/Field/Array.vue +324 -0
- package/app/components/Field/Asset/Actions.vue +97 -0
- package/app/components/Field/Asset/index.vue +639 -0
- package/app/components/Field/Boolean.vue +26 -0
- package/app/components/Field/Checkbox.vue +48 -0
- package/app/components/Field/Color.vue +27 -0
- package/app/components/Field/Date.vue +30 -0
- package/app/components/Field/Email.vue +51 -0
- package/app/components/Field/EmailTemplate.vue +420 -0
- package/app/components/Field/Html/ColorPicker.vue +127 -0
- package/app/components/Field/Html/Editor.vue +544 -0
- package/app/components/Field/Html/index.vue +25 -0
- package/app/components/Field/Icon.vue +114 -0
- package/app/components/Field/JSON.vue +113 -0
- package/app/components/Field/Mention.vue +36 -0
- package/app/components/Field/Number.vue +27 -0
- package/app/components/Field/Object.vue +92 -0
- package/app/components/Field/Password.vue +55 -0
- package/app/components/Field/Radio.vue +47 -0
- package/app/components/Field/S.vue +44 -0
- package/app/components/Field/Select.vue +49 -0
- package/app/components/Field/Table.vue +348 -0
- package/app/components/Field/Tags.vue +113 -0
- package/app/components/Field/Text.vue +31 -0
- package/app/components/Field/Textarea.vue +33 -0
- package/app/components/Field/Url.vue +40 -0
- package/app/components/Field/Wrapper.vue +128 -0
- package/app/components/Field/index.vue +68 -0
- package/app/components/FloatingChatbot.vue +73 -0
- package/app/components/Form/Card.vue +151 -0
- package/app/components/Form/Drawer.vue +195 -0
- package/app/components/Form/index.vue +621 -0
- package/app/components/Header.vue +328 -0
- package/app/components/Offline/ConflictModal.vue +200 -0
- package/app/components/Offline/SyncStatus.vue +249 -0
- package/app/components/PlatformFeatureLanding.vue +65 -0
- package/app/components/Table/Backups.vue +638 -0
- package/app/components/Table/Flows.vue +814 -0
- package/app/components/Table/Grid.vue +296 -0
- package/app/components/Table/Icon.vue +36 -0
- package/app/components/Table/Logs.vue +181 -0
- package/app/components/Table/Schedules.vue +722 -0
- package/app/components/Table/Search/Button.vue +256 -0
- package/app/components/Table/Search/Items.vue +286 -0
- package/app/components/Table/Search/index.vue +87 -0
- package/app/components/Table/Settings/Schema.vue +801 -0
- package/app/components/Table/Settings/index.vue +631 -0
- package/app/components/Table/TranslateDrawer.vue +526 -0
- package/app/components/Table/Views/Kanban.vue +289 -0
- package/app/components/Table/Views/Table.vue +1273 -0
- package/app/components/Table/index.vue +890 -0
- package/app/composables/Translation/define.ts +41 -0
- package/app/composables/Translation/fetch.ts +107 -0
- package/app/composables/Translation/languages.ts +1 -0
- package/app/composables/Translation/loadCoreTranslations.ts +38 -0
- package/app/composables/Translation/translate.ts +250 -0
- package/app/composables/databaseCookies.ts +79 -0
- package/app/composables/fieldValidator.ts +72 -0
- package/app/composables/fieldsList.ts +301 -0
- package/app/composables/formatDatabase.ts +101 -0
- package/app/composables/index.ts +444 -0
- package/app/composables/openDrawer.ts +118 -0
- package/app/composables/optimizeFile.ts +287 -0
- package/app/composables/renderLabel.ts +88 -0
- package/app/composables/schemaClipboard.ts +95 -0
- package/app/composables/search.ts +353 -0
- package/app/composables/setThemeConfig.ts +48 -0
- package/app/composables/translationValue.ts +169 -0
- package/app/composables/useAssetPreview.ts +84 -0
- package/app/composables/useAssetUploader.ts +56 -0
- package/app/composables/useDashboardData.ts +310 -0
- package/app/composables/useDocumentThumbnail.ts +351 -0
- package/app/composables/useEcharts.ts +33 -0
- package/app/composables/useLocalDatabaseConfig.ts +51 -0
- package/app/composables/useOcr.ts +300 -0
- package/app/composables/useOfflineFetch.ts +194 -0
- package/app/composables/useOfflineItem.ts +107 -0
- package/app/composables/useOfflineQueue.ts +302 -0
- package/app/composables/useOfflineSync.ts +235 -0
- package/app/composables/usePasskeyAuth.ts +156 -0
- package/app/composables/usePdfCompressor.ts +180 -0
- package/app/composables/usePlatformRedirect.ts +72 -0
- package/app/composables/useRealtimeSync.ts +233 -0
- package/app/composables/useReferencedItems.ts +180 -0
- package/app/composables/useScanner.ts +664 -0
- package/app/composables/useSubscription.ts +650 -0
- package/app/composables/useThumbnailCache.ts +136 -0
- package/app/composables/useVideoCompressor.ts +194 -0
- package/app/error.vue +112 -0
- package/app/layouts/api.vue +188 -0
- package/app/layouts/dashboard.vue +91 -0
- package/app/layouts/default.vue +228 -0
- package/app/layouts/table.vue +471 -0
- package/app/locales/ar.ts +996 -0
- package/app/locales/en.ts +655 -0
- package/app/locales/es.ts +996 -0
- package/app/locales/fr.ts +997 -0
- package/app/middleware/dashboard.ts +33 -0
- package/app/middleware/database.ts +83 -0
- package/app/middleware/global.ts +1 -0
- package/app/middleware/sid-ingest.global.ts +29 -0
- package/app/middleware/table.ts +54 -0
- package/app/middleware/user.ts +26 -0
- package/app/pages/[[database]]/admin/api/auth/index.vue +306 -0
- package/app/pages/[[database]]/admin/api/index.vue +18 -0
- package/app/pages/[[database]]/admin/api/tables/[table]/index.vue +497 -0
- package/app/pages/[[database]]/admin/api/tables/index.vue +87 -0
- package/app/pages/[[database]]/admin/billing/index.vue +16 -0
- package/app/pages/[[database]]/admin/dashboards/[id]/edit.vue +66 -0
- package/app/pages/[[database]]/admin/dashboards/[id]/index.vue +117 -0
- package/app/pages/[[database]]/admin/dashboards/index.vue +20 -0
- package/app/pages/[[database]]/admin/index.vue +28 -0
- package/app/pages/[[database]]/admin/settings.vue +526 -0
- package/app/pages/[[database]]/admin/tables/[table]/[id]/edit.vue +143 -0
- package/app/pages/[[database]]/admin/tables/[table]/[id]/index.vue +146 -0
- package/app/pages/[[database]]/admin/tables/[table]/flows.vue +10 -0
- package/app/pages/[[database]]/admin/tables/[table]/index.vue +10 -0
- package/app/pages/[[database]]/admin/tables/[table]/new.vue +12 -0
- package/app/pages/[[database]]/admin/tables/[table]/schedules.vue +10 -0
- package/app/pages/[[database]]/admin/tables/[table]/settings.vue +30 -0
- package/app/pages/[[database]]/admin/tables/assets/[...path].vue +24 -0
- package/app/pages/[[database]]/admin/tables/assets/flows.vue +10 -0
- package/app/pages/[[database]]/admin/tables/backups/index.vue +10 -0
- package/app/pages/[[database]]/admin/tables/blocks/index.vue +16 -0
- package/app/pages/[[database]]/admin/tables/index.vue +20 -0
- package/app/pages/[[database]]/admin/tables/pages/index.vue +16 -0
- package/app/pages/[[database]]/admin/tables/templates/index.vue +16 -0
- package/app/pages/[[database]]/api/index.vue +120 -0
- package/app/pages/[[database]]/auth/index.vue +12 -0
- package/app/pages/[[database]]/auth/reset.vue +10 -0
- package/app/pages/[[database]]/index.vue +10 -0
- package/app/plugins/offline-warmup.client.ts +53 -0
- package/app/plugins/template-globals.ts +9 -0
- package/app/spa-loading-template.html +93 -0
- package/biome.json +53 -0
- package/index.d.ts +380 -0
- package/modules/naiveui.ts +41 -0
- package/nuxt.config.ts +269 -0
- package/package.json +56 -0
- package/pnpm-workspace.yaml +5 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/pwa-maskable-192x192.png +0 -0
- package/public/pwa-maskable-512x512.png +0 -0
- package/public/vendor/opencv.js +48 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
interface PricingData {
|
|
2
|
+
plans: any[];
|
|
3
|
+
additionalStorage: any;
|
|
4
|
+
estimatedMonthly?: any;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface SubscriptionData {
|
|
8
|
+
status: string;
|
|
9
|
+
plan: string;
|
|
10
|
+
paypalSubscriptionId?: string;
|
|
11
|
+
periodStart?: number;
|
|
12
|
+
periodEnd?: number;
|
|
13
|
+
database_storage_gb?: number;
|
|
14
|
+
asset_storage_gb?: number;
|
|
15
|
+
credit?: number;
|
|
16
|
+
autoCharge?: boolean;
|
|
17
|
+
autoRenew?: boolean;
|
|
18
|
+
custom_monthly_amount_cents?: number;
|
|
19
|
+
custom_usage_price_cents_per_gb?: number;
|
|
20
|
+
emailsSent?: number;
|
|
21
|
+
emailsLimit?: number;
|
|
22
|
+
emailsExceeded?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface UsageData {
|
|
26
|
+
database_storage_used_gb: number;
|
|
27
|
+
asset_storage_used_gb: number;
|
|
28
|
+
updated_at: number;
|
|
29
|
+
emailsSent?: number;
|
|
30
|
+
emailsLimit?: number;
|
|
31
|
+
emailsExceeded?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Database {
|
|
35
|
+
id: string;
|
|
36
|
+
slug: string;
|
|
37
|
+
[key: string]: any;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Shared in-flight promise so simultaneously mounted components don't trigger duplicate fetches
|
|
41
|
+
let loadSubscriptionDataPromise: Promise<void> | null = null;
|
|
42
|
+
|
|
43
|
+
export const useSubscription = () => {
|
|
44
|
+
// useState keeps this reactive state shared across every component using the composable
|
|
45
|
+
const pricing = useState<PricingData | null>(
|
|
46
|
+
"subscription-pricing",
|
|
47
|
+
() => null,
|
|
48
|
+
);
|
|
49
|
+
const subscription = useState<SubscriptionData | null>(
|
|
50
|
+
"subscription-data",
|
|
51
|
+
() => null,
|
|
52
|
+
);
|
|
53
|
+
const usage = useState<UsageData | null>("subscription-usage", () => null);
|
|
54
|
+
const loading = useState("subscription-loading", () => false);
|
|
55
|
+
const error = useState<string | null>("subscription-error", () => null);
|
|
56
|
+
const config = useRuntimeConfig();
|
|
57
|
+
const database = useState<Database>("database");
|
|
58
|
+
const sessionID = useScopedCookie<string>("sid", database.value?.slug);
|
|
59
|
+
|
|
60
|
+
// Storage configuration
|
|
61
|
+
const selectedDatabaseStorage = useState(
|
|
62
|
+
"subscription-selected-database-storage",
|
|
63
|
+
() => 0.5,
|
|
64
|
+
);
|
|
65
|
+
const selectedAssetStorage = useState(
|
|
66
|
+
"subscription-selected-asset-storage",
|
|
67
|
+
() => 5,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Computed properties
|
|
71
|
+
const isDatabaseStorageExceeded = computed(() => {
|
|
72
|
+
if (!subscription.value || !usage.value) return false;
|
|
73
|
+
const limit = subscription.value.database_storage_gb || 0.5;
|
|
74
|
+
return usage.value.database_storage_used_gb > limit;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const isAssetStorageExceeded = computed(() => {
|
|
78
|
+
if (!subscription.value || !usage.value) return false;
|
|
79
|
+
const limit = subscription.value.asset_storage_gb || 5;
|
|
80
|
+
return usage.value.asset_storage_used_gb > limit;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const hasActiveSubscription = computed(
|
|
84
|
+
() => subscription.value?.status === "active",
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const storagePercentageDB = computed(() => {
|
|
88
|
+
if (!usage.value || !subscription.value) return 0;
|
|
89
|
+
const limit = subscription.value.database_storage_gb || 0.5;
|
|
90
|
+
return Math.min(100, (usage.value.database_storage_used_gb / limit) * 100);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const storagePercentageAsset = computed(() => {
|
|
94
|
+
if (!usage.value || !subscription.value) return 0;
|
|
95
|
+
const limit = subscription.value.asset_storage_gb || 5;
|
|
96
|
+
return Math.min(100, (usage.value.asset_storage_used_gb / limit) * 100);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const fetchPricing = async (databaseGb?: number, assetGb?: number) => {
|
|
100
|
+
loading.value = true;
|
|
101
|
+
error.value = null;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const query: Record<string, any> = {};
|
|
105
|
+
if (databaseGb) query.storageDB = databaseGb;
|
|
106
|
+
if (assetGb) query.storageAsset = assetGb;
|
|
107
|
+
|
|
108
|
+
const response = await $fetch<apiResponse<PricingData>>(
|
|
109
|
+
`${config.public.apiBase}inicontent/billing/pricing`,
|
|
110
|
+
{
|
|
111
|
+
credentials: "include",
|
|
112
|
+
params: {
|
|
113
|
+
[`${database.value.slug}_sid`]: sessionID.value,
|
|
114
|
+
},
|
|
115
|
+
query,
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
pricing.value = response.result;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
error.value =
|
|
122
|
+
err instanceof Error ? err.message : "Failed to load pricing";
|
|
123
|
+
console.error("Error fetching pricing:", err);
|
|
124
|
+
} finally {
|
|
125
|
+
loading.value = false;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const fetchSubscription = async () => {
|
|
130
|
+
loading.value = true;
|
|
131
|
+
error.value = null;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const response = await $fetch<apiResponse<SubscriptionData>>(
|
|
135
|
+
`${config.public.apiBase}inicontent/billing/subscription`,
|
|
136
|
+
{
|
|
137
|
+
credentials: "include",
|
|
138
|
+
params: {
|
|
139
|
+
[`${database.value.slug}_sid`]: sessionID.value,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
const data = response.result;
|
|
144
|
+
subscription.value = data;
|
|
145
|
+
|
|
146
|
+
if (data.status === "active") {
|
|
147
|
+
selectedDatabaseStorage.value = data.database_storage_gb || 0.5;
|
|
148
|
+
selectedAssetStorage.value = data.asset_storage_gb || 5;
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error("Error fetching subscription:", err);
|
|
152
|
+
subscription.value = {
|
|
153
|
+
status: "free",
|
|
154
|
+
plan: "base",
|
|
155
|
+
};
|
|
156
|
+
} finally {
|
|
157
|
+
loading.value = false;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const fetchUsage = async () => {
|
|
162
|
+
loading.value = true;
|
|
163
|
+
error.value = null;
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const response = await $fetch<apiResponse<UsageData>>(
|
|
167
|
+
`${config.public.apiBase}inicontent/billing/usage`,
|
|
168
|
+
{
|
|
169
|
+
credentials: "include",
|
|
170
|
+
params: {
|
|
171
|
+
[`${database.value.slug}_sid`]: sessionID.value,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
usage.value = response.result;
|
|
176
|
+
} catch (err) {
|
|
177
|
+
error.value = err instanceof Error ? err.message : "Failed to load usage";
|
|
178
|
+
console.error("Error fetching usage:", err);
|
|
179
|
+
} finally {
|
|
180
|
+
loading.value = false;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const createCheckoutSession = async (
|
|
185
|
+
databaseGb: number = selectedDatabaseStorage.value,
|
|
186
|
+
assetGb: number = selectedAssetStorage.value,
|
|
187
|
+
) => {
|
|
188
|
+
loading.value = true;
|
|
189
|
+
error.value = null;
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const response = await $fetch<
|
|
193
|
+
apiResponse<{ url?: string; approval_url?: string }>
|
|
194
|
+
>(`${config.public.apiBase}inicontent/billing/checkout`, {
|
|
195
|
+
credentials: "include",
|
|
196
|
+
params: {
|
|
197
|
+
[`${database.value.slug}_sid`]: sessionID.value,
|
|
198
|
+
},
|
|
199
|
+
method: "POST",
|
|
200
|
+
body: {
|
|
201
|
+
database_storage_gb: databaseGb,
|
|
202
|
+
asset_storage_gb: assetGb,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (response?.code === "loginFirst") {
|
|
207
|
+
const authPath =
|
|
208
|
+
database.value?.slug === "inicontent"
|
|
209
|
+
? "/auth"
|
|
210
|
+
: `/${database.value?.slug}/auth`;
|
|
211
|
+
const redirectTo = `/pricing?checkout=1&databaseStorage=${encodeURIComponent(String(databaseGb))}&assetStorage=${encodeURIComponent(String(assetGb))}`;
|
|
212
|
+
await navigateTo(
|
|
213
|
+
`${authPath}?tab=signup&redirectTo=${encodeURIComponent(redirectTo)}`,
|
|
214
|
+
);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const approvalUrl = response.result?.url || response.result?.approval_url;
|
|
219
|
+
if (approvalUrl) window.location.href = approvalUrl;
|
|
220
|
+
|
|
221
|
+
return response;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
error.value =
|
|
224
|
+
err instanceof Error
|
|
225
|
+
? err.message
|
|
226
|
+
: "Failed to create checkout session";
|
|
227
|
+
console.error("Error creating checkout session:", err);
|
|
228
|
+
throw err;
|
|
229
|
+
} finally {
|
|
230
|
+
loading.value = false;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const loadSubscriptionData = async () => {
|
|
235
|
+
if (loadSubscriptionDataPromise) return loadSubscriptionDataPromise;
|
|
236
|
+
|
|
237
|
+
loading.value = true;
|
|
238
|
+
error.value = null;
|
|
239
|
+
|
|
240
|
+
loadSubscriptionDataPromise = (async () => {
|
|
241
|
+
try {
|
|
242
|
+
await Promise.all([fetchPricing(), fetchSubscription(), fetchUsage()]);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
error.value =
|
|
245
|
+
err instanceof Error ? err.message : "Failed to load data";
|
|
246
|
+
console.error("Error loading subscription data:", err);
|
|
247
|
+
} finally {
|
|
248
|
+
loading.value = false;
|
|
249
|
+
}
|
|
250
|
+
})().finally(() => {
|
|
251
|
+
loadSubscriptionDataPromise = null;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return loadSubscriptionDataPromise;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const cancelSubscription = async () => {
|
|
258
|
+
loading.value = true;
|
|
259
|
+
error.value = null;
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const response = await $fetch<apiResponse>(
|
|
263
|
+
`${config.public.apiBase}inicontent/billing/cancel`,
|
|
264
|
+
{
|
|
265
|
+
credentials: "include",
|
|
266
|
+
params: {
|
|
267
|
+
[`${database.value.slug}_sid`]: sessionID.value,
|
|
268
|
+
},
|
|
269
|
+
method: "POST",
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (response?.code === "subscriptionCanceled") {
|
|
274
|
+
subscription.value = { status: "canceled", plan: "base" };
|
|
275
|
+
return response.result;
|
|
276
|
+
}
|
|
277
|
+
throw new Error(
|
|
278
|
+
(response?.code as string) || "Failed to cancel subscription",
|
|
279
|
+
);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
error.value =
|
|
282
|
+
err instanceof Error ? err.message : "Failed to cancel subscription";
|
|
283
|
+
console.error("Error canceling subscription:", err);
|
|
284
|
+
throw err;
|
|
285
|
+
} finally {
|
|
286
|
+
loading.value = false;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const upgradeSubscription = async (
|
|
291
|
+
databaseGb: number = selectedDatabaseStorage.value,
|
|
292
|
+
assetGb: number = selectedAssetStorage.value,
|
|
293
|
+
) => {
|
|
294
|
+
loading.value = true;
|
|
295
|
+
error.value = null;
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const response = await $fetch<
|
|
299
|
+
apiResponse<{ url?: string; approval_url?: string }>
|
|
300
|
+
>(`${config.public.apiBase}inicontent/billing/upgrade`, {
|
|
301
|
+
credentials: "include",
|
|
302
|
+
params: {
|
|
303
|
+
[`${database.value.slug}_sid`]: sessionID.value,
|
|
304
|
+
},
|
|
305
|
+
method: "POST",
|
|
306
|
+
body: {
|
|
307
|
+
database_storage_gb: databaseGb,
|
|
308
|
+
asset_storage_gb: assetGb,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const result = response.result;
|
|
313
|
+
if (result?.url || result?.approval_url)
|
|
314
|
+
window.location.href = (result.url || result.approval_url) as string;
|
|
315
|
+
|
|
316
|
+
return result;
|
|
317
|
+
} catch (err) {
|
|
318
|
+
error.value =
|
|
319
|
+
err instanceof Error ? err.message : "Failed to upgrade subscription";
|
|
320
|
+
console.error("Error upgrading subscription:", err);
|
|
321
|
+
throw err;
|
|
322
|
+
} finally {
|
|
323
|
+
loading.value = false;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const fetchTransactions = async () => {
|
|
328
|
+
loading.value = true;
|
|
329
|
+
error.value = null;
|
|
330
|
+
try {
|
|
331
|
+
const response = await $fetch<
|
|
332
|
+
apiResponse<
|
|
333
|
+
| { transactions: unknown[]; total: number }[]
|
|
334
|
+
| { transactions: unknown[]; total: number }
|
|
335
|
+
>
|
|
336
|
+
>(`${config.public.apiBase}inicontent/billing/transactions`, {
|
|
337
|
+
credentials: "include",
|
|
338
|
+
params: {
|
|
339
|
+
[`${database.value.slug}_sid`]: sessionID.value,
|
|
340
|
+
limit: 50,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
const result = response.result;
|
|
344
|
+
if (Array.isArray(result)) {
|
|
345
|
+
return { transactions: result, total: result.length };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return result || { transactions: [], total: 0 };
|
|
349
|
+
} catch (err) {
|
|
350
|
+
error.value =
|
|
351
|
+
err instanceof Error ? err.message : "Failed to fetch transactions";
|
|
352
|
+
console.error("Error fetching transactions:", err);
|
|
353
|
+
return { transactions: [], total: 0, error: error.value };
|
|
354
|
+
} finally {
|
|
355
|
+
loading.value = false;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const initiateCreditTopup = async (amountUsd: number) => {
|
|
360
|
+
loading.value = true;
|
|
361
|
+
error.value = null;
|
|
362
|
+
try {
|
|
363
|
+
const response = await $fetch<
|
|
364
|
+
apiResponse<{ url?: string; approval_url?: string }>
|
|
365
|
+
>(`${config.public.apiBase}inicontent/billing/credit/topup`, {
|
|
366
|
+
credentials: "include",
|
|
367
|
+
params: { [`${database.value.slug}_sid`]: sessionID.value },
|
|
368
|
+
method: "POST",
|
|
369
|
+
body: { amount_usd: amountUsd },
|
|
370
|
+
});
|
|
371
|
+
const result = response.result;
|
|
372
|
+
if (result?.url || result?.approval_url)
|
|
373
|
+
window.location.href = (result.url || result.approval_url) as string;
|
|
374
|
+
|
|
375
|
+
return result;
|
|
376
|
+
} catch (err) {
|
|
377
|
+
error.value =
|
|
378
|
+
err instanceof Error ? err.message : "Failed to initiate credit top-up";
|
|
379
|
+
console.error("Error initiating credit top-up:", err);
|
|
380
|
+
throw err;
|
|
381
|
+
} finally {
|
|
382
|
+
loading.value = false;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const submitCreditRequest = async (amountUsd: number, note: string) => {
|
|
387
|
+
loading.value = true;
|
|
388
|
+
error.value = null;
|
|
389
|
+
try {
|
|
390
|
+
const response = await $fetch<apiResponse>(
|
|
391
|
+
`${config.public.apiBase}inicontent/billing/credit/request`,
|
|
392
|
+
{
|
|
393
|
+
credentials: "include",
|
|
394
|
+
params: { [`${database.value.slug}_sid`]: sessionID.value },
|
|
395
|
+
method: "POST",
|
|
396
|
+
body: { amount_usd: amountUsd, note },
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
return response.result;
|
|
400
|
+
} catch (err) {
|
|
401
|
+
error.value =
|
|
402
|
+
err instanceof Error ? err.message : "Failed to submit credit request";
|
|
403
|
+
console.error("Error submitting credit request:", err);
|
|
404
|
+
throw err;
|
|
405
|
+
} finally {
|
|
406
|
+
loading.value = false;
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const fetchCreditRequests = async (status = "pending") => {
|
|
411
|
+
loading.value = true;
|
|
412
|
+
error.value = null;
|
|
413
|
+
try {
|
|
414
|
+
const response = await $fetch<
|
|
415
|
+
apiResponse<{ requests: any[]; total: number }>
|
|
416
|
+
>(`${config.public.apiBase}inicontent/billing/credit/requests`, {
|
|
417
|
+
credentials: "include",
|
|
418
|
+
params: { [`${database.value.slug}_sid`]: sessionID.value, status },
|
|
419
|
+
});
|
|
420
|
+
return response.result || { requests: [], total: 0 };
|
|
421
|
+
} catch (err) {
|
|
422
|
+
error.value =
|
|
423
|
+
err instanceof Error ? err.message : "Failed to fetch credit requests";
|
|
424
|
+
console.error("Error fetching credit requests:", err);
|
|
425
|
+
return { requests: [], total: 0 };
|
|
426
|
+
} finally {
|
|
427
|
+
loading.value = false;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const setupAutoCharge = async () => {
|
|
432
|
+
loading.value = true;
|
|
433
|
+
error.value = null;
|
|
434
|
+
try {
|
|
435
|
+
const response = await $fetch<apiResponse<{ approval_url?: string }>>(
|
|
436
|
+
`${config.public.apiBase}inicontent/billing/autocharge/setup`,
|
|
437
|
+
{
|
|
438
|
+
method: "POST",
|
|
439
|
+
credentials: "include",
|
|
440
|
+
params: { [`${database.value.slug}_sid`]: sessionID.value },
|
|
441
|
+
},
|
|
442
|
+
);
|
|
443
|
+
if (response.result.approval_url)
|
|
444
|
+
window.location.href = response.result.approval_url as string;
|
|
445
|
+
} catch (err) {
|
|
446
|
+
error.value =
|
|
447
|
+
err instanceof Error ? err.message : "Failed to set up auto-charge";
|
|
448
|
+
throw err;
|
|
449
|
+
} finally {
|
|
450
|
+
loading.value = false;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const activateAutoCharge = async (approvalSessionId: string) => {
|
|
455
|
+
loading.value = true;
|
|
456
|
+
error.value = null;
|
|
457
|
+
try {
|
|
458
|
+
const response = await $fetch<apiResponse<{ approval_url?: string }>>(
|
|
459
|
+
`${config.public.apiBase}inicontent/billing/autocharge/activate`,
|
|
460
|
+
{
|
|
461
|
+
method: "POST",
|
|
462
|
+
credentials: "include",
|
|
463
|
+
params: {
|
|
464
|
+
[`${database.value.slug}_sid`]: sessionID.value,
|
|
465
|
+
approval_session_id: approvalSessionId,
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
);
|
|
469
|
+
await fetchSubscription();
|
|
470
|
+
return response.result;
|
|
471
|
+
} catch (err) {
|
|
472
|
+
error.value =
|
|
473
|
+
err instanceof Error ? err.message : "Failed to activate auto-charge";
|
|
474
|
+
throw err;
|
|
475
|
+
} finally {
|
|
476
|
+
loading.value = false;
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const disableAutoCharge = async () => {
|
|
481
|
+
loading.value = true;
|
|
482
|
+
error.value = null;
|
|
483
|
+
try {
|
|
484
|
+
await $fetch<apiResponse>(
|
|
485
|
+
`${config.public.apiBase}inicontent/billing/autocharge/disable`,
|
|
486
|
+
{
|
|
487
|
+
method: "POST",
|
|
488
|
+
credentials: "include",
|
|
489
|
+
params: { [`${database.value.slug}_sid`]: sessionID.value },
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
await fetchSubscription();
|
|
493
|
+
} catch (err) {
|
|
494
|
+
error.value =
|
|
495
|
+
err instanceof Error ? err.message : "Failed to disable auto-charge";
|
|
496
|
+
throw err;
|
|
497
|
+
} finally {
|
|
498
|
+
loading.value = false;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const approveCreditRequest = async (id: string) => {
|
|
503
|
+
loading.value = true;
|
|
504
|
+
error.value = null;
|
|
505
|
+
try {
|
|
506
|
+
const response = await $fetch<apiResponse>(
|
|
507
|
+
`${config.public.apiBase}inicontent/billing/credit/request/${id}/approve`,
|
|
508
|
+
{
|
|
509
|
+
credentials: "include",
|
|
510
|
+
params: { [`${database.value.slug}_sid`]: sessionID.value },
|
|
511
|
+
method: "POST",
|
|
512
|
+
},
|
|
513
|
+
);
|
|
514
|
+
return response.result;
|
|
515
|
+
} catch (err) {
|
|
516
|
+
error.value =
|
|
517
|
+
err instanceof Error ? err.message : "Failed to approve credit request";
|
|
518
|
+
throw err;
|
|
519
|
+
} finally {
|
|
520
|
+
loading.value = false;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const rejectCreditRequest = async (id: string, reason?: string) => {
|
|
525
|
+
loading.value = true;
|
|
526
|
+
error.value = null;
|
|
527
|
+
try {
|
|
528
|
+
const response = await $fetch<apiResponse>(
|
|
529
|
+
`${config.public.apiBase}inicontent/billing/credit/request/${id}/reject`,
|
|
530
|
+
{
|
|
531
|
+
credentials: "include",
|
|
532
|
+
params: { [`${database.value.slug}_sid`]: sessionID.value },
|
|
533
|
+
method: "POST",
|
|
534
|
+
body: { reason },
|
|
535
|
+
},
|
|
536
|
+
);
|
|
537
|
+
return response.result;
|
|
538
|
+
} catch (err) {
|
|
539
|
+
error.value =
|
|
540
|
+
err instanceof Error ? err.message : "Failed to reject credit request";
|
|
541
|
+
throw err;
|
|
542
|
+
} finally {
|
|
543
|
+
loading.value = false;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const adminAdjustCredit = async (
|
|
548
|
+
userId: string,
|
|
549
|
+
amountCents: number,
|
|
550
|
+
note?: string,
|
|
551
|
+
) => {
|
|
552
|
+
loading.value = true;
|
|
553
|
+
error.value = null;
|
|
554
|
+
try {
|
|
555
|
+
const response = await $fetch<apiResponse>(
|
|
556
|
+
`${config.public.apiBase}inicontent/billing/credit/adjust`,
|
|
557
|
+
{
|
|
558
|
+
credentials: "include",
|
|
559
|
+
params: { [`${database.value.slug}_sid`]: sessionID.value },
|
|
560
|
+
method: "POST",
|
|
561
|
+
body: { userId, amount_cents: amountCents, note },
|
|
562
|
+
},
|
|
563
|
+
);
|
|
564
|
+
return response.result;
|
|
565
|
+
} catch (err) {
|
|
566
|
+
error.value =
|
|
567
|
+
err instanceof Error ? err.message : "Failed to adjust credit";
|
|
568
|
+
throw err;
|
|
569
|
+
} finally {
|
|
570
|
+
loading.value = false;
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const setCustomPlan = async (
|
|
575
|
+
userId: string,
|
|
576
|
+
monthlyAmountUsd: number,
|
|
577
|
+
usagePriceUsdPerGb: number,
|
|
578
|
+
) => {
|
|
579
|
+
loading.value = true;
|
|
580
|
+
error.value = null;
|
|
581
|
+
try {
|
|
582
|
+
const response = await $fetch<apiResponse>(
|
|
583
|
+
`${config.public.apiBase}inicontent/billing/custom-plan`,
|
|
584
|
+
{
|
|
585
|
+
credentials: "include",
|
|
586
|
+
params: { [`${database.value.slug}_sid`]: sessionID.value },
|
|
587
|
+
method: "POST",
|
|
588
|
+
body: {
|
|
589
|
+
userId,
|
|
590
|
+
monthly_amount_cents: Math.round(monthlyAmountUsd * 100),
|
|
591
|
+
usage_price_cents_per_gb: Math.round(usagePriceUsdPerGb * 100),
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
if (response?.code === "customPlanUpdated") {
|
|
597
|
+
await fetchSubscription();
|
|
598
|
+
return response.result;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
throw new Error(
|
|
602
|
+
(response?.code as string) || "Failed to update custom plan",
|
|
603
|
+
);
|
|
604
|
+
} catch (err) {
|
|
605
|
+
error.value =
|
|
606
|
+
err instanceof Error ? err.message : "Failed to update custom plan";
|
|
607
|
+
throw err;
|
|
608
|
+
} finally {
|
|
609
|
+
loading.value = false;
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
// State
|
|
615
|
+
pricing,
|
|
616
|
+
subscription,
|
|
617
|
+
usage,
|
|
618
|
+
loading,
|
|
619
|
+
error,
|
|
620
|
+
selectedDatabaseStorage,
|
|
621
|
+
selectedAssetStorage,
|
|
622
|
+
|
|
623
|
+
// Computed
|
|
624
|
+
isDatabaseStorageExceeded,
|
|
625
|
+
isAssetStorageExceeded,
|
|
626
|
+
hasActiveSubscription,
|
|
627
|
+
storagePercentageDB,
|
|
628
|
+
storagePercentageAsset,
|
|
629
|
+
|
|
630
|
+
// Methods
|
|
631
|
+
fetchPricing,
|
|
632
|
+
fetchSubscription,
|
|
633
|
+
fetchUsage,
|
|
634
|
+
createCheckoutSession,
|
|
635
|
+
loadSubscriptionData,
|
|
636
|
+
cancelSubscription,
|
|
637
|
+
upgradeSubscription,
|
|
638
|
+
fetchTransactions,
|
|
639
|
+
initiateCreditTopup,
|
|
640
|
+
submitCreditRequest,
|
|
641
|
+
fetchCreditRequests,
|
|
642
|
+
approveCreditRequest,
|
|
643
|
+
rejectCreditRequest,
|
|
644
|
+
adminAdjustCredit,
|
|
645
|
+
setCustomPlan,
|
|
646
|
+
setupAutoCharge,
|
|
647
|
+
activateAutoCharge,
|
|
648
|
+
disableAutoCharge,
|
|
649
|
+
};
|
|
650
|
+
};
|