strapi-plugin-magic-sessionmanager 4.0.1 → 4.0.2

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.
Files changed (57) hide show
  1. package/README.md +2 -2
  2. package/admin/src/components/OnlineUsersWidget.jsx +11 -7
  3. package/admin/src/components/SessionDetailModal.jsx +42 -38
  4. package/admin/src/components/SessionInfoPanel.jsx +29 -20
  5. package/admin/src/index.js +9 -0
  6. package/admin/src/pages/HomePage.jsx +128 -161
  7. package/admin/src/pages/License.jsx +3 -3
  8. package/admin/src/pages/Settings.jsx +139 -135
  9. package/admin/src/pages/UpgradePage.jsx +448 -0
  10. package/admin/src/pluginId.js +1 -0
  11. package/admin/src/translations/de.json +294 -15
  12. package/admin/src/translations/en.json +293 -14
  13. package/admin/src/translations/es.json +284 -18
  14. package/admin/src/translations/fr.json +284 -18
  15. package/admin/src/translations/pt.json +284 -18
  16. package/admin/src/utils/theme.js +85 -0
  17. package/dist/_chunks/{Analytics-Dv9f_0eZ.mjs → Analytics-DTE_zmRV.mjs} +2 -2
  18. package/dist/_chunks/{Analytics-BBdv1I5y.js → Analytics-lw_JaOVy.js} +2 -2
  19. package/dist/_chunks/{App-CJaZPNjt.js → App-DDKYCjKw.js} +216 -206
  20. package/dist/_chunks/{App-CIQ-7sa7.mjs → App-DJW1ZNl5.mjs} +216 -206
  21. package/dist/_chunks/{License-nrmFxoBm.mjs → License-DaOFuImm.mjs} +4 -8
  22. package/dist/_chunks/{License-D24rgaZQ.js → License-Tk-6UfPl.js} +4 -8
  23. package/dist/_chunks/{OnlineUsersWidget-B8JS1xZu.js → OnlineUsersWidget-C1qTpsws.js} +11 -7
  24. package/dist/_chunks/{OnlineUsersWidget-ArMl0nen.mjs → OnlineUsersWidget-CADphbXG.mjs} +11 -7
  25. package/dist/_chunks/{Settings-D5dLEGc_.mjs → Settings-C9xvckgq.mjs} +191 -179
  26. package/dist/_chunks/{Settings-CqxgjU0y.js → Settings-DyEAuTNQ.js} +191 -179
  27. package/dist/_chunks/UpgradePage-Dssk8A0Z.js +354 -0
  28. package/dist/_chunks/UpgradePage-cINvE9zY.mjs +352 -0
  29. package/dist/_chunks/de-CDA1V0rF.mjs +292 -0
  30. package/dist/_chunks/de-I-Q-pWqu.js +292 -0
  31. package/dist/_chunks/en-Bd7_h-4e.js +292 -0
  32. package/dist/_chunks/en-DzmOCyzQ.mjs +292 -0
  33. package/dist/_chunks/es-BcAx18XG.js +277 -0
  34. package/dist/_chunks/es-Cx-SN6qV.mjs +277 -0
  35. package/dist/_chunks/fr-DCzYMuJ-.js +277 -0
  36. package/dist/_chunks/fr-DXlXE5Eo.mjs +277 -0
  37. package/dist/_chunks/{index-WH04CS1c.js → index-CWcvrfXc.js} +45 -42
  38. package/dist/_chunks/{index-Duk1_Wrz.mjs → index-DQO9bNP7.mjs} +45 -42
  39. package/dist/_chunks/pt-21-MAb72.js +277 -0
  40. package/dist/_chunks/pt-zsdTSjba.mjs +277 -0
  41. package/dist/_chunks/{useLicense-BwOlCyhc.js → useLicense-DtvJOszr.js} +1 -1
  42. package/dist/_chunks/{useLicense-Ce8GaxB0.mjs → useLicense-DxbD4Wf8.mjs} +1 -1
  43. package/dist/admin/index.js +1 -1
  44. package/dist/admin/index.mjs +1 -1
  45. package/dist/server/index.js +1 -1
  46. package/dist/server/index.mjs +1 -1
  47. package/package.json +1 -1
  48. package/dist/_chunks/de-BxFx1pwE.js +0 -23
  49. package/dist/_chunks/de-CdO3s01z.mjs +0 -23
  50. package/dist/_chunks/en-CsPpPJL3.mjs +0 -23
  51. package/dist/_chunks/en-RqmpDHdS.js +0 -23
  52. package/dist/_chunks/es-CuLHazN1.js +0 -23
  53. package/dist/_chunks/es-Dkmjhy9c.mjs +0 -23
  54. package/dist/_chunks/fr-BAJp2yhI.js +0 -23
  55. package/dist/_chunks/fr-Bssg_3UF.mjs +0 -23
  56. package/dist/_chunks/pt-BAP9cKs3.js +0 -23
  57. package/dist/_chunks/pt-BVNoNcuY.mjs +0 -23
