@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
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { createRoot, Root } from "react-dom/client";
|
|
5
|
+
import Shepherd from "shepherd.js";
|
|
6
|
+
import "shepherd.js/dist/css/shepherd.css";
|
|
7
|
+
import { OnboardingCard } from "../components/OnboardingCard";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_ONBOARDING_LABELS,
|
|
10
|
+
OnboardingContextValue,
|
|
11
|
+
OnboardingProviderProps,
|
|
12
|
+
OnboardingStepConfig,
|
|
13
|
+
} from "../interfaces";
|
|
14
|
+
import "../styles/onboarding.css";
|
|
15
|
+
|
|
16
|
+
const OnboardingContext = createContext<OnboardingContextValue | null>(null);
|
|
17
|
+
|
|
18
|
+
export function OnboardingProvider({
|
|
19
|
+
children,
|
|
20
|
+
tours = [],
|
|
21
|
+
tourPaths = {},
|
|
22
|
+
labels = DEFAULT_ONBOARDING_LABELS,
|
|
23
|
+
renderCard,
|
|
24
|
+
zIndex = 9999,
|
|
25
|
+
}: OnboardingProviderProps) {
|
|
26
|
+
const [isTourActive, setIsTourActive] = useState(false);
|
|
27
|
+
const [activeTourId, setActiveTourId] = useState<string | null>(null);
|
|
28
|
+
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
|
29
|
+
const [totalSteps, setTotalSteps] = useState(0);
|
|
30
|
+
|
|
31
|
+
const tourRef = useRef<InstanceType<typeof Shepherd.Tour> | null>(null);
|
|
32
|
+
const rootsRef = useRef<Map<string, Root>>(new Map());
|
|
33
|
+
|
|
34
|
+
const cleanupRoots = useCallback(() => {
|
|
35
|
+
rootsRef.current.forEach((root) => {
|
|
36
|
+
try {
|
|
37
|
+
root.unmount();
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// Root may already be unmounted
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
rootsRef.current.clear();
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const closeTour = useCallback(() => {
|
|
46
|
+
if (tourRef.current) {
|
|
47
|
+
tourRef.current.cancel();
|
|
48
|
+
tourRef.current = null;
|
|
49
|
+
}
|
|
50
|
+
cleanupRoots();
|
|
51
|
+
setIsTourActive(false);
|
|
52
|
+
setActiveTourId(null);
|
|
53
|
+
setCurrentStepIndex(0);
|
|
54
|
+
setTotalSteps(0);
|
|
55
|
+
}, [cleanupRoots]);
|
|
56
|
+
|
|
57
|
+
const nextStep = useCallback(() => {
|
|
58
|
+
if (tourRef.current) {
|
|
59
|
+
tourRef.current.next();
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
const previousStep = useCallback(() => {
|
|
64
|
+
if (tourRef.current) {
|
|
65
|
+
tourRef.current.back();
|
|
66
|
+
}
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
const goToStep = useCallback((index: number) => {
|
|
70
|
+
if (tourRef.current) {
|
|
71
|
+
tourRef.current.show(index);
|
|
72
|
+
}
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
const startTour = useCallback(
|
|
76
|
+
(tourId: string, steps?: OnboardingStepConfig[]) => {
|
|
77
|
+
if (typeof window === "undefined") return;
|
|
78
|
+
|
|
79
|
+
// Close any existing tour
|
|
80
|
+
if (tourRef.current) {
|
|
81
|
+
closeTour();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Get steps from provided steps or find tour by ID
|
|
85
|
+
let tourSteps = steps;
|
|
86
|
+
if (!tourSteps) {
|
|
87
|
+
const tour = tours.find((t) => t.id === tourId);
|
|
88
|
+
tourSteps = tour?.steps;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!tourSteps || tourSteps.length === 0) {
|
|
92
|
+
console.warn(`No steps found for tour: ${tourId}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setTotalSteps(tourSteps.length);
|
|
97
|
+
setActiveTourId(tourId);
|
|
98
|
+
|
|
99
|
+
const tour = new Shepherd.Tour({
|
|
100
|
+
useModalOverlay: true,
|
|
101
|
+
defaultStepOptions: {
|
|
102
|
+
classes: "shepherd-theme-custom",
|
|
103
|
+
scrollTo: { behavior: "smooth", block: "center" },
|
|
104
|
+
cancelIcon: { enabled: false },
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Set up tour event listeners
|
|
109
|
+
tour.on("show", () => setIsTourActive(true));
|
|
110
|
+
tour.on("cancel", () => {
|
|
111
|
+
cleanupRoots();
|
|
112
|
+
setIsTourActive(false);
|
|
113
|
+
setActiveTourId(null);
|
|
114
|
+
setCurrentStepIndex(0);
|
|
115
|
+
setTotalSteps(0);
|
|
116
|
+
tourRef.current = null;
|
|
117
|
+
});
|
|
118
|
+
tour.on("complete", () => {
|
|
119
|
+
cleanupRoots();
|
|
120
|
+
setIsTourActive(false);
|
|
121
|
+
setActiveTourId(null);
|
|
122
|
+
setCurrentStepIndex(0);
|
|
123
|
+
setTotalSteps(0);
|
|
124
|
+
tourRef.current = null;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
tourSteps.forEach((stepConfig, index) => {
|
|
128
|
+
const stepId = stepConfig.id || `step-${index}`;
|
|
129
|
+
|
|
130
|
+
tour.addStep({
|
|
131
|
+
id: stepId,
|
|
132
|
+
attachTo: stepConfig.selector ? { element: stepConfig.selector, on: stepConfig.side || "bottom" } : undefined,
|
|
133
|
+
arrow: true,
|
|
134
|
+
// Use text callback to return DOM element with React content
|
|
135
|
+
text: () => {
|
|
136
|
+
const container = document.createElement("div");
|
|
137
|
+
const root = createRoot(container);
|
|
138
|
+
rootsRef.current.set(stepId, root);
|
|
139
|
+
|
|
140
|
+
const cardProps = {
|
|
141
|
+
step: stepConfig,
|
|
142
|
+
currentIndex: index,
|
|
143
|
+
totalSteps: tourSteps!.length,
|
|
144
|
+
labels,
|
|
145
|
+
onNext: () => tour.next(),
|
|
146
|
+
onPrevious: () => tour.back(),
|
|
147
|
+
onClose: () => tour.cancel(),
|
|
148
|
+
onSkip: () => tour.cancel(),
|
|
149
|
+
isFirst: index === 0,
|
|
150
|
+
isLast: index === tourSteps!.length - 1,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
root.render(renderCard ? renderCard(cardProps) : <OnboardingCard {...cardProps} />);
|
|
154
|
+
|
|
155
|
+
return container;
|
|
156
|
+
},
|
|
157
|
+
buttons: [], // Empty - our card handles navigation
|
|
158
|
+
beforeShowPromise: stepConfig.showDelay
|
|
159
|
+
? () => new Promise((resolve) => setTimeout(resolve, stepConfig.showDelay))
|
|
160
|
+
: undefined,
|
|
161
|
+
canClickTarget: stepConfig.canClickTarget ?? false,
|
|
162
|
+
modalOverlayOpeningPadding: 8,
|
|
163
|
+
modalOverlayOpeningRadius: 8,
|
|
164
|
+
when: {
|
|
165
|
+
show: () => {
|
|
166
|
+
setCurrentStepIndex(index);
|
|
167
|
+
stepConfig.onShow?.();
|
|
168
|
+
},
|
|
169
|
+
hide: () => {
|
|
170
|
+
stepConfig.onHide?.();
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
tourRef.current = tour;
|
|
177
|
+
tour.start();
|
|
178
|
+
},
|
|
179
|
+
[tours, labels, renderCard, closeTour, cleanupRoots],
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Cleanup on unmount
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
return () => {
|
|
185
|
+
if (tourRef.current) {
|
|
186
|
+
tourRef.current.cancel();
|
|
187
|
+
}
|
|
188
|
+
cleanupRoots();
|
|
189
|
+
};
|
|
190
|
+
}, [cleanupRoots]);
|
|
191
|
+
|
|
192
|
+
const value: OnboardingContextValue = {
|
|
193
|
+
startTour,
|
|
194
|
+
closeTour,
|
|
195
|
+
nextStep,
|
|
196
|
+
previousStep,
|
|
197
|
+
goToStep,
|
|
198
|
+
isTourActive,
|
|
199
|
+
activeTourId,
|
|
200
|
+
currentStepIndex,
|
|
201
|
+
totalSteps,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return <OnboardingContext.Provider value={value}>{children}</OnboardingContext.Provider>;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function useOnboarding(): OnboardingContextValue {
|
|
208
|
+
const context = useContext(OnboardingContext);
|
|
209
|
+
if (!context) {
|
|
210
|
+
throw new Error("useOnboarding must be used within an OnboardingProvider");
|
|
211
|
+
}
|
|
212
|
+
return context;
|
|
213
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./onboarding.interface";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type OnboardingStepSide =
|
|
4
|
+
| "top"
|
|
5
|
+
| "top-start"
|
|
6
|
+
| "top-end"
|
|
7
|
+
| "bottom"
|
|
8
|
+
| "bottom-start"
|
|
9
|
+
| "bottom-end"
|
|
10
|
+
| "left"
|
|
11
|
+
| "left-start"
|
|
12
|
+
| "left-end"
|
|
13
|
+
| "right"
|
|
14
|
+
| "right-start"
|
|
15
|
+
| "right-end";
|
|
16
|
+
|
|
17
|
+
export interface OnboardingStepConfig {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
content: ReactNode;
|
|
21
|
+
selector?: string;
|
|
22
|
+
side?: OnboardingStepSide;
|
|
23
|
+
onShow?: () => void;
|
|
24
|
+
onHide?: () => void;
|
|
25
|
+
action?: { label: string; onClick: () => void };
|
|
26
|
+
showDelay?: number;
|
|
27
|
+
canClickTarget?: boolean;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface OnboardingTourConfig {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
steps: OnboardingStepConfig[];
|
|
36
|
+
routes?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface OnboardingTourPaths {
|
|
40
|
+
[routePattern: string]: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface OnboardingLabels {
|
|
44
|
+
next: string;
|
|
45
|
+
previous: string;
|
|
46
|
+
finish: string;
|
|
47
|
+
skip: string;
|
|
48
|
+
close: string;
|
|
49
|
+
stepCounter: (current: number, total: number) => string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const DEFAULT_ONBOARDING_LABELS: OnboardingLabels = {
|
|
53
|
+
next: "Next",
|
|
54
|
+
previous: "Previous",
|
|
55
|
+
finish: "Finish",
|
|
56
|
+
skip: "Skip",
|
|
57
|
+
close: "Close",
|
|
58
|
+
stepCounter: (current, total) => `${current} of ${total}`,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export interface OnboardingContextValue {
|
|
62
|
+
startTour: (tourId: string, steps?: OnboardingStepConfig[]) => void;
|
|
63
|
+
closeTour: () => void;
|
|
64
|
+
nextStep: () => void;
|
|
65
|
+
previousStep: () => void;
|
|
66
|
+
goToStep: (index: number) => void;
|
|
67
|
+
isTourActive: boolean;
|
|
68
|
+
activeTourId: string | null;
|
|
69
|
+
currentStepIndex: number;
|
|
70
|
+
totalSteps: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface OnboardingProviderProps {
|
|
74
|
+
children: React.ReactNode;
|
|
75
|
+
tours?: OnboardingTourConfig[];
|
|
76
|
+
tourPaths?: OnboardingTourPaths;
|
|
77
|
+
labels?: OnboardingLabels;
|
|
78
|
+
renderCard?: (props: OnboardingCardRenderProps) => ReactNode;
|
|
79
|
+
zIndex?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface OnboardingCardRenderProps {
|
|
83
|
+
step: OnboardingStepConfig;
|
|
84
|
+
currentIndex: number;
|
|
85
|
+
totalSteps: number;
|
|
86
|
+
labels: OnboardingLabels;
|
|
87
|
+
onNext: () => void;
|
|
88
|
+
onPrevious: () => void;
|
|
89
|
+
onClose: () => void;
|
|
90
|
+
onSkip: () => void;
|
|
91
|
+
isFirst: boolean;
|
|
92
|
+
isLast: boolean;
|
|
93
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Shepherd Modal Overlay */
|
|
2
|
+
.shepherd-modal-overlay-container.shepherd-modal-is-visible {
|
|
3
|
+
opacity: 1 !important;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.shepherd-modal-overlay-container path {
|
|
7
|
+
fill: rgba(0, 0, 0, 0.75) !important;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* Remove default shepherd styling - we use custom React card */
|
|
11
|
+
.shepherd-element {
|
|
12
|
+
background: transparent !important;
|
|
13
|
+
border: none !important;
|
|
14
|
+
box-shadow: none !important;
|
|
15
|
+
padding: 0 !important;
|
|
16
|
+
max-width: none !important;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.shepherd-content,
|
|
20
|
+
.shepherd-text {
|
|
21
|
+
padding: 0 !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Arrow styling to match card background */
|
|
25
|
+
.shepherd-arrow::before {
|
|
26
|
+
background: hsl(var(--card)) !important;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Hide default Shepherd header and footer - we render custom ones */
|
|
30
|
+
.shepherd-header,
|
|
31
|
+
.shepherd-footer {
|
|
32
|
+
display: none !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Dark mode support */
|
|
36
|
+
.dark .shepherd-arrow::before {
|
|
37
|
+
background: hsl(var(--card)) !important;
|
|
38
|
+
}
|
|
@@ -33,7 +33,7 @@ export interface CurrentUserContextType<T extends UserInterface = UserInterface>
|
|
|
33
33
|
hasAccesToFeature: (featureIdentifier: string) => boolean;
|
|
34
34
|
matchUrlToModule: (prarms?: { path: string }) => ModuleWithPermissions | undefined;
|
|
35
35
|
hasRole: (roleId: string) => boolean;
|
|
36
|
-
refreshUser: () => Promise<void>;
|
|
36
|
+
refreshUser: (options?: { skipCookieUpdate?: boolean }) => Promise<void>;
|
|
37
37
|
isRefreshing: boolean;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -136,27 +136,36 @@ export const CurrentUserProvider = ({ children }: { children: React.ReactNode })
|
|
|
136
136
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
137
137
|
|
|
138
138
|
// Function to refresh user data from the API
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
// skipCookieUpdate: When true, only updates React state without calling the Server Action
|
|
140
|
+
// This prevents page reloads when refresh is triggered by WebSocket events
|
|
141
|
+
const refreshUser = useCallback(async (options?: { skipCookieUpdate?: boolean }): Promise<void> => {
|
|
142
|
+
if (isRefreshing) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
141
145
|
|
|
142
146
|
setIsRefreshing(true);
|
|
143
147
|
try {
|
|
144
148
|
const fullUser = await UserService.findFullUser();
|
|
145
149
|
if (fullUser) {
|
|
146
|
-
|
|
150
|
+
const dehydrated = fullUser.dehydrate();
|
|
151
|
+
|
|
152
|
+
setDehydratedUser(dehydrated as any);
|
|
147
153
|
setUser(fullUser);
|
|
148
154
|
|
|
149
155
|
// Update authentication cookies with fresh user data
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
// Skip when triggered by WebSocket to prevent page reload (Server Actions modify cookies)
|
|
157
|
+
if (!options?.skipCookieUpdate) {
|
|
158
|
+
await getTokenHandler()?.updateToken({
|
|
159
|
+
userId: fullUser.id,
|
|
160
|
+
companyId: fullUser.company?.id,
|
|
161
|
+
roles: fullUser.roles.map((role) => role.id),
|
|
162
|
+
features: fullUser.company?.features?.map((feature) => feature.id) ?? [],
|
|
163
|
+
modules: fullUser.modules.map((module) => ({
|
|
164
|
+
id: module.id,
|
|
165
|
+
permissions: module.permissions,
|
|
166
|
+
})),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
160
169
|
}
|
|
161
170
|
} catch (error) {
|
|
162
171
|
console.error("Failed to refresh user data:", error);
|
|
@@ -175,23 +184,29 @@ export const CurrentUserProvider = ({ children }: { children: React.ReactNode })
|
|
|
175
184
|
// Track refresh in progress to prevent duplicate API calls
|
|
176
185
|
const isRefreshingRef = useRef(false);
|
|
177
186
|
|
|
178
|
-
// Listen for company
|
|
187
|
+
// Listen for company WebSocket events that trigger user refresh
|
|
179
188
|
useEffect(() => {
|
|
180
|
-
if (!socket || !isConnected || !currentUser?.company?.id)
|
|
189
|
+
if (!socket || !isConnected || !currentUser?.company?.id) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
181
192
|
|
|
182
|
-
const
|
|
193
|
+
const handleCompanyUpdate = (data: { companyId: string; type: string }) => {
|
|
183
194
|
if (data.companyId === currentUser.company?.id && !isRefreshingRef.current) {
|
|
184
195
|
isRefreshingRef.current = true;
|
|
185
|
-
|
|
196
|
+
// Skip cookie update to prevent page reload - only update React state
|
|
197
|
+
refreshUserRef.current({ skipCookieUpdate: true }).finally(() => {
|
|
186
198
|
isRefreshingRef.current = false;
|
|
187
199
|
});
|
|
188
200
|
}
|
|
189
201
|
};
|
|
190
202
|
|
|
191
|
-
|
|
203
|
+
// Both events trigger the same refresh behavior
|
|
204
|
+
socket.on("company:tokens_updated", handleCompanyUpdate);
|
|
205
|
+
socket.on("company:subscription_updated", handleCompanyUpdate);
|
|
192
206
|
|
|
193
207
|
return () => {
|
|
194
|
-
socket.off("company:tokens_updated",
|
|
208
|
+
socket.off("company:tokens_updated", handleCompanyUpdate);
|
|
209
|
+
socket.off("company:subscription_updated", handleCompanyUpdate);
|
|
195
210
|
};
|
|
196
211
|
}, [socket, isConnected, currentUser?.company?.id]);
|
|
197
212
|
|
package/src/shadcnui/ui/card.tsx
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import * as React from "react"
|
|
1
|
+
import * as React from "react";
|
|
2
2
|
|
|
3
|
-
import { cn } from "@/lib/utils"
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
4
|
|
|
5
|
-
function Card({
|
|
6
|
-
className,
|
|
7
|
-
size = "default",
|
|
8
|
-
...props
|
|
9
|
-
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
5
|
+
function Card({ className, size = "default", ...props }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
10
6
|
return (
|
|
11
7
|
<div
|
|
12
8
|
data-slot="card"
|
|
13
9
|
data-size={size}
|
|
14
|
-
className={cn(
|
|
10
|
+
className={cn(
|
|
11
|
+
"ring-foreground/10 bg-card text-card-foreground gap-4 overflow-clip rounded-lg py-4 text-xs/relaxed ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
15
14
|
{...props}
|
|
16
15
|
/>
|
|
17
|
-
)
|
|
16
|
+
);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
@@ -23,72 +22,48 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
23
22
|
data-slot="card-header"
|
|
24
23
|
className={cn(
|
|
25
24
|
"gap-1 rounded-t-lg px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
|
|
26
|
-
className
|
|
25
|
+
className,
|
|
27
26
|
)}
|
|
28
27
|
{...props}
|
|
29
28
|
/>
|
|
30
|
-
)
|
|
29
|
+
);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
34
|
-
return (
|
|
35
|
-
<div
|
|
36
|
-
data-slot="card-title"
|
|
37
|
-
className={cn("text-sm font-medium", className)}
|
|
38
|
-
{...props}
|
|
39
|
-
/>
|
|
40
|
-
)
|
|
33
|
+
return <div data-slot="card-title" className={cn("text-sm font-medium", className)} {...props} />;
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
44
37
|
return (
|
|
45
|
-
<div
|
|
46
|
-
|
|
47
|
-
className={cn("text-muted-foreground text-xs/relaxed", className)}
|
|
48
|
-
{...props}
|
|
49
|
-
/>
|
|
50
|
-
)
|
|
38
|
+
<div data-slot="card-description" className={cn("text-muted-foreground text-xs/relaxed", className)} {...props} />
|
|
39
|
+
);
|
|
51
40
|
}
|
|
52
41
|
|
|
53
42
|
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
54
43
|
return (
|
|
55
44
|
<div
|
|
56
45
|
data-slot="card-action"
|
|
57
|
-
className={cn(
|
|
58
|
-
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
59
|
-
className
|
|
60
|
-
)}
|
|
46
|
+
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
|
61
47
|
{...props}
|
|
62
48
|
/>
|
|
63
|
-
)
|
|
49
|
+
);
|
|
64
50
|
}
|
|
65
51
|
|
|
66
52
|
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
67
|
-
return (
|
|
68
|
-
<div
|
|
69
|
-
data-slot="card-content"
|
|
70
|
-
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
|
71
|
-
{...props}
|
|
72
|
-
/>
|
|
73
|
-
)
|
|
53
|
+
return <div data-slot="card-content" className={cn("px-4 group-data-[size=sm]/card:px-3", className)} {...props} />;
|
|
74
54
|
}
|
|
75
55
|
|
|
76
56
|
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
77
57
|
return (
|
|
78
58
|
<div
|
|
79
59
|
data-slot="card-footer"
|
|
80
|
-
className={cn(
|
|
60
|
+
className={cn(
|
|
61
|
+
"rounded-b-lg px-4 group-data-[size=sm]/card:px-3 [.border-t]:pt-4 group-data-[size=sm]/card:[.border-t]:pt-3 flex items-center",
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
81
64
|
{...props}
|
|
82
65
|
/>
|
|
83
|
-
)
|
|
66
|
+
);
|
|
84
67
|
}
|
|
85
68
|
|
|
86
|
-
export {
|
|
87
|
-
Card,
|
|
88
|
-
CardHeader,
|
|
89
|
-
CardFooter,
|
|
90
|
-
CardTitle,
|
|
91
|
-
CardAction,
|
|
92
|
-
CardDescription,
|
|
93
|
-
CardContent,
|
|
94
|
-
}
|
|
69
|
+
export { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
|