@carlonicora/nextjs-jsonapi 1.34.0 → 1.36.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/dist/{BlockNoteEditor-DFHJWZZB.mjs → BlockNoteEditor-KFUTQVUK.mjs} +3 -3
- package/dist/{BlockNoteEditor-TS5AN6W2.js → BlockNoteEditor-N3J42SBY.js} +13 -13
- package/dist/{BlockNoteEditor-TS5AN6W2.js.map → BlockNoteEditor-N3J42SBY.js.map} +1 -1
- package/dist/BlockNoteEditor-YOAJRPWU.css +29 -0
- package/dist/BlockNoteEditor-YOAJRPWU.css.map +1 -0
- package/dist/billing/index.css +29 -0
- package/dist/billing/index.css.map +1 -0
- package/dist/billing/index.d.mts +1 -1
- package/dist/billing/index.d.ts +1 -1
- package/dist/billing/index.js +374 -358
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +22 -6
- package/dist/billing/index.mjs.map +1 -1
- package/dist/{chunk-GFRODCD7.mjs → chunk-DLJTN632.mjs} +1242 -981
- package/dist/chunk-DLJTN632.mjs.map +1 -0
- package/dist/{chunk-TZRAOUAR.js → chunk-NNCTRU4O.js} +29 -1
- package/dist/chunk-NNCTRU4O.js.map +1 -0
- package/dist/{chunk-UPA67DQF.js → chunk-S2RZBQP4.js} +977 -716
- package/dist/chunk-S2RZBQP4.js.map +1 -0
- package/dist/{chunk-HWQBSVBT.mjs → chunk-YZV24UWN.mjs} +29 -1
- package/dist/chunk-YZV24UWN.mjs.map +1 -0
- package/dist/client/index.css +29 -0
- package/dist/client/index.css.map +1 -0
- package/dist/client/index.js +3 -3
- package/dist/client/index.mjs +2 -2
- package/dist/components/index.css +29 -0
- package/dist/components/index.css.map +1 -0
- package/dist/components/index.d.mts +4 -1
- package/dist/components/index.d.ts +4 -1
- package/dist/components/index.js +5 -3
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +4 -2
- package/dist/contexts/index.css +29 -0
- package/dist/contexts/index.css.map +1 -0
- package/dist/contexts/index.d.mts +9 -2
- package/dist/contexts/index.d.ts +9 -2
- package/dist/contexts/index.js +9 -3
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +8 -2
- package/dist/core/index.d.mts +6 -4
- package/dist/core/index.d.ts +6 -4
- package/dist/core/index.js +2 -2
- package/dist/core/index.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.mjs +1 -1
- package/dist/onboarding.interface-Djyl9qYu.d.mts +71 -0
- package/dist/onboarding.interface-Djyl9qYu.d.ts +71 -0
- package/dist/{s3.service-DcqkGrKD.d.ts → s3.service-DXkDoMf1.d.ts} +3 -0
- package/dist/{s3.service-ag6M_7GO.d.mts → s3.service-hnTPVTm2.d.mts} +3 -0
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/dist/{stripe-subscription.interface-_VWPY2AA.d.mts → stripe-subscription.interface-C8uhCYIZ.d.mts} +2 -0
- package/dist/{stripe-subscription.interface-Dm__xmvE.d.ts → stripe-subscription.interface-DK7BJaNd.d.ts} +2 -0
- package/package.json +3 -1
- package/src/components/index.ts +1 -0
- package/src/components/navigations/Header.tsx +2 -32
- package/src/contexts/index.ts +1 -0
- package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +22 -6
- package/src/features/billing/stripe-price/data/stripe-price.interface.ts +2 -0
- package/src/features/billing/stripe-price/data/stripe-price.ts +9 -0
- package/src/features/billing/stripe-subscription/components/widgets/ProductPricingList.tsx +3 -0
- package/src/features/company/components/forms/CompanyDeleter.tsx +157 -70
- package/src/features/company/contexts/CompanyContext.tsx +1 -1
- package/src/features/company/data/company.service.ts +23 -0
- package/src/features/onboarding/components/OnboardingCard.tsx +45 -0
- package/src/features/onboarding/components/index.ts +1 -0
- package/src/features/onboarding/contexts/OnboardingContext.tsx +213 -0
- package/src/features/onboarding/contexts/index.ts +2 -0
- package/src/features/onboarding/index.ts +3 -0
- package/src/features/onboarding/interfaces/index.ts +1 -0
- package/src/features/onboarding/interfaces/onboarding.interface.ts +93 -0
- package/src/features/onboarding/styles/onboarding.css +38 -0
- package/src/features/user/contexts/CurrentUserContext.tsx +35 -20
- package/src/shadcnui/ui/card.tsx +22 -47
- package/dist/chunk-GFRODCD7.mjs.map +0 -1
- package/dist/chunk-HWQBSVBT.mjs.map +0 -1
- package/dist/chunk-TZRAOUAR.js.map +0 -1
- package/dist/chunk-UPA67DQF.js.map +0 -1
- /package/dist/{BlockNoteEditor-DFHJWZZB.mjs.map → BlockNoteEditor-KFUTQVUK.mjs.map} +0 -0
|
@@ -38,6 +38,7 @@ type PriceFormValues = {
|
|
|
38
38
|
usageType?: "licensed" | "metered";
|
|
39
39
|
nickname?: string;
|
|
40
40
|
active: boolean;
|
|
41
|
+
isTrial: boolean;
|
|
41
42
|
description?: string;
|
|
42
43
|
features: string[];
|
|
43
44
|
token: string;
|
|
@@ -75,6 +76,7 @@ export function PriceEditor({ productId, price, open, onOpenChange, onSuccess }:
|
|
|
75
76
|
usageType: z.enum(["licensed", "metered"]).optional(),
|
|
76
77
|
nickname: z.string().optional(),
|
|
77
78
|
active: z.boolean(),
|
|
79
|
+
isTrial: z.boolean(),
|
|
78
80
|
description: z.string().optional(),
|
|
79
81
|
features: z.array(z.string()),
|
|
80
82
|
token: z.string(),
|
|
@@ -102,6 +104,7 @@ export function PriceEditor({ productId, price, open, onOpenChange, onSuccess }:
|
|
|
102
104
|
usageType: price?.recurring?.usageType || "licensed",
|
|
103
105
|
nickname: price?.nickname || "",
|
|
104
106
|
active: price?.active ?? true,
|
|
107
|
+
isTrial: price?.isTrial ?? false,
|
|
105
108
|
description: price?.description || "",
|
|
106
109
|
features: price?.features || [],
|
|
107
110
|
token: price?.token?.toString() ?? "",
|
|
@@ -126,6 +129,7 @@ export function PriceEditor({ productId, price, open, onOpenChange, onSuccess }:
|
|
|
126
129
|
usageType: price?.recurring?.usageType || "licensed",
|
|
127
130
|
nickname: price?.nickname || "",
|
|
128
131
|
active: price?.active ?? true,
|
|
132
|
+
isTrial: price?.isTrial ?? false,
|
|
129
133
|
description: price?.description || "",
|
|
130
134
|
features: price?.features || [],
|
|
131
135
|
token: price?.token?.toString() ?? "",
|
|
@@ -145,15 +149,15 @@ export function PriceEditor({ productId, price, open, onOpenChange, onSuccess }:
|
|
|
145
149
|
const unitAmountInCents = Math.round(values.unitAmount * 100);
|
|
146
150
|
|
|
147
151
|
if (isEditMode) {
|
|
148
|
-
// Update existing price (nickname, description, features, token can be updated - Stripe fields are limited)
|
|
152
|
+
// Update existing price (nickname, description, features, token, isTrial can be updated - Stripe fields are limited)
|
|
149
153
|
await StripePriceService.updatePrice({
|
|
150
154
|
id: price.id,
|
|
151
155
|
nickname: values.nickname || undefined,
|
|
152
156
|
description: values.description || undefined,
|
|
153
157
|
features: values.features.filter((f) => f.trim()) || undefined,
|
|
154
158
|
token: values.token ? parseInt(values.token, 10) : undefined,
|
|
155
|
-
// Only include featureIds for recurring prices
|
|
156
|
-
...(price?.priceType === "recurring" ? { featureIds: values.featureIds } : {}),
|
|
159
|
+
// Only include isTrial and featureIds for recurring prices
|
|
160
|
+
...(price?.priceType === "recurring" ? { isTrial: values.isTrial, featureIds: values.featureIds } : {}),
|
|
157
161
|
});
|
|
158
162
|
} else {
|
|
159
163
|
// Create new price
|
|
@@ -190,9 +194,12 @@ export function PriceEditor({ productId, price, open, onOpenChange, onSuccess }:
|
|
|
190
194
|
createInput.token = parseInt(values.token, 10);
|
|
191
195
|
}
|
|
192
196
|
|
|
193
|
-
// Add platform feature IDs only for recurring prices (Neo4j only, not sent to Stripe)
|
|
194
|
-
if (isRecurring
|
|
195
|
-
createInput.
|
|
197
|
+
// Add isTrial and platform feature IDs only for recurring prices (Neo4j only, not sent to Stripe)
|
|
198
|
+
if (isRecurring) {
|
|
199
|
+
createInput.isTrial = values.isTrial;
|
|
200
|
+
if (values.featureIds.length > 0) {
|
|
201
|
+
createInput.featureIds = values.featureIds;
|
|
202
|
+
}
|
|
196
203
|
}
|
|
197
204
|
|
|
198
205
|
await StripePriceService.createPrice(createInput);
|
|
@@ -403,6 +410,15 @@ export function PriceEditor({ productId, price, open, onOpenChange, onSuccess }:
|
|
|
403
410
|
|
|
404
411
|
<FormCheckbox form={form} id="active" name="Active" />
|
|
405
412
|
|
|
413
|
+
{isRecurring && (
|
|
414
|
+
<FormCheckbox
|
|
415
|
+
form={form}
|
|
416
|
+
id="isTrial"
|
|
417
|
+
name="Trial Price"
|
|
418
|
+
description="Mark this as the trial subscription plan (only one price should be marked as trial)"
|
|
419
|
+
/>
|
|
420
|
+
)}
|
|
421
|
+
|
|
406
422
|
<CommonEditorButtons isEdit={isEditMode} form={form} disabled={isSubmitting} setOpen={onOpenChange} />
|
|
407
423
|
</form>
|
|
408
424
|
</Form>
|
|
@@ -21,6 +21,7 @@ export interface StripePriceInterface extends ApiDataInterface {
|
|
|
21
21
|
get description(): string | undefined;
|
|
22
22
|
get features(): string[] | undefined;
|
|
23
23
|
get token(): number | undefined;
|
|
24
|
+
get isTrial(): boolean | undefined;
|
|
24
25
|
get priceFeatures(): FeatureInterface[]; // Platform Feature entities linked to this price
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -49,5 +50,6 @@ export type StripePriceInput = {
|
|
|
49
50
|
description?: string;
|
|
50
51
|
features?: string[];
|
|
51
52
|
token?: number;
|
|
53
|
+
isTrial?: boolean;
|
|
52
54
|
featureIds?: string[]; // Feature entity IDs to link (Neo4j only, NOT sent to Stripe)
|
|
53
55
|
};
|
|
@@ -18,6 +18,7 @@ export class StripePrice extends AbstractApiData implements StripePriceInterface
|
|
|
18
18
|
private _description?: string;
|
|
19
19
|
private _features?: string[];
|
|
20
20
|
private _token?: number;
|
|
21
|
+
private _isTrial?: boolean;
|
|
21
22
|
private _priceFeatures: FeatureInterface[] = []; // Platform Feature entities
|
|
22
23
|
|
|
23
24
|
get stripePriceId(): string {
|
|
@@ -80,6 +81,10 @@ export class StripePrice extends AbstractApiData implements StripePriceInterface
|
|
|
80
81
|
return this._token;
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
get isTrial(): boolean | undefined {
|
|
85
|
+
return this._isTrial;
|
|
86
|
+
}
|
|
87
|
+
|
|
83
88
|
get priceFeatures(): FeatureInterface[] {
|
|
84
89
|
return this._priceFeatures;
|
|
85
90
|
}
|
|
@@ -120,6 +125,7 @@ export class StripePrice extends AbstractApiData implements StripePriceInterface
|
|
|
120
125
|
: undefined;
|
|
121
126
|
|
|
122
127
|
this._token = data.jsonApi.attributes.token;
|
|
128
|
+
this._isTrial = data.jsonApi.attributes.isTrial;
|
|
123
129
|
|
|
124
130
|
// Hydrate product relationship
|
|
125
131
|
this._product = this._readIncluded(data, "product", Modules.StripeProduct) as StripeProductInterface;
|
|
@@ -169,6 +175,9 @@ export class StripePrice extends AbstractApiData implements StripePriceInterface
|
|
|
169
175
|
if ("token" in data && data.token !== undefined) {
|
|
170
176
|
response.data.attributes.token = data.token;
|
|
171
177
|
}
|
|
178
|
+
if ("isTrial" in data && data.isTrial !== undefined) {
|
|
179
|
+
response.data.attributes.isTrial = data.isTrial;
|
|
180
|
+
}
|
|
172
181
|
|
|
173
182
|
// Convert featureIds to JSON:API relationships format
|
|
174
183
|
if (data.featureIds && data.featureIds.length > 0) {
|
|
@@ -84,6 +84,9 @@ export function ProductPricingList({
|
|
|
84
84
|
pricesToFilter = pricesToFilter.filter((price) => price.priceType !== "one_time");
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Filter out trial prices - users shouldn't manually select trial plans
|
|
88
|
+
pricesToFilter = pricesToFilter.filter((price) => !price.isTrial);
|
|
89
|
+
|
|
87
90
|
const filteredPrices = getFilteredPrices(pricesToFilter, selectedInterval);
|
|
88
91
|
|
|
89
92
|
if (filteredPrices.length === 0) {
|
|
@@ -5,7 +5,7 @@ import { useTranslations } from "next-intl";
|
|
|
5
5
|
import { useState } from "react";
|
|
6
6
|
import { errorToast } from "../../../../components";
|
|
7
7
|
import { Modules } from "../../../../core";
|
|
8
|
-
import {
|
|
8
|
+
import { usePageUrlGenerator } from "../../../../hooks";
|
|
9
9
|
import { Action } from "../../../../permissions";
|
|
10
10
|
import { getRoleId } from "../../../../roles";
|
|
11
11
|
import {
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
Input,
|
|
20
20
|
Label,
|
|
21
21
|
} from "../../../../shadcnui";
|
|
22
|
+
import { AuthService } from "../../../auth/data";
|
|
22
23
|
import { UserInterface } from "../../../user";
|
|
23
24
|
import { useCurrentUserContext } from "../../../user/contexts";
|
|
24
25
|
import { CompanyInterface } from "../../data";
|
|
@@ -28,97 +29,183 @@ type CompanyDeleterProps = {
|
|
|
28
29
|
company: CompanyInterface;
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
type CompanyDeleterInternalProps = CompanyDeleterProps & {
|
|
33
|
+
isAdministrator: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function CompanyDeleterInternal({ company, isAdministrator }: CompanyDeleterInternalProps) {
|
|
32
37
|
const t = useTranslations();
|
|
33
|
-
const
|
|
38
|
+
const generateUrl = usePageUrlGenerator();
|
|
34
39
|
const [open, setOpen] = useState<boolean>(false);
|
|
40
|
+
const [finalWarningOpen, setFinalWarningOpen] = useState<boolean>(false);
|
|
35
41
|
const [isDeleting, setIsDeleting] = useState<boolean>(false);
|
|
36
42
|
const [companyName, setCompanyName] = useState<string>("");
|
|
37
43
|
|
|
38
|
-
const
|
|
44
|
+
const handleProceedToFinalWarning = () => {
|
|
45
|
+
console.log("[CompanyDeleter] Proceeding to final warning dialog");
|
|
46
|
+
console.log("[CompanyDeleter] Company name entered:", companyName);
|
|
47
|
+
console.log("[CompanyDeleter] Expected company name:", company.name);
|
|
48
|
+
setOpen(false);
|
|
49
|
+
setFinalWarningOpen(true);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleFinalDelete = async () => {
|
|
53
|
+
console.log("[CompanyDeleter] handleFinalDelete called");
|
|
54
|
+
console.log("[CompanyDeleter] isAdministrator:", isAdministrator);
|
|
55
|
+
console.log("[CompanyDeleter] company.id:", company.id);
|
|
56
|
+
console.log("[CompanyDeleter] company.name:", company.name);
|
|
57
|
+
|
|
39
58
|
setIsDeleting(true);
|
|
40
59
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
if (isAdministrator) {
|
|
61
|
+
console.log("[CompanyDeleter] Using Administrator delete endpoint");
|
|
62
|
+
await CompanyService.delete({ companyId: company.id });
|
|
63
|
+
} else {
|
|
64
|
+
console.log("[CompanyDeleter] Using CompanyAdministrator self-delete endpoint");
|
|
65
|
+
await CompanyService.selfDelete({ companyId: company.id });
|
|
66
|
+
}
|
|
67
|
+
console.log("[CompanyDeleter] Delete successful, logging out user");
|
|
68
|
+
await AuthService.logout();
|
|
69
|
+
window.location.href = generateUrl({ page: `/` });
|
|
43
70
|
} catch (error) {
|
|
71
|
+
console.error("[CompanyDeleter] Delete failed with error:", error);
|
|
72
|
+
console.error("[CompanyDeleter] Error details:", JSON.stringify(error, null, 2));
|
|
44
73
|
errorToast({ title: t(`common.errors.delete`), error: error });
|
|
74
|
+
setIsDeleting(false);
|
|
45
75
|
}
|
|
46
|
-
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleGoBack = () => {
|
|
79
|
+
setFinalWarningOpen(false);
|
|
80
|
+
setCompanyName("");
|
|
47
81
|
};
|
|
48
82
|
|
|
49
83
|
return (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{t(`ui.buttons.delete`)}
|
|
55
|
-
</Button>
|
|
56
|
-
</AlertDialogTrigger>
|
|
57
|
-
<AlertDialogContent className={`flex max-h-[70vh] max-w-3xl flex-col overflow-y-auto`}>
|
|
58
|
-
<AlertDialogHeader>
|
|
59
|
-
<AlertDialogTitle>
|
|
60
|
-
{t(`common.delete.title`, { type: t(`entities.companies`, { count: 1 }) })}
|
|
61
|
-
</AlertDialogTitle>
|
|
62
|
-
<AlertDialogDescription>
|
|
63
|
-
{t(`common.delete.subtitle`, { type: t(`entities.companies`, { count: 1 }) })}
|
|
64
|
-
</AlertDialogDescription>
|
|
65
|
-
</AlertDialogHeader>
|
|
66
|
-
<div className="text-destructive p-4 text-sm">
|
|
67
|
-
{t(`common.delete.description`, { type: t(`entities.companies`, { count: 1 }) })}
|
|
68
|
-
</div>
|
|
69
|
-
<div className="flex w-full flex-col gap-y-2">
|
|
70
|
-
<div>{t(`common.delete.confirmation`, { type: t(`entities.companies`, { count: 1 }) })}</div>
|
|
71
|
-
<div className="flex w-full flex-col">
|
|
72
|
-
<Label className="flex items-center">
|
|
73
|
-
{t(`company.fields.name.label`)}
|
|
74
|
-
<span className="text-destructive ml-2 font-semibold">*</span>
|
|
75
|
-
</Label>
|
|
76
|
-
<Input
|
|
77
|
-
className={`w-full`}
|
|
78
|
-
placeholder={t(`company.fields.name.placeholder`)}
|
|
79
|
-
onChange={(e) => setCompanyName(e.target.value)}
|
|
80
|
-
/>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
<div className="flex justify-end">
|
|
84
|
+
<>
|
|
85
|
+
{/* Dialog 1: Company Name Confirmation */}
|
|
86
|
+
<AlertDialog open={open} onOpenChange={setOpen}>
|
|
87
|
+
<AlertDialogTrigger>
|
|
84
88
|
<Button
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
render={<div />}
|
|
90
|
+
nativeButton={false}
|
|
91
|
+
size="sm"
|
|
92
|
+
variant="ghost"
|
|
93
|
+
className="text-muted-foreground hover:text-destructive"
|
|
90
94
|
>
|
|
91
|
-
|
|
95
|
+
<Trash2Icon />
|
|
92
96
|
</Button>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
97
|
+
</AlertDialogTrigger>
|
|
98
|
+
<AlertDialogContent className={`flex max-h-[70vh] max-w-3xl flex-col overflow-y-auto`}>
|
|
99
|
+
<AlertDialogHeader>
|
|
100
|
+
<AlertDialogTitle>
|
|
101
|
+
{t(`common.delete.title`, { type: t(`entities.companies`, { count: 1 }) })}
|
|
102
|
+
</AlertDialogTitle>
|
|
103
|
+
<AlertDialogDescription>
|
|
104
|
+
{t(`common.delete.subtitle`, { type: t(`entities.companies`, { count: 1 }) })}
|
|
105
|
+
</AlertDialogDescription>
|
|
106
|
+
</AlertDialogHeader>
|
|
107
|
+
<div className="text-destructive p-4 text-sm">
|
|
108
|
+
{t(`common.delete.description`, { type: t(`entities.companies`, { count: 1 }) })}
|
|
109
|
+
</div>
|
|
110
|
+
<div className="flex w-full flex-col gap-y-2">
|
|
111
|
+
<div>{t(`common.delete.confirmation`, { type: t(`entities.companies`, { count: 1 }) })}</div>
|
|
112
|
+
<div className="flex w-full flex-col">
|
|
113
|
+
<Label className="flex items-center">
|
|
114
|
+
{t(`company.fields.name.label`)}
|
|
115
|
+
<span className="text-destructive ml-2 font-semibold">*</span>
|
|
116
|
+
</Label>
|
|
117
|
+
<Input
|
|
118
|
+
className={`w-full`}
|
|
119
|
+
placeholder={t(`company.fields.name.placeholder`)}
|
|
120
|
+
onChange={(e) => setCompanyName(e.target.value)}
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
<div className="flex justify-end">
|
|
125
|
+
<Button className="mr-2" variant={"outline"} type={`button`} onClick={() => setOpen(false)}>
|
|
126
|
+
{t(`ui.buttons.cancel`)}
|
|
127
|
+
</Button>
|
|
128
|
+
<Button
|
|
129
|
+
type="submit"
|
|
130
|
+
onClick={(e) => {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
handleProceedToFinalWarning();
|
|
133
|
+
}}
|
|
134
|
+
variant={"destructive"}
|
|
135
|
+
disabled={company.name !== companyName}
|
|
136
|
+
>
|
|
137
|
+
{t(`ui.buttons.delete`)}
|
|
138
|
+
</Button>
|
|
139
|
+
</div>
|
|
140
|
+
</AlertDialogContent>
|
|
141
|
+
</AlertDialog>
|
|
142
|
+
|
|
143
|
+
{/* Dialog 2: Final Warning */}
|
|
144
|
+
<AlertDialog open={finalWarningOpen} onOpenChange={setFinalWarningOpen}>
|
|
145
|
+
<AlertDialogContent className={`flex max-h-[70vh] max-w-3xl flex-col overflow-y-auto`}>
|
|
146
|
+
<AlertDialogHeader>
|
|
147
|
+
<AlertDialogTitle>{t(`common.delete.finalWarning.title`)}</AlertDialogTitle>
|
|
148
|
+
<AlertDialogDescription>{t(`common.delete.finalWarning.subtitle`)}</AlertDialogDescription>
|
|
149
|
+
</AlertDialogHeader>
|
|
150
|
+
<div className="bg-destructive/10 border-destructive text-destructive rounded-md border p-4 text-sm">
|
|
151
|
+
<p className="mb-3 font-semibold">{t(`common.delete.finalWarning.description`)}</p>
|
|
152
|
+
<ul className="list-disc space-y-1 pl-5">
|
|
153
|
+
<li>{t(`common.delete.finalWarning.bullet1`)}</li>
|
|
154
|
+
<li>{t(`common.delete.finalWarning.bullet2`)}</li>
|
|
155
|
+
<li>{t(`common.delete.finalWarning.bullet3`)}</li>
|
|
156
|
+
<li>{t(`common.delete.finalWarning.bullet4`)}</li>
|
|
157
|
+
<li>{t(`common.delete.finalWarning.bullet5`)}</li>
|
|
158
|
+
</ul>
|
|
159
|
+
</div>
|
|
160
|
+
<div className="flex justify-end">
|
|
161
|
+
<Button className="mr-2" variant={"outline"} type={`button`} onClick={handleGoBack} disabled={isDeleting}>
|
|
162
|
+
{t(`ui.buttons.go_back`)}
|
|
163
|
+
</Button>
|
|
164
|
+
<Button
|
|
165
|
+
type="submit"
|
|
166
|
+
onClick={(e) => {
|
|
167
|
+
e.preventDefault();
|
|
168
|
+
handleFinalDelete();
|
|
169
|
+
}}
|
|
170
|
+
variant={"destructive"}
|
|
171
|
+
disabled={isDeleting}
|
|
172
|
+
>
|
|
173
|
+
{isDeleting ? (
|
|
174
|
+
<>
|
|
175
|
+
{t(`ui.buttons.deleting`)}
|
|
176
|
+
<LoaderCircleIcon className="animate-spin-slow h-5 w-5" />
|
|
177
|
+
</>
|
|
178
|
+
) : (
|
|
179
|
+
t(`ui.buttons.delete_forever`)
|
|
180
|
+
)}
|
|
181
|
+
</Button>
|
|
182
|
+
</div>
|
|
183
|
+
</AlertDialogContent>
|
|
184
|
+
</AlertDialog>
|
|
185
|
+
</>
|
|
114
186
|
);
|
|
115
187
|
}
|
|
116
188
|
|
|
117
189
|
export function CompanyDeleter({ company }: CompanyDeleterProps) {
|
|
118
190
|
const { hasPermissionToModule, hasRole } = useCurrentUserContext<UserInterface>();
|
|
119
191
|
|
|
120
|
-
|
|
192
|
+
const isAdministrator = hasRole(getRoleId().Administrator);
|
|
193
|
+
const isCompanyAdministrator = hasRole(getRoleId().CompanyAdministrator);
|
|
194
|
+
const hasDeletePermission = hasPermissionToModule({ module: Modules.Company, action: Action.Delete });
|
|
195
|
+
|
|
196
|
+
console.log("[CompanyDeleter] Role check:", {
|
|
197
|
+
isAdministrator,
|
|
198
|
+
isCompanyAdministrator,
|
|
199
|
+
hasDeletePermission,
|
|
200
|
+
companyId: company.id,
|
|
201
|
+
companyName: company.name,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (!isAdministrator && !isCompanyAdministrator && !hasDeletePermission) {
|
|
205
|
+
console.log("[CompanyDeleter] Access denied - no delete permissions");
|
|
121
206
|
return null;
|
|
207
|
+
}
|
|
122
208
|
|
|
123
|
-
|
|
209
|
+
console.log("[CompanyDeleter] Access granted, rendering with isAdministrator:", isAdministrator);
|
|
210
|
+
return <CompanyDeleterInternal company={company} isAdministrator={isAdministrator} />;
|
|
124
211
|
}
|
|
@@ -62,7 +62,7 @@ export const CompanyProvider = ({ children, dehydratedCompany }: CompanyProvider
|
|
|
62
62
|
|
|
63
63
|
if (
|
|
64
64
|
company &&
|
|
65
|
-
hasRole(getRoleId().Administrator) &&
|
|
65
|
+
(hasRole(getRoleId().Administrator) || hasRole(getRoleId().CompanyAdministrator)) &&
|
|
66
66
|
hasPermissionToModule({ module: Modules.Company, action: Action.Delete })
|
|
67
67
|
)
|
|
68
68
|
functions.push(<CompanyDeleter key="companyDeleter" company={company} />);
|
|
@@ -31,6 +31,29 @@ export class CompanyService extends AbstractService {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
static async selfDelete(params: { companyId: string }): Promise<void> {
|
|
35
|
+
const endpoint = new EndpointCreator({
|
|
36
|
+
endpoint: Modules.Company,
|
|
37
|
+
id: params.companyId,
|
|
38
|
+
childEndpoint: "self-delete",
|
|
39
|
+
}).generate();
|
|
40
|
+
|
|
41
|
+
console.log("[CompanyService.selfDelete] Called with companyId:", params.companyId);
|
|
42
|
+
console.log("[CompanyService.selfDelete] Generated endpoint:", endpoint);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await this.callApi({
|
|
46
|
+
type: Modules.Company,
|
|
47
|
+
method: HttpMethod.DELETE,
|
|
48
|
+
endpoint: endpoint,
|
|
49
|
+
});
|
|
50
|
+
console.log("[CompanyService.selfDelete] API call successful");
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("[CompanyService.selfDelete] API call failed:", error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
34
57
|
static async create(params: CompanyInput): Promise<CompanyInterface> {
|
|
35
58
|
return this.callApi({
|
|
36
59
|
type: Modules.Company,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { X } from "lucide-react";
|
|
4
|
+
import { Button, Card, CardContent, CardHeader, CardTitle } from "../../../shadcnui";
|
|
5
|
+
import { OnboardingCardRenderProps } from "../interfaces";
|
|
6
|
+
|
|
7
|
+
// Matches a360ai ShepherdCard design exactly
|
|
8
|
+
export function OnboardingCard({
|
|
9
|
+
step,
|
|
10
|
+
currentIndex,
|
|
11
|
+
totalSteps,
|
|
12
|
+
labels,
|
|
13
|
+
onNext,
|
|
14
|
+
onPrevious,
|
|
15
|
+
onClose,
|
|
16
|
+
isFirst,
|
|
17
|
+
isLast,
|
|
18
|
+
}: OnboardingCardRenderProps) {
|
|
19
|
+
return (
|
|
20
|
+
<Card className="w-[320px] relative" data-shepherd-card>
|
|
21
|
+
<Button
|
|
22
|
+
variant="ghost"
|
|
23
|
+
size="icon"
|
|
24
|
+
className="absolute right-2 top-2"
|
|
25
|
+
onClick={onClose}
|
|
26
|
+
aria-label={labels.close}
|
|
27
|
+
>
|
|
28
|
+
<X className="h-4 w-4" />
|
|
29
|
+
</Button>
|
|
30
|
+
<CardHeader>
|
|
31
|
+
<CardTitle className="flex items-center gap-2">{step.title}</CardTitle>
|
|
32
|
+
<p className="text-xs text-muted-foreground">{labels.stepCounter(currentIndex + 1, totalSteps)}</p>
|
|
33
|
+
</CardHeader>
|
|
34
|
+
<CardContent className="space-y-4">
|
|
35
|
+
<div>{step.content}</div>
|
|
36
|
+
<div className="flex justify-between">
|
|
37
|
+
<Button variant="outline" onClick={onPrevious} disabled={isFirst}>
|
|
38
|
+
{labels.previous}
|
|
39
|
+
</Button>
|
|
40
|
+
<Button onClick={isLast ? onClose : onNext}>{isLast ? labels.finish : labels.next}</Button>
|
|
41
|
+
</div>
|
|
42
|
+
</CardContent>
|
|
43
|
+
</Card>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./OnboardingCard";
|