@umituz/web-dashboard 2.3.0 → 2.4.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/package.json +1 -1
- package/src/domains/analytics/components/AnalyticsLayout.tsx +100 -64
- package/src/domains/analytics/types/analytics.ts +45 -0
- package/src/domains/auth/components/AuthLayout.tsx +1 -0
- package/src/domains/auth/components/LoginForm.tsx +59 -0
- package/src/domains/auth/components/RegisterForm.tsx +59 -0
- package/src/domains/auth/types/auth.ts +12 -0
- package/src/domains/billing/types/billing.ts +1 -1
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Configurable analytics page layout with KPIs and charts
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { RefreshCw, Download, Calendar } from "lucide-react";
|
|
7
|
+
import { RefreshCw, Download, Calendar, Loader2 } from "lucide-react";
|
|
8
8
|
import { cn } from "@umituz/web-design-system/utils";
|
|
9
9
|
import { Button } from "@umituz/web-design-system/atoms";
|
|
10
10
|
import type { AnalyticsLayoutProps } from "../types/analytics";
|
|
@@ -13,11 +13,16 @@ import { MetricCard } from "./MetricCard";
|
|
|
13
13
|
import { getDateRangePresets } from "../utils/analytics";
|
|
14
14
|
|
|
15
15
|
export const AnalyticsLayout = ({
|
|
16
|
+
config,
|
|
16
17
|
title,
|
|
17
18
|
description,
|
|
19
|
+
metrics,
|
|
20
|
+
loading = false,
|
|
21
|
+
period,
|
|
22
|
+
onPeriodChange,
|
|
18
23
|
showDateRange = true,
|
|
19
|
-
showRefresh
|
|
20
|
-
showExport
|
|
24
|
+
showRefresh,
|
|
25
|
+
showExport,
|
|
21
26
|
kpis,
|
|
22
27
|
charts,
|
|
23
28
|
headerContent,
|
|
@@ -35,38 +40,50 @@ export const AnalyticsLayout = ({
|
|
|
35
40
|
console.log("Refreshing analytics data...");
|
|
36
41
|
};
|
|
37
42
|
|
|
43
|
+
// Use config settings if props not provided
|
|
44
|
+
const _showRefresh = showRefresh ?? config?.showRefresh ?? true;
|
|
45
|
+
const _showExport = showExport ?? config?.showExport ?? true;
|
|
46
|
+
|
|
38
47
|
return (
|
|
39
48
|
<div className="w-full space-y-6">
|
|
40
49
|
{/* Header */}
|
|
41
50
|
<div className="flex items-center justify-between">
|
|
42
51
|
<div>
|
|
43
|
-
{title
|
|
52
|
+
{(title || config?.brandName) && (
|
|
53
|
+
<h1 className="text-3xl font-bold text-foreground">
|
|
54
|
+
{title || `${config?.brandName || ''} Analytics`}
|
|
55
|
+
</h1>
|
|
56
|
+
)}
|
|
44
57
|
{description && <p className="text-muted-foreground mt-1">{description}</p>}
|
|
45
58
|
</div>
|
|
46
59
|
|
|
47
60
|
{/* Actions */}
|
|
48
61
|
<div className="flex items-center gap-3">
|
|
49
|
-
{showDateRange && (
|
|
62
|
+
{showDateRange && onPeriodChange && (
|
|
50
63
|
<div className="flex items-center gap-2 bg-background border border-border rounded-lg px-3 py-2">
|
|
51
64
|
<Calendar className="h-4 w-4 text-muted-foreground" />
|
|
52
|
-
<select
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
<select
|
|
66
|
+
className="bg-transparent text-sm text-foreground outline-none"
|
|
67
|
+
value={period}
|
|
68
|
+
onChange={(e) => onPeriodChange(e.target.value)}
|
|
69
|
+
>
|
|
70
|
+
{config?.availablePeriods?.map((p) => (
|
|
71
|
+
<option key={p} value={p}>
|
|
72
|
+
{p === "7d" ? "7 Days" : p === "30d" ? "30 Days" : p === "90d" ? "90 Days" : p}
|
|
56
73
|
</option>
|
|
57
74
|
))}
|
|
58
75
|
</select>
|
|
59
76
|
</div>
|
|
60
77
|
)}
|
|
61
78
|
|
|
62
|
-
{
|
|
63
|
-
<Button variant="ghost" size="sm" onClick={handleRefresh}>
|
|
64
|
-
<RefreshCw className="h-4 w-4" />
|
|
79
|
+
{_showRefresh && (
|
|
80
|
+
<Button variant="ghost" size="sm" onClick={handleRefresh} disabled={loading}>
|
|
81
|
+
<RefreshCw className={cn("h-4 w-4", loading && "animate-spin")} />
|
|
65
82
|
</Button>
|
|
66
83
|
)}
|
|
67
84
|
|
|
68
|
-
{
|
|
69
|
-
<Button variant="ghost" size="sm" onClick={handleExport}>
|
|
85
|
+
{_showExport && (
|
|
86
|
+
<Button variant="ghost" size="sm" onClick={handleExport} disabled={loading}>
|
|
70
87
|
<Download className="h-4 w-4" />
|
|
71
88
|
Export
|
|
72
89
|
</Button>
|
|
@@ -76,59 +93,78 @@ export const AnalyticsLayout = ({
|
|
|
76
93
|
</div>
|
|
77
94
|
</div>
|
|
78
95
|
|
|
79
|
-
{/*
|
|
80
|
-
{
|
|
81
|
-
<div className="
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
96
|
+
{/* Loading State */}
|
|
97
|
+
{loading && (
|
|
98
|
+
<div className="flex items-center justify-center py-24">
|
|
99
|
+
<Loader2 className="h-12 w-12 animate-spin text-muted-foreground" />
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{/* Metrics / KPI Cards */}
|
|
104
|
+
{!loading && (metrics || kpis) && (
|
|
105
|
+
<div className={cn(
|
|
106
|
+
"grid gap-4",
|
|
107
|
+
(metrics || kpis) && "grid-cols-1 sm:grid-cols-2 lg:grid-cols-5"
|
|
108
|
+
)}>
|
|
109
|
+
{metrics?.map((metric) => (
|
|
110
|
+
<MetricCard key={metric.id} metric={metric} />
|
|
111
|
+
))}
|
|
112
|
+
|
|
113
|
+
{/* Legacy kpis support */}
|
|
114
|
+
{kpis && !metrics && (
|
|
115
|
+
<>
|
|
116
|
+
<MetricCard
|
|
117
|
+
metric={{
|
|
118
|
+
id: "downloads",
|
|
119
|
+
name: "Downloads",
|
|
120
|
+
value: kpis.downloads.current,
|
|
121
|
+
previousValue: kpis.downloads.previous,
|
|
122
|
+
unit: "K",
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
<MetricCard
|
|
126
|
+
metric={{
|
|
127
|
+
id: "engagement",
|
|
128
|
+
name: "Engagement",
|
|
129
|
+
value: kpis.engagement.current,
|
|
130
|
+
previousValue: kpis.engagement.previous,
|
|
131
|
+
unit: "%",
|
|
132
|
+
}}
|
|
133
|
+
/>
|
|
134
|
+
<MetricCard
|
|
135
|
+
metric={{
|
|
136
|
+
id: "users",
|
|
137
|
+
name: "Users",
|
|
138
|
+
value: kpis.users.current,
|
|
139
|
+
previousValue: kpis.users.previous,
|
|
140
|
+
unit: "K",
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
<MetricCard
|
|
144
|
+
metric={{
|
|
145
|
+
id: "revenue",
|
|
146
|
+
name: "Revenue",
|
|
147
|
+
value: kpis.revenue.current,
|
|
148
|
+
previousValue: kpis.revenue.previous,
|
|
149
|
+
unit: "$",
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
<MetricCard
|
|
153
|
+
metric={{
|
|
154
|
+
id: "retention",
|
|
155
|
+
name: "Retention",
|
|
156
|
+
value: kpis.retention.current,
|
|
157
|
+
previousValue: kpis.retention.previous,
|
|
158
|
+
unit: "%",
|
|
159
|
+
}}
|
|
160
|
+
/>
|
|
161
|
+
</>
|
|
162
|
+
)}
|
|
127
163
|
</div>
|
|
128
164
|
)}
|
|
129
165
|
|
|
130
166
|
{/* Charts */}
|
|
131
|
-
{charts && charts.length > 0 && (
|
|
167
|
+
{!loading && charts && charts.length > 0 && (
|
|
132
168
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
133
169
|
{charts.map((chartConfig, index) => (
|
|
134
170
|
<AnalyticsCard
|
|
@@ -142,7 +178,7 @@ export const AnalyticsLayout = ({
|
|
|
142
178
|
)}
|
|
143
179
|
|
|
144
180
|
{/* Custom Content */}
|
|
145
|
-
{children}
|
|
181
|
+
{!loading && children}
|
|
146
182
|
</div>
|
|
147
183
|
);
|
|
148
184
|
};
|
|
@@ -225,10 +225,20 @@ export interface AnalyticsCardProps {
|
|
|
225
225
|
* Analytics layout props
|
|
226
226
|
*/
|
|
227
227
|
export interface AnalyticsLayoutProps {
|
|
228
|
+
/** Analytics configuration */
|
|
229
|
+
config?: AnalyticsConfig;
|
|
228
230
|
/** Page title */
|
|
229
231
|
title?: string;
|
|
230
232
|
/** Page description */
|
|
231
233
|
description?: string;
|
|
234
|
+
/** Metrics data */
|
|
235
|
+
metrics?: Metric[];
|
|
236
|
+
/** Loading state */
|
|
237
|
+
loading?: boolean;
|
|
238
|
+
/** Current period */
|
|
239
|
+
period?: string;
|
|
240
|
+
/** Period change handler */
|
|
241
|
+
onPeriodChange?: (period: string) => void;
|
|
232
242
|
/** Date range selector */
|
|
233
243
|
showDateRange?: boolean;
|
|
234
244
|
/** Refresh button */
|
|
@@ -289,3 +299,38 @@ export interface AnalyticsExportOptions {
|
|
|
289
299
|
/** Filename */
|
|
290
300
|
filename?: string;
|
|
291
301
|
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Analytics configuration
|
|
305
|
+
*/
|
|
306
|
+
export interface AnalyticsConfig {
|
|
307
|
+
/** Brand/application name */
|
|
308
|
+
brandName: string;
|
|
309
|
+
/** Default period selection */
|
|
310
|
+
defaultPeriod?: string;
|
|
311
|
+
/** Available period options */
|
|
312
|
+
availablePeriods?: string[];
|
|
313
|
+
/** Show export button */
|
|
314
|
+
showExport?: boolean;
|
|
315
|
+
/** Show refresh button */
|
|
316
|
+
showRefresh?: boolean;
|
|
317
|
+
/** Enable real-time updates */
|
|
318
|
+
enableRealTime?: boolean;
|
|
319
|
+
/** Metrics configuration */
|
|
320
|
+
metrics?: MetricConfig[];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Metric configuration
|
|
325
|
+
*/
|
|
326
|
+
export interface MetricConfig {
|
|
327
|
+
/** Metric identifier */
|
|
328
|
+
id: string;
|
|
329
|
+
/** Metric name */
|
|
330
|
+
name: string;
|
|
331
|
+
/** Unit type */
|
|
332
|
+
unit?: string;
|
|
333
|
+
/** Icon identifier */
|
|
334
|
+
icon?: string;
|
|
335
|
+
}
|
|
336
|
+
|
|
@@ -18,8 +18,11 @@ export const LoginForm = ({
|
|
|
18
18
|
showRememberMe = true,
|
|
19
19
|
showForgotPassword = true,
|
|
20
20
|
showRegisterLink = true,
|
|
21
|
+
showSocialLogin,
|
|
21
22
|
onLoginSuccess,
|
|
22
23
|
onLoginError,
|
|
24
|
+
onGoogleLogin,
|
|
25
|
+
onAppleLogin,
|
|
23
26
|
}: LoginFormProps) => {
|
|
24
27
|
const navigate = useNavigate();
|
|
25
28
|
const [email, setEmail] = useState(defaultCredentials.email || "");
|
|
@@ -221,6 +224,62 @@ export const LoginForm = ({
|
|
|
221
224
|
</button>
|
|
222
225
|
</p>
|
|
223
226
|
)}
|
|
227
|
+
|
|
228
|
+
{/* Social Login */}
|
|
229
|
+
{showSocialLogin && config.showSocialLogin && config.socialProviders && config.socialProviders.length > 0 && (
|
|
230
|
+
<>
|
|
231
|
+
{/* Divider */}
|
|
232
|
+
<div className="relative">
|
|
233
|
+
<div className="absolute inset-0 flex items-center">
|
|
234
|
+
<div className="w-full border-t border-border" />
|
|
235
|
+
</div>
|
|
236
|
+
<div className="relative flex justify-center text-sm">
|
|
237
|
+
<span className="px-2 bg-background text-muted-foreground">
|
|
238
|
+
Or continue with
|
|
239
|
+
</span>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
{/* Social Buttons */}
|
|
244
|
+
<div className="grid grid-cols-2 gap-3">
|
|
245
|
+
{config.socialProviders.map((provider) => (
|
|
246
|
+
<button
|
|
247
|
+
key={provider.id}
|
|
248
|
+
type="button"
|
|
249
|
+
className={cn(
|
|
250
|
+
"flex items-center justify-center gap-2 px-4 py-3 rounded-lg border",
|
|
251
|
+
"bg-background hover:bg-muted transition-colors",
|
|
252
|
+
"text-sm font-medium text-foreground",
|
|
253
|
+
isLoading && "opacity-50 cursor-not-allowed"
|
|
254
|
+
)}
|
|
255
|
+
onClick={async () => {
|
|
256
|
+
if (provider.id === "google" && onGoogleLogin) {
|
|
257
|
+
await onGoogleLogin();
|
|
258
|
+
} else if (provider.id === "apple" && onAppleLogin) {
|
|
259
|
+
await onAppleLogin();
|
|
260
|
+
}
|
|
261
|
+
}}
|
|
262
|
+
disabled={isLoading}
|
|
263
|
+
>
|
|
264
|
+
{provider.icon === "google" && (
|
|
265
|
+
<svg width="18" height="18" viewBox="0 0 24 24">
|
|
266
|
+
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/>
|
|
267
|
+
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
|
268
|
+
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
|
|
269
|
+
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
|
270
|
+
</svg>
|
|
271
|
+
)}
|
|
272
|
+
{provider.icon === "apple" && (
|
|
273
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" className="text-foreground">
|
|
274
|
+
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8.3-3.12 1.38-5.98 0-8.1-3.12-1.87-3.12-3.28-8.1.3-8.1.8-1.06 0-2.29.44-3.06.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
|
275
|
+
</svg>
|
|
276
|
+
)}
|
|
277
|
+
<span>{provider.name}</span>
|
|
278
|
+
</button>
|
|
279
|
+
))}
|
|
280
|
+
</div>
|
|
281
|
+
</>
|
|
282
|
+
)}
|
|
224
283
|
</form>
|
|
225
284
|
);
|
|
226
285
|
};
|
|
@@ -18,8 +18,11 @@ export const RegisterForm = ({
|
|
|
18
18
|
showTerms = true,
|
|
19
19
|
showLoginLink = true,
|
|
20
20
|
requirePasswordConfirm = true,
|
|
21
|
+
showSocialLogin,
|
|
21
22
|
onRegisterSuccess,
|
|
22
23
|
onRegisterError,
|
|
24
|
+
onGoogleLogin,
|
|
25
|
+
onAppleLogin,
|
|
23
26
|
}: RegisterFormProps) => {
|
|
24
27
|
const navigate = useNavigate();
|
|
25
28
|
const [name, setName] = useState(defaultData.name || "");
|
|
@@ -289,6 +292,62 @@ export const RegisterForm = ({
|
|
|
289
292
|
</button>
|
|
290
293
|
</p>
|
|
291
294
|
)}
|
|
295
|
+
|
|
296
|
+
{/* Social Login */}
|
|
297
|
+
{showSocialLogin && config.showSocialLogin && config.socialProviders && config.socialProviders.length > 0 && (
|
|
298
|
+
<>
|
|
299
|
+
{/* Divider */}
|
|
300
|
+
<div className="relative">
|
|
301
|
+
<div className="absolute inset-0 flex items-center">
|
|
302
|
+
<div className="w-full border-t border-border" />
|
|
303
|
+
</div>
|
|
304
|
+
<div className="relative flex justify-center text-sm">
|
|
305
|
+
<span className="px-2 bg-background text-muted-foreground">
|
|
306
|
+
Or continue with
|
|
307
|
+
</span>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
{/* Social Buttons */}
|
|
312
|
+
<div className="grid grid-cols-2 gap-3">
|
|
313
|
+
{config.socialProviders.map((provider) => (
|
|
314
|
+
<button
|
|
315
|
+
key={provider.id}
|
|
316
|
+
type="button"
|
|
317
|
+
className={cn(
|
|
318
|
+
"flex items-center justify-center gap-2 px-4 py-3 rounded-lg border",
|
|
319
|
+
"bg-background hover:bg-muted transition-colors",
|
|
320
|
+
"text-sm font-medium text-foreground",
|
|
321
|
+
isLoading && "opacity-50 cursor-not-allowed"
|
|
322
|
+
)}
|
|
323
|
+
onClick={async () => {
|
|
324
|
+
if (provider.id === "google" && onGoogleLogin) {
|
|
325
|
+
await onGoogleLogin();
|
|
326
|
+
} else if (provider.id === "apple" && onAppleLogin) {
|
|
327
|
+
await onAppleLogin();
|
|
328
|
+
}
|
|
329
|
+
}}
|
|
330
|
+
disabled={isLoading}
|
|
331
|
+
>
|
|
332
|
+
{provider.icon === "google" && (
|
|
333
|
+
<svg width="18" height="18" viewBox="0 0 24 24">
|
|
334
|
+
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/>
|
|
335
|
+
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
|
336
|
+
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
|
|
337
|
+
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
|
338
|
+
</svg>
|
|
339
|
+
)}
|
|
340
|
+
{provider.icon === "apple" && (
|
|
341
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" className="text-foreground">
|
|
342
|
+
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8.3-3.12 1.38-5.98 0-8.1-3.12-1.87-3.12-3.28-8.1.3-8.1.8-1.06 0-2.29.44-3.06.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
|
343
|
+
</svg>
|
|
344
|
+
)}
|
|
345
|
+
<span>{provider.name}</span>
|
|
346
|
+
</button>
|
|
347
|
+
))}
|
|
348
|
+
</div>
|
|
349
|
+
</>
|
|
350
|
+
)}
|
|
292
351
|
</form>
|
|
293
352
|
);
|
|
294
353
|
};
|
|
@@ -210,10 +210,16 @@ export interface LoginFormProps {
|
|
|
210
210
|
showForgotPassword?: boolean;
|
|
211
211
|
/** Show register link */
|
|
212
212
|
showRegisterLink?: boolean;
|
|
213
|
+
/** Show social login buttons */
|
|
214
|
+
showSocialLogin?: boolean;
|
|
213
215
|
/** On successful login */
|
|
214
216
|
onLoginSuccess?: (user: User) => void | Promise<void>;
|
|
215
217
|
/** On login error */
|
|
216
218
|
onLoginError?: (error: string) => void;
|
|
219
|
+
/** Google login handler */
|
|
220
|
+
onGoogleLogin?: () => void | Promise<void>;
|
|
221
|
+
/** Apple login handler */
|
|
222
|
+
onAppleLogin?: () => void | Promise<void>;
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
/**
|
|
@@ -230,10 +236,16 @@ export interface RegisterFormProps {
|
|
|
230
236
|
showLoginLink?: boolean;
|
|
231
237
|
/** Require password confirmation */
|
|
232
238
|
requirePasswordConfirm?: boolean;
|
|
239
|
+
/** Show social login buttons */
|
|
240
|
+
showSocialLogin?: boolean;
|
|
233
241
|
/** On successful registration */
|
|
234
242
|
onRegisterSuccess?: (user: User) => void | Promise<void>;
|
|
235
243
|
/** On registration error */
|
|
236
244
|
onRegisterError?: (error: string) => void;
|
|
245
|
+
/** Google login handler */
|
|
246
|
+
onGoogleLogin?: () => void | Promise<void>;
|
|
247
|
+
/** Apple login handler */
|
|
248
|
+
onAppleLogin?: () => void | Promise<void>;
|
|
237
249
|
}
|
|
238
250
|
|
|
239
251
|
/**
|
|
@@ -136,7 +136,7 @@ export interface PaymentMethod {
|
|
|
136
136
|
/**
|
|
137
137
|
* Invoice status
|
|
138
138
|
*/
|
|
139
|
-
export type InvoiceStatus = "draft" | "open" "paid" "void" "uncollectible";
|
|
139
|
+
export type InvoiceStatus = "draft" | "open" | "paid" | "void" | "uncollectible";
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
142
|
* Invoice item
|