@licklist/design 0.78.5-dev.93 → 0.78.5-dev.94
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/v2/components/AvatarUpload/AvatarUpload.d.ts.map +1 -1
- package/dist/v2/components/AvatarUpload/AvatarUpload.js +7 -3
- package/dist/v2/components/IconButton/IconButton.scss.js +1 -1
- package/dist/v2/components/Modal/DeleteModal.d.ts.map +1 -1
- package/dist/v2/components/Modal/DeleteModal.js +10 -4
- package/dist/v2/components/StatusBadge/StatusBadge.d.ts.map +1 -1
- package/dist/v2/components/StatusBadge/StatusBadge.js +17 -13
- package/dist/v2/components/TableControls/TableControls.d.ts.map +1 -1
- package/dist/v2/components/TableControls/TableControls.js +7 -3
- package/dist/v2/components/UserPanel/UserPanel.d.ts.map +1 -1
- package/dist/v2/components/UserPanel/UserPanel.js +18 -14
- package/dist/v2/dashboard-analytics/blog-posts/Blog.d.ts.map +1 -1
- package/dist/v2/dashboard-analytics/blog-posts/Blog.js +6 -2
- package/dist/v2/dashboard-analytics/chart/Chart.scss.js +1 -1
- package/dist/v2/dashboard-analytics/dashboard/Dashboard.d.ts.map +1 -1
- package/dist/v2/dashboard-analytics/dashboard/Dashboard.js +24 -16
- package/dist/v2/icons/index.d.ts +4 -0
- package/dist/v2/icons/index.d.ts.map +1 -1
- package/dist/v2/navigation/DashboardLayout/DashboardFooter.d.ts.map +1 -1
- package/dist/v2/navigation/DashboardLayout/DashboardFooter.js +9 -6
- package/dist/v2/navigation/SidebarUserElement/SidebarUserElement.d.ts.map +1 -1
- package/dist/v2/pages/RoleSelection/RoleSelectionPage.d.ts.map +1 -1
- package/dist/v2/pages/Settings/components/SidebarCustomisation.js +1 -0
- package/dist/v2/pages/Settings/components/SidebarNavItem.js +1 -0
- package/dist/v2/pages/UserDetails/UserDetailsPage.d.ts.map +1 -1
- package/dist/v2/pages/auth/AuthLayout/AuthLayout.d.ts.map +1 -1
- package/dist/v2/pages/auth/AuthLayout/AuthLayout.js +6 -2
- package/dist/v2/shadcn/_reference/auth/CreatePasswordPanel.d.ts.map +1 -1
- package/dist/v2/shadcn/_reference/auth/LoginForm.d.ts.map +1 -1
- package/dist/v2/shadcn/_reference/auth/LoginPanel.d.ts.map +1 -1
- package/dist/v2/shadcn/_reference/auth/ResetPasswordForm.d.ts.map +1 -1
- package/dist/v2/shadcn/_reference/auth/ResetPasswordPanel.d.ts.map +1 -1
- package/dist/v2/shadcn/_reference/auth/VerifyEmailForm.d.ts.map +1 -1
- package/dist/v2/shadcn/_reference/auth/VerifyEmailPanel.d.ts.map +1 -1
- package/dist/v2/shadcn/_reference/email/EmailAttachment.d.ts.map +1 -1
- package/dist/v2/shadcn/_reference/shared/ConfirmationDialog.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/v2/components/AvatarUpload/AvatarUpload.tsx +5 -3
- package/src/v2/components/Customer/CustomersList.scss +8 -9
- package/src/v2/components/IconButton/IconButton.scss +9 -9
- package/src/v2/components/Modal/DeleteModal.tsx +5 -3
- package/src/v2/components/StatusBadge/StatusBadge.tsx +23 -18
- package/src/v2/components/TableControls/TableControls.tsx +5 -3
- package/src/v2/components/UserPanel/UserPanel.tsx +9 -5
- package/src/v2/dashboard-analytics/blog-posts/Blog.tsx +6 -4
- package/src/v2/dashboard-analytics/chart/Chart.scss +2 -2
- package/src/v2/dashboard-analytics/dashboard/Dashboard.tsx +19 -16
- package/src/v2/icons/index.tsx +15 -0
- package/src/v2/navigation/DashboardLayout/DashboardFooter.tsx +4 -2
- package/src/v2/navigation/SidebarUserElement/SidebarUserElement.tsx +5 -3
- package/src/v2/pages/RoleSelection/RoleSelectionPage.tsx +10 -8
- package/src/v2/pages/UserDetails/UserDetailsPage.tsx +16 -14
- package/src/v2/pages/auth/AuthLayout/AuthLayout.tsx +4 -2
- package/src/v2/shadcn/_reference/auth/CreatePasswordForm.tsx +9 -9
- package/src/v2/shadcn/_reference/auth/CreatePasswordPanel.tsx +5 -3
- package/src/v2/shadcn/_reference/auth/LoginForm.tsx +21 -17
- package/src/v2/shadcn/_reference/auth/LoginPanel.tsx +6 -4
- package/src/v2/shadcn/_reference/auth/ResetPasswordForm.tsx +14 -12
- package/src/v2/shadcn/_reference/auth/ResetPasswordPanel.tsx +5 -3
- package/src/v2/shadcn/_reference/auth/VerifyEmailForm.tsx +8 -6
- package/src/v2/shadcn/_reference/auth/VerifyEmailPanel.tsx +5 -3
- package/src/v2/shadcn/_reference/email/EmailAttachment.tsx +5 -3
- package/src/v2/shadcn/_reference/shared/ConfirmationDialog.tsx +13 -11
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
2
3
|
import { Chart, ChartProps } from '../chart';
|
|
3
4
|
import { MetricCard } from '../metric-card';
|
|
4
5
|
import { Select } from '../../components/Select/Select';
|
|
@@ -74,6 +75,8 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
|
|
74
75
|
isLoading,
|
|
75
76
|
isRefreshing = false,
|
|
76
77
|
}) => {
|
|
78
|
+
const { t } = useTranslation(['Design']);
|
|
79
|
+
|
|
77
80
|
const handleFilterChange = React.useCallback((filter: 'Today' | 'Yesterday' | 'This Week') => {
|
|
78
81
|
onFilterChange?.(filter);
|
|
79
82
|
}, [onFilterChange]);
|
|
@@ -82,10 +85,10 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
|
|
82
85
|
|
|
83
86
|
const getTimePeriodText = (filter: 'Today' | 'Yesterday' | 'This Week'): string => {
|
|
84
87
|
switch (filter) {
|
|
85
|
-
case 'Today': return '
|
|
86
|
-
case 'Yesterday': return '
|
|
87
|
-
case 'This Week': return '
|
|
88
|
-
default: return '
|
|
88
|
+
case 'Today': return t('dashboardToday');
|
|
89
|
+
case 'Yesterday': return t('dashboardYesterday');
|
|
90
|
+
case 'This Week': return t('dashboardThisWeek');
|
|
91
|
+
default: return t('dashboardToday');
|
|
89
92
|
}
|
|
90
93
|
};
|
|
91
94
|
|
|
@@ -115,8 +118,8 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
|
|
115
118
|
onBookingTypeChange?.(newBookingType);
|
|
116
119
|
}}
|
|
117
120
|
>
|
|
118
|
-
<option value="Bookings For">
|
|
119
|
-
<option value="Bookings Made">
|
|
121
|
+
<option value="Bookings For">{t('bookingsFor')}</option>
|
|
122
|
+
<option value="Bookings Made">{t('bookingsMade')}</option>
|
|
120
123
|
</Select>
|
|
121
124
|
|
|
122
125
|
<div className="dashboard__filter-container">
|
|
@@ -130,9 +133,9 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
|
|
130
133
|
}}
|
|
131
134
|
className={showRefreshing ? 'select--refreshing' : ''}
|
|
132
135
|
>
|
|
133
|
-
<option value="Today">
|
|
134
|
-
<option value="Yesterday">
|
|
135
|
-
<option value="This Week">
|
|
136
|
+
<option value="Today">{t('dashboardTodayOption')}</option>
|
|
137
|
+
<option value="Yesterday">{t('dashboardYesterdayOption')}</option>
|
|
138
|
+
<option value="This Week">{t('dashboardThisWeekOption')}</option>
|
|
136
139
|
</Select>
|
|
137
140
|
|
|
138
141
|
{showRefreshing && (
|
|
@@ -146,7 +149,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
|
|
146
149
|
<div className="dashboard__metrics-grid">
|
|
147
150
|
<MetricCard
|
|
148
151
|
icon="https://api.builder.io/api/v1/image/assets/TEMP/a9080a040516a2aee8e202c1fecbbf08f0f4f7b0?placeholderIfAbsent=true"
|
|
149
|
-
title={metrics.totalBookings.label ||
|
|
152
|
+
title={metrics.totalBookings.label || t('totalBookings')}
|
|
150
153
|
value={metrics.totalBookings.value}
|
|
151
154
|
performance={{
|
|
152
155
|
icon: getTrendIcon(metrics.totalBookings.change, metrics.totalBookings.is_positive),
|
|
@@ -159,7 +162,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
|
|
159
162
|
|
|
160
163
|
<MetricCard
|
|
161
164
|
icon="https://api.builder.io/api/v1/image/assets/TEMP/d2cc0fb13dfe9a16e4258917cb2c331c1f0e1189?placeholderIfAbsent=true"
|
|
162
|
-
title={metrics.revenue.label ||
|
|
165
|
+
title={metrics.revenue.label || t('revenue')}
|
|
163
166
|
value={metrics.revenue.value}
|
|
164
167
|
performance={{
|
|
165
168
|
icon: getTrendIcon(metrics.revenue.change, metrics.revenue.is_positive),
|
|
@@ -172,8 +175,8 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
|
|
172
175
|
|
|
173
176
|
<MetricCard
|
|
174
177
|
icon="https://api.builder.io/api/v1/image/assets/TEMP/8c83d255f42836d5497be545d10a8ad9295aa968?placeholderIfAbsent=true"
|
|
175
|
-
title={metrics.numberOfPeople.label ||
|
|
176
|
-
value={metrics.numberOfPeople.formatted_value ||
|
|
178
|
+
title={metrics.numberOfPeople.label || t('peopleBookedIn', { period: getTimePeriodText(timeFilter) })}
|
|
179
|
+
value={metrics.numberOfPeople.formatted_value || t('peopleSuffix', { count: metrics.numberOfPeople.value })}
|
|
177
180
|
performance={{
|
|
178
181
|
icon: getTrendIcon(metrics.numberOfPeople.change, metrics.numberOfPeople.is_positive),
|
|
179
182
|
text: metrics.numberOfPeople.change,
|
|
@@ -185,7 +188,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
|
|
185
188
|
|
|
186
189
|
<MetricCard
|
|
187
190
|
icon="https://api.builder.io/api/v1/image/assets/TEMP/265ca1f3a6e063263764400beb86070a49b952cb?placeholderIfAbsent=true"
|
|
188
|
-
title={metrics.busiestTime.label || (timeFilter === 'This Week' ? '
|
|
191
|
+
title={metrics.busiestTime.label || (timeFilter === 'This Week' ? t('busiestDay') : t('busiestTime'))}
|
|
189
192
|
value={metrics.busiestTime.value}
|
|
190
193
|
performance={{
|
|
191
194
|
text: `${metrics.busiestTime.peopleText} // ${metrics.busiestTime.bookingText}`,
|
|
@@ -221,12 +224,12 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
|
|
221
224
|
<div className="dashboard__venue-section">
|
|
222
225
|
<VenueCard
|
|
223
226
|
icon='https://api.builder.io/api/v1/image/assets/TEMP/b44b83b1f30b882d65f942ec883e90ae34fdb2d0?placeholderIfAbsent=true'
|
|
224
|
-
title='
|
|
227
|
+
title={t('inVenue')}
|
|
225
228
|
value={metrics.peopleInVenue.formatted_value || '0'}
|
|
226
229
|
/>
|
|
227
230
|
<VenueCard
|
|
228
231
|
icon={<VenueIcon />}
|
|
229
|
-
title=
|
|
232
|
+
title={t('openUntil')}
|
|
230
233
|
value={metrics.workHours?.message?.replace('Open until ', '') || ''}
|
|
231
234
|
shadowColor='#121E52'
|
|
232
235
|
/>
|
package/src/v2/icons/index.tsx
CHANGED
|
@@ -770,6 +770,21 @@ export const SettingsPageDashboardIcon = ({ size = 24, className = '' }: { size?
|
|
|
770
770
|
</svg>
|
|
771
771
|
)
|
|
772
772
|
|
|
773
|
+
export const BookedItMarkIcon = ({ size = 24, className = '' }: { size?: number; className?: string } = {}) => (
|
|
774
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
|
|
775
|
+
<defs>
|
|
776
|
+
<linearGradient id="bookedit-mark-gradient" x1="2" y1="16" x2="20" y2="8" gradientUnits="userSpaceOnUse">
|
|
777
|
+
<stop stopColor="#7C3AED" />
|
|
778
|
+
<stop offset="0.5" stopColor="#6366F1" />
|
|
779
|
+
<stop offset="1" stopColor="#3B82F6" />
|
|
780
|
+
</linearGradient>
|
|
781
|
+
</defs>
|
|
782
|
+
<path d="M6.85 4.27C8.08 3.02 8.65 2.72 9.32 2.51C9.99 2.3 10.67 2.3 11.34 2.51C12.01 2.72 12.58 3.02 13.81 4.27L15.87 6.35C17.1 7.6 17.43 8.18 17.64 8.87C17.85 9.56 17.85 10.26 17.64 10.95C17.43 11.64 17.1 12.22 15.87 13.47L13.81 15.55C12.58 16.8 12.01 17.1 11.34 17.31C10.67 17.52 9.99 17.52 9.32 17.31C8.65 17.1 8.08 16.8 6.85 15.55L4.79 13.47C3.56 12.22 3.23 11.64 3.02 10.95C2.81 10.26 2.81 9.56 3.02 8.87C3.23 8.18 3.56 7.6 4.79 6.35L6.85 4.27Z" fill="url(#bookedit-mark-gradient)" />
|
|
783
|
+
<path d="M12.5 8.5C12.91 8.08 13.57 8.08 13.98 8.5C14.39 8.92 14.39 9.59 13.98 10.01L10.76 13.28C10.35 13.7 9.69 13.7 9.28 13.28L8.81 12.81C8.72 12.72 8.7 12.68 8.68 12.63C8.67 12.57 8.67 12.52 8.68 12.46C8.7 12.41 8.72 12.37 8.81 12.28L12.5 8.5Z" fill="white" />
|
|
784
|
+
<path d="M7.62 10.17C7.72 10.06 7.86 10 8 10H9.89C9.99 10 10.08 10.09 10.08 10.19C10.08 10.24 10.06 10.28 10.03 10.31L9.93 10.41L8.73 11.64C8.62 11.75 8.44 11.75 8.33 11.64L7.62 10.93C7.42 10.72 7.42 10.38 7.62 10.17Z" fill="white" />
|
|
785
|
+
</svg>
|
|
786
|
+
)
|
|
787
|
+
|
|
773
788
|
export const SettingsPageWaiversIcon = ({ size = 24, className = '', ...props }: React.SVGProps<SVGSVGElement> & { size?: number; className?: string } = {}) => (
|
|
774
789
|
<svg width={size} height={size} viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" className={className} {...props}>
|
|
775
790
|
<path d="M29.3657 27.8354C31.4117 26.4405 34.0772 25.7503 37.729 26.1685L38.4731 26.2661L38.6235 26.2974C39.3599 26.4857 39.8491 27.2053 39.7339 27.9731C39.6184 28.741 38.9395 29.2849 38.1802 29.2485L38.0278 29.2339L37.395 29.1489C34.308 28.7933 32.394 29.4019 31.0562 30.314C29.5595 31.3345 28.5809 32.8586 27.5396 34.6099C26.5775 36.2279 25.4604 38.2594 23.6812 39.4409C21.8382 40.6643 19.5533 40.8605 16.5776 39.8687L15.9731 39.6548L15.8325 39.5933C15.1516 39.2557 14.8228 38.4502 15.0952 37.7231C15.3681 36.996 16.1461 36.6045 16.8813 36.7983L17.0278 36.8452L17.5229 37.021C19.9234 37.8245 21.1853 37.498 22.0229 36.9419C23.1024 36.2249 23.8614 34.9277 24.9614 33.0776C25.9824 31.3606 27.2536 29.2757 29.3657 27.8354ZM22.0132 6.82178C22.3878 6.08288 23.2909 5.78703 24.0298 6.16162L30.855 9.62256L30.9878 9.69775C31.6279 10.1082 31.8654 10.9454 31.5142 11.6382L21.6284 31.1382C21.5083 31.375 21.327 31.5762 21.104 31.7202L14.7261 35.8394C14.2781 36.1287 13.7106 36.1585 13.2349 35.9175C12.759 35.6762 12.4459 35.2001 12.4146 34.6675L11.9673 27.0884C11.9517 26.8234 12.0075 26.5586 12.1274 26.3218L22.0132 6.82178ZM14.9878 27.3149L15.2583 31.9224L19.1372 29.4185L28.1606 11.6196L24.0112 9.51611L14.9878 27.3149Z" fill="currentColor"/>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
2
3
|
import './DashboardFooter.scss'
|
|
3
4
|
|
|
4
5
|
export interface DashboardFooterProps {
|
|
@@ -8,16 +9,17 @@ export interface DashboardFooterProps {
|
|
|
8
9
|
export const DashboardFooter: React.FC<DashboardFooterProps> = ({
|
|
9
10
|
version
|
|
10
11
|
}) => {
|
|
12
|
+
const { t } = useTranslation(['Design'])
|
|
11
13
|
const currentYear = new Date().getFullYear()
|
|
12
14
|
|
|
13
15
|
return (
|
|
14
16
|
<footer className="dashboard-footer">
|
|
15
17
|
<div className="dashboard-footer__content">
|
|
16
18
|
<div className="dashboard-footer__version">
|
|
17
|
-
|
|
19
|
+
{t('versionLabel')}<span>{version}</span>
|
|
18
20
|
</div>
|
|
19
21
|
<div className="dashboard-footer__copyright">
|
|
20
|
-
|
|
22
|
+
{t('copyright', { year: currentYear })}
|
|
21
23
|
</div>
|
|
22
24
|
</div>
|
|
23
25
|
</footer>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
2
3
|
import './SidebarUserElement.scss'
|
|
3
4
|
|
|
4
5
|
// Icon assets for dropdown menu items
|
|
@@ -47,6 +48,7 @@ export const SidebarUserElement: React.FC<SidebarUserElementProps> = ({
|
|
|
47
48
|
onUserClick,
|
|
48
49
|
className,
|
|
49
50
|
}) => {
|
|
51
|
+
const { t } = useTranslation(['Design'])
|
|
50
52
|
const userInfo = (
|
|
51
53
|
<div className="sidebar-user-element__user-info">
|
|
52
54
|
<div className="sidebar-user-element__avatar" data-property-1="No User">
|
|
@@ -70,7 +72,7 @@ export const SidebarUserElement: React.FC<SidebarUserElementProps> = ({
|
|
|
70
72
|
<div className="sidebar-user-element__dropdown-icon" data-icon="Admin">
|
|
71
73
|
<AdminIcon />
|
|
72
74
|
</div>
|
|
73
|
-
<div className="sidebar-user-element__dropdown-text">
|
|
75
|
+
<div className="sidebar-user-element__dropdown-text">{t('siteAdmin')}</div>
|
|
74
76
|
</div>
|
|
75
77
|
</div>
|
|
76
78
|
|
|
@@ -82,7 +84,7 @@ export const SidebarUserElement: React.FC<SidebarUserElementProps> = ({
|
|
|
82
84
|
<div className="sidebar-user-element__dropdown-icon" data-icon="User Settings">
|
|
83
85
|
<ProfileIcon />
|
|
84
86
|
</div>
|
|
85
|
-
<div className="sidebar-user-element__dropdown-text">
|
|
87
|
+
<div className="sidebar-user-element__dropdown-text">{t('profile')}</div>
|
|
86
88
|
</div>
|
|
87
89
|
</div>
|
|
88
90
|
|
|
@@ -94,7 +96,7 @@ export const SidebarUserElement: React.FC<SidebarUserElementProps> = ({
|
|
|
94
96
|
<div className="sidebar-user-element__dropdown-icon" data-icon="Log out">
|
|
95
97
|
<LogoutIcon />
|
|
96
98
|
</div>
|
|
97
|
-
<div className="sidebar-user-element__dropdown-text">
|
|
99
|
+
<div className="sidebar-user-element__dropdown-text">{t('logOut')}</div>
|
|
98
100
|
</div>
|
|
99
101
|
</div>
|
|
100
102
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
2
3
|
import { Button } from '../../components/Button'
|
|
3
4
|
import { ArrowLeftIcon } from '../../icons'
|
|
4
5
|
import './RoleSelectionPage.scss'
|
|
@@ -32,17 +33,18 @@ export interface RoleSelectionPageProps {
|
|
|
32
33
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
33
34
|
|
|
34
35
|
export const RoleSelectionPage: React.FC<RoleSelectionPageProps> = ({
|
|
35
|
-
pageTitle
|
|
36
|
+
pageTitle,
|
|
36
37
|
userName,
|
|
37
38
|
roles,
|
|
38
39
|
defaultRole,
|
|
39
|
-
submitLabel
|
|
40
|
+
submitLabel,
|
|
40
41
|
onSubmit,
|
|
41
42
|
onBack,
|
|
42
43
|
onCancel,
|
|
43
44
|
isLoading = false,
|
|
44
45
|
error,
|
|
45
46
|
}) => {
|
|
47
|
+
const { t } = useTranslation(['Design'])
|
|
46
48
|
const [selectedRole, setSelectedRole] = useState<string>(
|
|
47
49
|
defaultRole ?? roles[0]?.value ?? ''
|
|
48
50
|
)
|
|
@@ -51,10 +53,10 @@ export const RoleSelectionPage: React.FC<RoleSelectionPageProps> = ({
|
|
|
51
53
|
<div className="role-selection-page">
|
|
52
54
|
{/* ── Header ── */}
|
|
53
55
|
<header className="role-selection-page__header">
|
|
54
|
-
<h1 className="role-selection-page__title">{pageTitle}</h1>
|
|
56
|
+
<h1 className="role-selection-page__title">{pageTitle ?? t('addUser')}</h1>
|
|
55
57
|
{onCancel && (
|
|
56
58
|
<Button variant="secondary-soft" onClick={onCancel}>
|
|
57
|
-
|
|
59
|
+
{t('cancel')}
|
|
58
60
|
</Button>
|
|
59
61
|
)}
|
|
60
62
|
</header>
|
|
@@ -64,7 +66,7 @@ export const RoleSelectionPage: React.FC<RoleSelectionPageProps> = ({
|
|
|
64
66
|
{onBack && (
|
|
65
67
|
<button className="role-selection-page__back" type="button" onClick={onBack}>
|
|
66
68
|
<ArrowLeftIcon />
|
|
67
|
-
|
|
69
|
+
{t('back')}
|
|
68
70
|
</button>
|
|
69
71
|
)}
|
|
70
72
|
|
|
@@ -76,10 +78,10 @@ export const RoleSelectionPage: React.FC<RoleSelectionPageProps> = ({
|
|
|
76
78
|
|
|
77
79
|
<div className="role-selection-page__step-header">
|
|
78
80
|
<h2 className="role-selection-page__step-title">
|
|
79
|
-
|
|
81
|
+
{t('whatSortOfUser')} {userName ? <strong>{userName}</strong> : t('thisUser')}?
|
|
80
82
|
</h2>
|
|
81
83
|
<p className="role-selection-page__step-desc">
|
|
82
|
-
|
|
84
|
+
{t('permissionsCustomisableAfterCreation')}
|
|
83
85
|
</p>
|
|
84
86
|
</div>
|
|
85
87
|
|
|
@@ -119,7 +121,7 @@ export const RoleSelectionPage: React.FC<RoleSelectionPageProps> = ({
|
|
|
119
121
|
isLoading={isLoading}
|
|
120
122
|
disabled={isLoading || !selectedRole}
|
|
121
123
|
>
|
|
122
|
-
{submitLabel}
|
|
124
|
+
{submitLabel ?? t('sendInvitation')}
|
|
123
125
|
</Button>
|
|
124
126
|
</div>
|
|
125
127
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
2
3
|
import { Button } from '../../components/Button'
|
|
3
4
|
import { FormField } from '../../components/FormField'
|
|
4
5
|
import { UserAvatar } from '../../components/UserAvatar'
|
|
@@ -57,7 +58,7 @@ function getInitials(fullName: string): string {
|
|
|
57
58
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
58
59
|
|
|
59
60
|
export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
60
|
-
pageTitle
|
|
61
|
+
pageTitle,
|
|
61
62
|
existingUser,
|
|
62
63
|
defaultFirstName = '',
|
|
63
64
|
defaultLastName = '',
|
|
@@ -67,6 +68,7 @@ export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
|
67
68
|
isLoading = false,
|
|
68
69
|
error,
|
|
69
70
|
}) => {
|
|
71
|
+
const { t } = useTranslation(['Design'])
|
|
70
72
|
const [firstName, setFirstName] = useState(defaultFirstName)
|
|
71
73
|
const [lastName, setLastName] = useState(defaultLastName)
|
|
72
74
|
const [fieldErrors, setFieldErrors] = useState<{ firstName?: string; lastName?: string }>({})
|
|
@@ -79,8 +81,8 @@ export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
const errs: { firstName?: string; lastName?: string } = {}
|
|
82
|
-
if (!firstName.trim()) errs.firstName = '
|
|
83
|
-
if (!lastName.trim()) errs.lastName = '
|
|
84
|
+
if (!firstName.trim()) errs.firstName = t('firstNameRequired')
|
|
85
|
+
if (!lastName.trim()) errs.lastName = t('lastNameRequired')
|
|
84
86
|
|
|
85
87
|
if (Object.keys(errs).length > 0) {
|
|
86
88
|
setFieldErrors(errs)
|
|
@@ -95,10 +97,10 @@ export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
|
95
97
|
<div className="user-details-page">
|
|
96
98
|
{/* ── Header ── */}
|
|
97
99
|
<header className="user-details-page__header">
|
|
98
|
-
<h1 className="user-details-page__title">{pageTitle}</h1>
|
|
100
|
+
<h1 className="user-details-page__title">{pageTitle ?? t('addUser')}</h1>
|
|
99
101
|
{onCancel && (
|
|
100
102
|
<Button variant="secondary-soft" onClick={onCancel}>
|
|
101
|
-
|
|
103
|
+
{t('cancel')}
|
|
102
104
|
</Button>
|
|
103
105
|
)}
|
|
104
106
|
</header>
|
|
@@ -108,7 +110,7 @@ export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
|
108
110
|
{onBack && (
|
|
109
111
|
<button className="user-details-page__back" type="button" onClick={onBack}>
|
|
110
112
|
<ArrowLeftIcon />
|
|
111
|
-
|
|
113
|
+
{t('back')}
|
|
112
114
|
</button>
|
|
113
115
|
)}
|
|
114
116
|
|
|
@@ -122,9 +124,9 @@ export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
|
122
124
|
/* ── Existing user found ── */
|
|
123
125
|
<div className="user-details-page__existing">
|
|
124
126
|
<div className="user-details-page__step-header">
|
|
125
|
-
<h2 className="user-details-page__step-title">
|
|
127
|
+
<h2 className="user-details-page__step-title">{t('existingUserFound')}</h2>
|
|
126
128
|
<p className="user-details-page__step-desc">
|
|
127
|
-
|
|
129
|
+
{t('existingUserFoundDesc')}
|
|
128
130
|
</p>
|
|
129
131
|
</div>
|
|
130
132
|
|
|
@@ -141,7 +143,7 @@ export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
|
141
143
|
|
|
142
144
|
{existingUser.assignments && existingUser.assignments.length > 0 && (
|
|
143
145
|
<div className="user-details-page__assignments">
|
|
144
|
-
<p className="user-details-page__assignments-label">
|
|
146
|
+
<p className="user-details-page__assignments-label">{t('currentAssignments')}</p>
|
|
145
147
|
<ul className="user-details-page__assignment-list">
|
|
146
148
|
{existingUser.assignments.map((a) => (
|
|
147
149
|
<li key={a.id} className="user-details-page__assignment-item">
|
|
@@ -162,19 +164,19 @@ export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
|
162
164
|
)}
|
|
163
165
|
|
|
164
166
|
<Button variant="primary" onClick={handleNext} isLoading={isLoading} disabled={isLoading}>
|
|
165
|
-
|
|
167
|
+
{t('continue')}
|
|
166
168
|
</Button>
|
|
167
169
|
</div>
|
|
168
170
|
) : (
|
|
169
171
|
/* ── New user — name form ── */
|
|
170
172
|
<div className="user-details-page__form">
|
|
171
173
|
<div className="user-details-page__step-header">
|
|
172
|
-
<h2 className="user-details-page__step-title">
|
|
174
|
+
<h2 className="user-details-page__step-title">{t('userDetailsStepTitle')}</h2>
|
|
173
175
|
</div>
|
|
174
176
|
|
|
175
177
|
<div className="user-details-page__fields">
|
|
176
178
|
<FormField
|
|
177
|
-
label=
|
|
179
|
+
label={t('firstName')}
|
|
178
180
|
value={firstName}
|
|
179
181
|
onChange={(e) => {
|
|
180
182
|
setFirstName(e.target.value)
|
|
@@ -186,7 +188,7 @@ export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
|
186
188
|
disabled={isLoading}
|
|
187
189
|
/>
|
|
188
190
|
<FormField
|
|
189
|
-
label=
|
|
191
|
+
label={t('lastName')}
|
|
190
192
|
value={lastName}
|
|
191
193
|
onChange={(e) => {
|
|
192
194
|
setLastName(e.target.value)
|
|
@@ -200,7 +202,7 @@ export const UserDetailsPage: React.FC<UserDetailsPageProps> = ({
|
|
|
200
202
|
</div>
|
|
201
203
|
|
|
202
204
|
<Button variant="primary" onClick={handleNext} isLoading={isLoading} disabled={isLoading}>
|
|
203
|
-
|
|
205
|
+
{t('next')}
|
|
204
206
|
</Button>
|
|
205
207
|
</div>
|
|
206
208
|
)}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
2
3
|
import './AuthLayout.scss'
|
|
3
4
|
import { DashboardFooter } from '../../../navigation/DashboardLayout/DashboardFooter'
|
|
4
5
|
import { AuthBgDecorationIcon, BookedLogo } from '../../../icons'
|
|
@@ -17,6 +18,7 @@ export interface AuthLayoutProps {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export const AuthLayout: React.FC<AuthLayoutProps> = ({ badgeLabel, icon, title, subtitle, children, version, error, successMessage }) => {
|
|
21
|
+
const { t } = useTranslation(['Design'])
|
|
20
22
|
const [dismissed, setDismissed] = useState(false)
|
|
21
23
|
|
|
22
24
|
useEffect(() => {
|
|
@@ -31,10 +33,10 @@ export const AuthLayout: React.FC<AuthLayoutProps> = ({ badgeLabel, icon, title,
|
|
|
31
33
|
<AuthBgDecorationIcon />
|
|
32
34
|
|
|
33
35
|
{showError && (
|
|
34
|
-
<Alert variant="error" title=
|
|
36
|
+
<Alert variant="error" title={t('errorTitle')} message={error} className="auth-layout__alert" onDismiss={() => setDismissed(true)} />
|
|
35
37
|
)}
|
|
36
38
|
{showSuccess && (
|
|
37
|
-
<Alert variant="success" title=
|
|
39
|
+
<Alert variant="success" title={t('successTitle')} message={successMessage} className="auth-layout__alert" onDismiss={() => setDismissed(true)} />
|
|
38
40
|
)}
|
|
39
41
|
|
|
40
42
|
<div className="auth-layout__card">
|
|
@@ -16,7 +16,7 @@ export const CreatePasswordForm: React.FC = () => {
|
|
|
16
16
|
const [showError, setShowError] = useState(false);
|
|
17
17
|
const navigate = useNavigate();
|
|
18
18
|
const { showSuccess, showError: showNotifyError } = useNotify();
|
|
19
|
-
const { t } = useTranslation(['Design', 'Validation']);
|
|
19
|
+
const { t } = useTranslation(['Design', 'User', 'Validation']);
|
|
20
20
|
|
|
21
21
|
// Password strength calculation
|
|
22
22
|
const hasNumber = /\d/.test(password);
|
|
@@ -75,7 +75,7 @@ export const CreatePasswordForm: React.FC = () => {
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
if (error) {
|
|
78
|
-
showNotifyError('
|
|
78
|
+
showNotifyError(t('updateFailed', { ns: 'User' }), error.message);
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -84,7 +84,7 @@ export const CreatePasswordForm: React.FC = () => {
|
|
|
84
84
|
if (currentUser) {
|
|
85
85
|
const { error: profileError } = await supabase
|
|
86
86
|
.from('profiles')
|
|
87
|
-
.update({
|
|
87
|
+
.update({
|
|
88
88
|
force_password_reset: false,
|
|
89
89
|
password_last_changed_at: new Date().toISOString()
|
|
90
90
|
})
|
|
@@ -95,12 +95,12 @@ export const CreatePasswordForm: React.FC = () => {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
showSuccess('
|
|
99
|
-
|
|
98
|
+
showSuccess(t('passwordUpdatedSuccessfully', { ns: 'User' }));
|
|
99
|
+
|
|
100
100
|
// Get user's primary role and assignments to determine dashboard
|
|
101
101
|
const { data: { user } } = await supabase.auth.getUser();
|
|
102
102
|
if (!user) {
|
|
103
|
-
showNotifyError('
|
|
103
|
+
showNotifyError(t('authenticationFailed', { ns: 'User' }));
|
|
104
104
|
navigate('/auth');
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
@@ -110,7 +110,7 @@ export const CreatePasswordForm: React.FC = () => {
|
|
|
110
110
|
|
|
111
111
|
if (roleError) {
|
|
112
112
|
console.error('Error fetching role:', roleError);
|
|
113
|
-
showNotifyError('
|
|
113
|
+
showNotifyError(t('failedToDeterminePermissions', { ns: 'User' }));
|
|
114
114
|
navigate('/auth');
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
@@ -161,10 +161,10 @@ export const CreatePasswordForm: React.FC = () => {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// No assignments found - show error
|
|
164
|
-
showNotifyError('
|
|
164
|
+
showNotifyError(t('noDashboardAccess', { ns: 'User' }));
|
|
165
165
|
navigate('/auth');
|
|
166
166
|
} catch (error) {
|
|
167
|
-
showNotifyError('
|
|
167
|
+
showNotifyError(t('unexpectedError', { ns: 'User' }));
|
|
168
168
|
} finally {
|
|
169
169
|
setIsSubmitting(false);
|
|
170
170
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
2
3
|
import { CreatePasswordForm } from './CreatePasswordForm';
|
|
3
4
|
import { AuthLayout } from './AuthLayout';
|
|
4
5
|
import { IconUser } from '../../../icons';
|
|
5
6
|
export const CreatePasswordPanel: React.FC = () => {
|
|
7
|
+
const { t } = useTranslation(['User']);
|
|
6
8
|
return (
|
|
7
9
|
<AuthLayout
|
|
8
|
-
badgeLabel=
|
|
10
|
+
badgeLabel={t('createNewPassword')}
|
|
9
11
|
icon={
|
|
10
12
|
<IconUser className="w-16 h-16 text-fill-primary" />
|
|
11
13
|
}
|
|
12
|
-
title=
|
|
13
|
-
subtitle=
|
|
14
|
+
title={t('createNewPassword')}
|
|
15
|
+
subtitle={t('createNewPasswordSubtitle')}
|
|
14
16
|
>
|
|
15
17
|
<CreatePasswordForm />
|
|
16
18
|
</AuthLayout>
|
|
@@ -11,18 +11,22 @@ import { useNavigate } from 'react-router-dom';
|
|
|
11
11
|
import { useAuth } from '@/contexts/AuthContext';
|
|
12
12
|
import { supabase } from '@/integrations/supabase/client';
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
email:
|
|
16
|
-
password:
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type LoginFormData = z.infer<typeof loginSchema>;
|
|
14
|
+
type LoginFormData = {
|
|
15
|
+
email: string;
|
|
16
|
+
password: string;
|
|
17
|
+
};
|
|
20
18
|
|
|
21
19
|
export const LoginForm: React.FC = () => {
|
|
22
20
|
const navigate = useNavigate();
|
|
23
21
|
const { signIn } = useAuth();
|
|
24
22
|
const { showSuccess, showError } = useNotify();
|
|
25
|
-
const { t } = useTranslation(['Design']);
|
|
23
|
+
const { t } = useTranslation(['Design', 'User', 'Validation']);
|
|
24
|
+
|
|
25
|
+
const loginSchema = z.object({
|
|
26
|
+
email: z.string().email(t('invalidEmailAddress', { ns: 'Validation' })),
|
|
27
|
+
password: z.string().min(1, t('passwordRequired', { ns: 'Validation' })),
|
|
28
|
+
});
|
|
29
|
+
|
|
26
30
|
const form = useForm<LoginFormData>({
|
|
27
31
|
resolver: zodResolver(loginSchema),
|
|
28
32
|
defaultValues: {
|
|
@@ -37,14 +41,14 @@ export const LoginForm: React.FC = () => {
|
|
|
37
41
|
const { error } = await signIn(data.email, data.password);
|
|
38
42
|
|
|
39
43
|
if (error) {
|
|
40
|
-
showError('
|
|
44
|
+
showError(t('signInFailed', { ns: 'User' }), error.message);
|
|
41
45
|
return;
|
|
42
46
|
}
|
|
43
|
-
|
|
47
|
+
|
|
44
48
|
// Get user's primary role and assignments to determine dashboard
|
|
45
49
|
const { data: { user } } = await supabase.auth.getUser();
|
|
46
50
|
if (!user) {
|
|
47
|
-
showError('
|
|
51
|
+
showError(t('authenticationFailed', { ns: 'User' }));
|
|
48
52
|
return;
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -58,7 +62,7 @@ export const LoginForm: React.FC = () => {
|
|
|
58
62
|
if (profile?.status === 'suspended') {
|
|
59
63
|
// Log them out immediately
|
|
60
64
|
await supabase.auth.signOut();
|
|
61
|
-
showError('
|
|
65
|
+
showError(t('accountSuspendedTitle', { ns: 'User' }), t('accountSuspended', { ns: 'User' }));
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
64
68
|
|
|
@@ -67,7 +71,7 @@ export const LoginForm: React.FC = () => {
|
|
|
67
71
|
|
|
68
72
|
if (roleError) {
|
|
69
73
|
console.error('Error fetching role:', roleError);
|
|
70
|
-
showError('
|
|
74
|
+
showError(t('failedToDeterminePermissions', { ns: 'User' }));
|
|
71
75
|
return;
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -75,7 +79,7 @@ export const LoginForm: React.FC = () => {
|
|
|
75
79
|
|
|
76
80
|
// Super admins and system admins go to admin dashboard
|
|
77
81
|
if (primaryRole === 'super_admin' || primaryRole === 'system_admin') {
|
|
78
|
-
showSuccess('
|
|
82
|
+
showSuccess(t('welcomeBackAdmin', { ns: 'User' }));
|
|
79
83
|
navigate('/admin');
|
|
80
84
|
return;
|
|
81
85
|
}
|
|
@@ -90,7 +94,7 @@ export const LoginForm: React.FC = () => {
|
|
|
90
94
|
|
|
91
95
|
if (companyAssignments && companyAssignments.length > 0) {
|
|
92
96
|
const companyId = companyAssignments[0].company_id;
|
|
93
|
-
showSuccess('
|
|
97
|
+
showSuccess(t('welcomeBack', { ns: 'User' }));
|
|
94
98
|
navigate(`/company/${companyId}`);
|
|
95
99
|
return;
|
|
96
100
|
}
|
|
@@ -115,18 +119,18 @@ export const LoginForm: React.FC = () => {
|
|
|
115
119
|
if (provider) {
|
|
116
120
|
const providerType = provider.type === 'venue' ? 'venue' : 'promoter';
|
|
117
121
|
const friendlyId = provider.venue_id || '01';
|
|
118
|
-
showSuccess('
|
|
122
|
+
showSuccess(t('welcomeBack', { ns: 'User' }));
|
|
119
123
|
navigate(`/${providerType}/${friendlyId}`);
|
|
120
124
|
return;
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
// No assignments found - show error
|
|
125
|
-
showError('
|
|
129
|
+
showError(t('noDashboardAccess', { ns: 'User' }));
|
|
126
130
|
console.error('User has role but no company/provider assignments');
|
|
127
131
|
} catch (error) {
|
|
128
132
|
console.error('Login error:', error);
|
|
129
|
-
showError('
|
|
133
|
+
showError(t('unexpectedError', { ns: 'User' }));
|
|
130
134
|
}
|
|
131
135
|
};
|
|
132
136
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
2
3
|
import { useLocation } from 'react-router-dom';
|
|
3
4
|
import { LoginForm } from './LoginForm';
|
|
4
5
|
import { AuthLayout } from './AuthLayout';
|
|
@@ -8,6 +9,7 @@ import { Alert, AlertDescription } from '../ui/alert';
|
|
|
8
9
|
export const LoginPanel: React.FC = () => {
|
|
9
10
|
const location = useLocation();
|
|
10
11
|
const [showSuspendedAlert, setShowSuspendedAlert] = useState(false);
|
|
12
|
+
const { t } = useTranslation(['User']);
|
|
11
13
|
|
|
12
14
|
useEffect(() => {
|
|
13
15
|
// Check if user was redirected due to suspension
|
|
@@ -19,17 +21,17 @@ export const LoginPanel: React.FC = () => {
|
|
|
19
21
|
|
|
20
22
|
return (
|
|
21
23
|
<AuthLayout
|
|
22
|
-
badgeLabel=
|
|
24
|
+
badgeLabel={t('logIn')}
|
|
23
25
|
icon={
|
|
24
26
|
<IconLogin className="w-16 h-16 text-fill-primary" />
|
|
25
27
|
}
|
|
26
|
-
title=
|
|
27
|
-
subtitle=
|
|
28
|
+
title={t('logIn')}
|
|
29
|
+
subtitle={t('loginSubtitle')}
|
|
28
30
|
>
|
|
29
31
|
{showSuspendedAlert && (
|
|
30
32
|
<Alert className="mb-4 border-status-error bg-status-error/10">
|
|
31
33
|
<AlertDescription className="text-label-primary text-sm">
|
|
32
|
-
|
|
34
|
+
{t('accountSuspended')}
|
|
33
35
|
</AlertDescription>
|
|
34
36
|
</Alert>
|
|
35
37
|
)}
|