package/README.md CHANGED
@@ -136,7 +136,7 @@ npm run develop
136
136
  3. Click it!
137
137
  4. You'll see the dashboard (like the screenshot above)
138
138
 
139
- **That's it! You're done!** 🎉
139
+ **That's it! You're done!**
140
140
 
141
141
  ---
142
142
 
@@ -621,4 +621,4 @@ Language automatically follows your Strapi admin interface setting.
621
621
 
622
622
  ---
623
623
 
624
- **Made with ❤️ for Strapi v5**
624
+ **Made for Strapi v5**
@@ -1,14 +1,18 @@
1
1
  import { useState, useEffect, useCallback } from 'react';
2
+ import { useIntl } from 'react-intl';
2
3
  import { Box, Typography, Flex, Grid } from '@strapi/design-system';
3
4
  import { Check, Cross, Clock, User, Monitor } from '@strapi/icons';
4
5
  import { useFetchClient } from '@strapi/strapi/admin';
6
+ import { getTranslation } from '../utils/getTranslation';
5
7
 
6
8
  /**
7
9
  * Online Users Widget - Dashboard widget showing user activity statistics
8
10
  * Styled exactly like Project Statistics
9
11
  */
10
12
  const OnlineUsersWidget = () => {
13
+ const { formatMessage } = useIntl();
11
14
  const { get } = useFetchClient();
15
+ const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
12
16
  const [stats, setStats] = useState({
13
17
  onlineNow: 0,
14
18
  offline: 0,
@@ -137,7 +141,7 @@ const OnlineUsersWidget = () => {
137
141
  if (loading) {
138
142
  return (
139
143
  <Box padding={4}>
140
- <Typography variant="pi" textColor="neutral600">Loading...</Typography>
144
+ <Typography variant="pi" textColor="neutral600">{t('common.loading', 'Loading...')}</Typography>
141
145
  </Box>
142
146
  );
143
147
  }
@@ -149,7 +153,7 @@ const OnlineUsersWidget = () => {
149
153
  <Grid.Item col={1}>
150
154
  <StatCard
151
155
  icon={Check}
152
- label="Online Now"
156
+ label={t('widget.stats.onlineNow', 'Online Now')}
153
157
  value={stats.onlineNow}
154
158
  color="success"
155
159
  />
@@ -158,7 +162,7 @@ const OnlineUsersWidget = () => {
158
162
  <Grid.Item col={1}>
159
163
  <StatCard
160
164
  icon={Cross}
161
- label="Offline"
165
+ label={t('widget.stats.offline', 'Offline')}
162
166
  value={stats.offline}
163
167
  color="neutral"
164
168
  />
@@ -167,7 +171,7 @@ const OnlineUsersWidget = () => {
167
171
  <Grid.Item col={1}>
168
172
  <StatCard
169
173
  icon={Clock}
170
- label="Last 15 min"
174
+ label={t('widget.stats.last15min', 'Last 15 min')}
171
175
  value={stats.last15min}
172
176
  color="primary"
173
177
  />
@@ -176,7 +180,7 @@ const OnlineUsersWidget = () => {
176
180
  <Grid.Item col={1}>
177
181
  <StatCard
178
182
  icon={Clock}
179
- label="Last 30 min"
183
+ label={t('widget.stats.last30min', 'Last 30 min')}
180
184
  value={stats.last30min}
181
185
  color="secondary"
182
186
  />
@@ -185,7 +189,7 @@ const OnlineUsersWidget = () => {
185
189
  <Grid.Item col={1}>
186
190
  <StatCard
187
191
  icon={User}
188
- label="Total Users"
192
+ label={t('widget.stats.totalUsers', 'Total Users')}
189
193
  value={stats.totalUsers}
190
194
  color="neutral"
191
195
  />
@@ -194,7 +198,7 @@ const OnlineUsersWidget = () => {
194
198
  <Grid.Item col={1}>
195
199
  <StatCard
196
200
  icon={Cross}
197
- label="Blocked"
201
+ label={t('widget.stats.blocked', 'Blocked')}
198
202
  value={stats.blocked}
199
203
  color="danger"
200
204
  />
@@ -1,4 +1,5 @@
1
1
  import { useState, useEffect } from 'react';
2
+ import { useIntl } from 'react-intl';
2
3
  import styled from 'styled-components';
3
4
  import {
4
5
  Modal,
@@ -25,6 +26,7 @@ import { useFetchClient, useNotification } from '@strapi/strapi/admin';
25
26
  import parseUserAgent from '../utils/parseUserAgent';
26
27
  import pluginId from '../pluginId';
27
28
  import { useLicense } from '../hooks/useLicense';
29
+ import { getTranslation } from '../utils/getTranslation';
28
30
 
29
31
  const TwoColumnGrid = styled.div`
30
32
  display: grid;
@@ -53,6 +55,7 @@ const Section = styled(Box)`
53
55
  `;
54
56
 
55
57
  const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
58
+ const { formatMessage } = useIntl();
56
59
  const { get, post } = useFetchClient();
57
60
  const { toggleNotification } = useNotification();
58
61
  const { isPremium, loading: licenseLoading } = useLicense();
@@ -60,6 +63,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
60
63
  const [showUserAgent, setShowUserAgent] = useState(false);
61
64
  const [geoData, setGeoData] = useState(null);
62
65
  const [geoLoading, setGeoLoading] = useState(false);
66
+ const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
63
67
 
64
68
  if (!session) return null;
65
69
 
@@ -117,7 +121,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
117
121
  const DeviceIcon = getDeviceIcon(deviceInfo.device);
118
122
 
119
123
  const handleTerminate = async () => {
120
- if (!confirm('Are you sure you want to terminate this session?')) {
124
+ if (!confirm(t('modal.confirm.terminate', 'Are you sure you want to terminate this session?'))) {
121
125
  return;
122
126
  }
123
127
 
@@ -127,7 +131,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
127
131
 
128
132
  toggleNotification({
129
133
  type: 'success',
130
- message: 'Session terminated successfully',
134
+ message: t('notifications.success.terminated', 'Session terminated successfully'),
131
135
  });
132
136
 
133
137
  onSessionTerminated();
@@ -136,7 +140,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
136
140
  console.error('[SessionDetailModal] Error:', err);
137
141
  toggleNotification({
138
142
  type: 'danger',
139
- message: 'Failed to terminate session',
143
+ message: t('notifications.error.terminate', 'Failed to terminate session'),
140
144
  });
141
145
  } finally {
142
146
  setTerminating(false);
@@ -188,10 +192,10 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
188
192
  </Box>
189
193
  <Flex direction="column" alignItems="flex-start">
190
194
  <Typography variant="beta" fontWeight="bold">
191
- Session Details
195
+ {t('modal.title', 'Session Details')}
192
196
  </Typography>
193
197
  <Typography variant="pi" textColor="neutral600">
194
- ID: {session.id}
198
+ {t('modal.id', 'ID: {id}', { id: session.id })}
195
199
  </Typography>
196
200
  </Flex>
197
201
  </Flex>
@@ -207,7 +211,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
207
211
  size="M"
208
212
  style={{ fontSize: '14px', padding: '8px 20px', fontWeight: '600' }}
209
213
  >
210
- {isOnline ? 'ONLINE' : 'OFFLINE'}
214
+ {isOnline ? t('modal.status.online', 'ONLINE') : t('modal.status.offline', 'OFFLINE')}
211
215
  </Badge>
212
216
  </Flex>
213
217
 
@@ -220,24 +224,24 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
220
224
  {/* User Information */}
221
225
  <Section>
222
226
  <SectionTitle>
223
- User
227
+ {t('modal.section.user', 'User')}
224
228
  </SectionTitle>
225
229
 
226
- <DetailRow compact icon={Check} label="Username" value={session.user?.username || 'N/A'} />
227
- <DetailRow compact icon={Information} label="Email" value={session.user?.email || 'N/A'} />
228
- <DetailRow compact icon={Information} label="User ID" value={session.user?.id || 'N/A'} />
230
+ <DetailRow compact icon={Check} label={t('modal.user.username', 'Username')} value={session.user?.username || t('modal.user.na', 'N/A')} />
231
+ <DetailRow compact icon={Information} label={t('modal.user.email', 'Email')} value={session.user?.email || t('modal.user.na', 'N/A')} />
232
+ <DetailRow compact icon={Information} label={t('modal.user.id', 'User ID')} value={session.user?.id || t('modal.user.na', 'N/A')} />
229
233
  </Section>
230
234
 
231
235
  {/* Device Information */}
232
236
  <Section>
233
237
  <SectionTitle>
234
- Device
238
+ {t('modal.section.device', 'Device')}
235
239
  </SectionTitle>
236
240
 
237
- <DetailRow compact icon={DeviceIcon} label="Device" value={deviceInfo.device} />
238
- <DetailRow compact icon={Monitor} label="Browser" value={`${deviceInfo.browser} ${deviceInfo.browserVersion || ''}`} />
239
- <DetailRow compact icon={Server} label="OS" value={deviceInfo.os} />
240
- <DetailRow compact icon={Information} label="IP" value={session.ipAddress} />
241
+ <DetailRow compact icon={DeviceIcon} label={t('modal.device.device', 'Device')} value={deviceInfo.device} />
242
+ <DetailRow compact icon={Monitor} label={t('modal.device.browser', 'Browser')} value={`${deviceInfo.browser} ${deviceInfo.browserVersion || ''}`} />
243
+ <DetailRow compact icon={Server} label={t('modal.device.os', 'OS')} value={deviceInfo.os} />
244
+ <DetailRow compact icon={Information} label={t('modal.device.ip', 'IP')} value={session.ipAddress} />
241
245
  </Section>
242
246
  </Box>
243
247
 
@@ -245,13 +249,13 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
245
249
  <Box>
246
250
  <Section>
247
251
  <SectionTitle>
248
- Timeline
252
+ {t('modal.section.timeline', 'Timeline')}
249
253
  </SectionTitle>
250
254
 
251
255
  <DetailRow
252
256
  compact
253
257
  icon={Clock}
254
- label="Login"
258
+ label={t('modal.timeline.login', 'Login')}
255
259
  value={new Date(session.loginTime).toLocaleString('de-DE', {
256
260
  day: '2-digit',
257
261
  month: 'short',
@@ -262,7 +266,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
262
266
  <DetailRow
263
267
  compact
264
268
  icon={Clock}
265
- label="Last Active"
269
+ label={t('modal.timeline.lastActive', 'Last Active')}
266
270
  value={new Date(session.lastActive || session.loginTime).toLocaleString('de-DE', {
267
271
  day: '2-digit',
268
272
  month: 'short',
@@ -273,14 +277,14 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
273
277
  <DetailRow
274
278
  compact
275
279
  icon={Clock}
276
- label="Idle Time"
277
- value={`${session.minutesSinceActive} min`}
280
+ label={t('modal.timeline.idleTime', 'Idle Time')}
281
+ value={t('modal.timeline.minutes', '{minutes} min', { minutes: session.minutesSinceActive })}
278
282
  />
279
283
  {session.logoutTime && (
280
284
  <DetailRow
281
285
  compact
282
286
  icon={Cross}
283
- label="Logout"
287
+ label={t('modal.timeline.logout', 'Logout')}
284
288
  value={new Date(session.logoutTime).toLocaleString('de-DE', {
285
289
  day: '2-digit',
286
290
  month: 'short',
@@ -297,13 +301,13 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
297
301
  {isPremium ? (
298
302
  <Section>
299
303
  <SectionTitle>
300
- Location and Security
304
+ {t('modal.section.security', 'Location and Security')}
301
305
  </SectionTitle>
302
306
 
303
307
  {geoLoading ? (
304
308
  <Box padding={4} style={{ textAlign: 'center' }}>
305
309
  <Typography variant="pi" textColor="neutral600">
306
- Loading location data...
310
+ {t('modal.security.loading', 'Loading location data...')}
307
311
  </Typography>
308
312
  </Box>
309
313
  ) : (
@@ -312,30 +316,30 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
312
316
  <DetailRow
313
317
  compact
314
318
  icon={Earth}
315
- label="Country"
319
+ label={t('modal.security.country', 'Country')}
316
320
  value={`${premiumData.country_flag || ''} ${premiumData.country}`.trim()}
317
321
  />
318
- <DetailRow compact icon={Earth} label="City" value={premiumData.city} />
319
- <DetailRow compact icon={Clock} label="Timezone" value={premiumData.timezone} />
322
+ <DetailRow compact icon={Earth} label={t('modal.security.city', 'City')} value={premiumData.city} />
323
+ <DetailRow compact icon={Clock} label={t('modal.security.timezone', 'Timezone')} value={premiumData.timezone} />
320
324
  </Box>
321
325
  <Box>
322
326
  <DetailRow
323
327
  compact
324
328
  icon={Shield}
325
- label="Security"
329
+ label={t('modal.security.score', 'Security')}
326
330
  value={`${premiumData.securityScore}/100 (${premiumData.riskLevel})`}
327
331
  />
328
332
  <DetailRow
329
333
  compact
330
334
  icon={Shield}
331
- label="VPN"
332
- value={premiumData.isVpn ? '[WARNING] Yes' : 'No'}
335
+ label={t('modal.security.vpn', 'VPN')}
336
+ value={premiumData.isVpn ? t('modal.security.vpnWarning', '[WARNING] Yes') : t('modal.security.no', 'No')}
333
337
  />
334
338
  <DetailRow
335
339
  compact
336
340
  icon={Shield}
337
- label="Proxy"
338
- value={premiumData.isProxy ? '[WARNING] Yes' : 'No'}
341
+ label={t('modal.security.proxy', 'Proxy')}
342
+ value={premiumData.isProxy ? t('modal.security.vpnWarning', '[WARNING] Yes') : t('modal.security.no', 'No')}
339
343
  />
340
344
  </Box>
341
345
  </TwoColumnGrid>
@@ -355,10 +359,10 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
355
359
  <Flex direction="column" alignItems="center" gap={3}>
356
360
  <Crown style={{ width: '40px', height: '40px', color: '#d97706' }} />
357
361
  <Typography variant="beta" style={{ color: '#92400e', fontWeight: '700' }}>
358
- Location and Security Analysis
362
+ {t('modal.premium.title', 'Location and Security Analysis')}
359
363
  </Typography>
360
364
  <Typography variant="omega" style={{ color: '#78350f', fontSize: '14px', lineHeight: '1.6' }}>
361
- Unlock premium features to get IP geolocation, security scoring, and VPN/Proxy detection for every session
365
+ {t('modal.premium.description', 'Unlock premium features to get IP geolocation, security scoring, and VPN/Proxy detection for every session')}
362
366
  </Typography>
363
367
  <Button
364
368
  variant="secondary"
@@ -373,7 +377,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
373
377
  boxShadow: '0 4px 12px rgba(245, 158, 11, 0.3)',
374
378
  }}
375
379
  >
376
- Upgrade to Premium
380
+ {t('modal.premium.upgrade', 'Upgrade to Premium')}
377
381
  </Button>
378
382
  </Flex>
379
383
  </Box>
@@ -384,7 +388,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
384
388
  <Section>
385
389
  <Flex justifyContent="space-between" alignItems="center" style={{ marginBottom: '12px' }}>
386
390
  <SectionTitle style={{ marginBottom: 0, paddingBottom: 0, border: 'none' }}>
387
- Technical Details
391
+ {t('modal.section.technical', 'Technical Details')}
388
392
  </SectionTitle>
389
393
  <Button
390
394
  variant="tertiary"
@@ -392,7 +396,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
392
396
  onClick={() => setShowUserAgent(!showUserAgent)}
393
397
  style={{ fontSize: '12px' }}
394
398
  >
395
- {showUserAgent ? ' Hide Details' : ' Show Details'}
399
+ {showUserAgent ? `▲ ${t('modal.technical.hide', 'Hide Details')}` : `▼ ${t('modal.technical.show', 'Show Details')}`}
396
400
  </Button>
397
401
  </Flex>
398
402
 
@@ -423,7 +427,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
423
427
  <Modal.Footer>
424
428
  <Flex justifyContent="space-between" style={{ width: '100%' }}>
425
429
  <Button onClick={onClose} variant="tertiary">
426
- Close
430
+ {t('modal.actions.close', 'Close')}
427
431
  </Button>
428
432
  <Button
429
433
  onClick={handleTerminate}
@@ -432,7 +436,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
432
436
  loading={terminating}
433
437
  startIcon={<Cross />}
434
438
  >
435
- Terminate Session
439
+ {t('modal.actions.terminate', 'Terminate Session')}
436
440
  </Button>
437
441
  </Flex>
438
442
  </Modal.Footer>
@@ -1,20 +1,24 @@
1
1
  import { useState, useEffect } from 'react';
2
+ import { useIntl } from 'react-intl';
2
3
  import { Box, Typography, Flex, Button, Badge, Divider } from '@strapi/design-system';
3
4
  import { Check, Cross, Monitor, Phone, Server, Clock } from '@strapi/icons';
4
5
  import { useFetchClient, useNotification } from '@strapi/strapi/admin';
5
6
  import parseUserAgent from '../utils/parseUserAgent';
7
+ import { getTranslation } from '../utils/getTranslation';
6
8
 
7
9
  /**
8
10
  * Session Info Panel - Native Strapi design
9
11
  * Clean, professional sidebar panel for Content Manager
10
12
  */
11
13
  const SessionInfoPanel = ({ documentId, model, document }) => {
14
+ const { formatMessage } = useIntl();
12
15
  const [sessions, setSessions] = useState([]);
13
16
  const [loading, setLoading] = useState(true);
14
17
  const [isBlocked, setIsBlocked] = useState(false);
15
18
  const [actionLoading, setActionLoading] = useState(false);
16
19
  const { get, post: postRequest } = useFetchClient();
17
20
  const { toggleNotification } = useNotification();
21
+ const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
18
22
 
19
23
  // Strapi v5: Use documentId (string UUID) instead of numeric id
20
24
  const userId = document?.documentId || documentId;
@@ -53,14 +57,14 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
53
57
  if (response.data?.success) {
54
58
  toggleNotification({
55
59
  type: 'success',
56
- message: 'All sessions terminated successfully',
60
+ message: t('notifications.success.terminatedAll', 'All sessions terminated successfully'),
57
61
  });
58
62
  setSessions([]);
59
63
  }
60
64
  } catch (error) {
61
65
  toggleNotification({
62
66
  type: 'warning',
63
- message: 'Failed to terminate sessions',
67
+ message: t('notifications.error.terminateAll', 'Failed to terminate sessions'),
64
68
  });
65
69
  console.error('[SessionInfoPanel] Logout all error:', error);
66
70
  } finally {
@@ -81,7 +85,9 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
81
85
 
82
86
  toggleNotification({
83
87
  type: 'success',
84
- message: newBlockedStatus ? 'User blocked successfully' : 'User unblocked successfully',
88
+ message: newBlockedStatus
89
+ ? t('notifications.success.blocked', 'User blocked successfully')
90
+ : t('notifications.success.unblocked', 'User unblocked successfully'),
85
91
  });
86
92
 
87
93
  if (newBlockedStatus) {
@@ -91,7 +97,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
91
97
  } catch (error) {
92
98
  toggleNotification({
93
99
  type: 'warning',
94
- message: 'Failed to update user status',
100
+ message: t('notifications.error.block', 'Failed to update user status'),
95
101
  });
96
102
  console.error('[SessionInfoPanel] Toggle block error:', error);
97
103
  } finally {
@@ -112,10 +118,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
112
118
 
113
119
  if (loading) {
114
120
  return {
115
- title: 'Session Info',
121
+ title: t('panel.title', 'Session Info'),
116
122
  content: (
117
123
  <Box padding={4} background="neutral0">
118
- <Typography variant="pi" textColor="neutral600">Loading...</Typography>
124
+ <Typography variant="pi" textColor="neutral600">{t('panel.loading', 'Loading...')}</Typography>
119
125
  </Box>
120
126
  ),
121
127
  };
@@ -124,7 +130,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
124
130
  const isOnline = sessions.length > 0;
125
131
 
126
132
  return {
127
- title: 'Session Info',
133
+ title: t('panel.title', 'Session Info'),
128
134
  content: (
129
135
  <Box style={{ width: '100%' }}>
130
136
  <Flex direction="column" gap={4} alignItems="stretch">
@@ -145,10 +151,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
145
151
  size="M"
146
152
  style={{ fontSize: '14px', padding: '6px 12px' }}
147
153
  >
148
- {isOnline ? 'ACTIVE' : 'OFFLINE'}
154
+ {isOnline ? t('panel.status.active', 'ACTIVE') : t('panel.status.offline', 'OFFLINE')}
149
155
  </Badge>
150
156
  <Typography variant="omega" fontWeight="semiBold" textColor={isOnline ? 'success700' : 'neutral700'}>
151
- {sessions.length} active session{sessions.length !== 1 ? 's' : ''}
157
+ {t('panel.sessions.count', '{count} active session{count, plural, one {} other {s}}', { count: sessions.length })}
152
158
  </Typography>
153
159
  </Flex>
154
160
  </Box>
@@ -161,10 +167,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
161
167
  hasRadius
162
168
  >
163
169
  <Typography variant="omega" fontWeight="semiBold" textColor="danger700" marginBottom={1}>
164
- User is blocked
170
+ {t('panel.blocked.title', 'User is blocked')}
165
171
  </Typography>
166
172
  <Typography variant="pi" textColor="danger600">
167
- Authentication disabled
173
+ {t('panel.blocked.description', 'Authentication disabled')}
168
174
  </Typography>
169
175
  </Box>
170
176
  )}
@@ -177,7 +183,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
177
183
  letterSpacing: '0.5px',
178
184
  fontSize: '12px'
179
185
  }}>
180
- Active Sessions
186
+ {t('panel.sessions.title', 'Active Sessions')}
181
187
  </Typography>
182
188
 
183
189
  {sessions.slice(0, 5).map((session) => {
@@ -219,7 +225,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
219
225
  textColor="neutral0"
220
226
  size="S"
221
227
  >
222
- Active
228
+ {t('panel.sessions.active', 'Active')}
223
229
  </Badge>
224
230
 
225
231
  {/* Browser & OS */}
@@ -253,7 +259,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
253
259
  {/* Show minutes since last activity */}
254
260
  {session.minutesSinceActive !== undefined && session.minutesSinceActive < 60 && (
255
261
  <Typography variant="pi" textColor="success600" fontWeight="semiBold">
256
- Active {session.minutesSinceActive === 0 ? 'now' : `${session.minutesSinceActive} min ago`}
262
+ {session.minutesSinceActive === 0
263
+ ? t('panel.sessions.activeNow', 'Active now')
264
+ : t('panel.sessions.activeAgo', 'Active {minutes} min ago', { minutes: session.minutesSinceActive })
265
+ }
257
266
  </Typography>
258
267
  )}
259
268
  </Flex>
@@ -264,7 +273,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
264
273
  {sessions.length > 5 && (
265
274
  <Box padding={3} background="primary100" hasRadius textAlign="center">
266
275
  <Typography variant="pi" textColor="primary600" fontWeight="semiBold">
267
- +{sessions.length - 5} more session{sessions.length - 5 !== 1 ? 's' : ''}
276
+ {t('panel.sessions.more', '+{count} more session{count, plural, one {} other {s}}', { count: sessions.length - 5 })}
268
277
  </Typography>
269
278
  </Box>
270
279
  )}
@@ -288,10 +297,10 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
288
297
  💤
289
298
  </Typography>
290
299
  <Typography variant="omega" fontWeight="semiBold" textColor="neutral700">
291
- No active sessions
300
+ {t('panel.empty.title', 'No active sessions')}
292
301
  </Typography>
293
302
  <Typography variant="pi" textColor="neutral500" style={{ fontSize: '13px' }}>
294
- User has not logged in yet
303
+ {t('panel.empty.description', 'User has not logged in yet')}
295
304
  </Typography>
296
305
  </Flex>
297
306
  </Box>
@@ -306,7 +315,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
306
315
  letterSpacing: '0.5px',
307
316
  fontSize: '12px'
308
317
  }}>
309
- Actions
318
+ {t('panel.actions.title', 'Actions')}
310
319
  </Typography>
311
320
 
312
321
  <Button
@@ -335,7 +344,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
335
344
  }
336
345
  }}
337
346
  >
338
- Terminate All Sessions
347
+ {t('panel.actions.terminateAll', 'Terminate All Sessions')}
339
348
  </Button>
340
349
 
341
350
  <Button
@@ -364,7 +373,7 @@ const SessionInfoPanel = ({ documentId, model, document }) => {
364
373
  }
365
374
  }}
366
375
  >
367
- {isBlocked ? 'Unblock User' : 'Block User'}
376
+ {isBlocked ? t('panel.actions.unblockUser', 'Unblock User') : t('panel.actions.blockUser', 'Block User')}
368
377
  </Button>
369
378
  </Flex>
370
379
  </Flex>
@@ -34,6 +34,15 @@ export default {
34
34
  to: `/settings/${pluginId}`,
35
35
  },
36
36
  [
37
+ {
38
+ intlLabel: {
39
+ id: `${pluginId}.settings.upgrade`,
40
+ defaultMessage: 'Upgrade',
41
+ },
42
+ id: 'upgrade',
43
+ to: `/settings/${pluginId}/upgrade`,
44
+ Component: () => import('./pages/UpgradePage'),
45
+ },
37
46
  {
38
47
  intlLabel: {
39
48
  id: `${pluginId}.settings.general`,