goobs-frontend 0.8.3 → 0.8.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goobs-frontend",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "A comprehensive React-based UI library built on Material-UI, offering a wide range of customizable components including grids, typography, buttons, cards, forms, navigation, pricing tables, steppers, tooltips, accordions, and more. Designed for building responsive and consistent user interfaces with advanced features like form validation, theming, and code syntax highlighting.",
5
5
  "license": "MIT",
6
6
  "main": "./src/index.ts",
@@ -21,31 +21,32 @@
21
21
  "@emotion/cache": "^11.13.1",
22
22
  "@emotion/react": "^11.13.3",
23
23
  "@emotion/styled": "^11.13.0",
24
- "@mui/icons-material": "^6.1.0",
25
- "@mui/material": "^6.1.0",
26
- "@types/lodash": "^4.17.7",
24
+ "@mui/icons-material": "^6.1.4",
25
+ "@mui/material": "^6.1.4",
26
+ "@types/lodash": "^4.17.12",
27
27
  "highlight.js": "^11.10.0",
28
- "jotai": "^2.9.3",
28
+ "jotai": "^2.10.1",
29
29
  "lodash": "^4.17.21",
30
- "next": "14.2.9",
31
- "react-datepicker": "^7.3.0",
30
+ "next": "14.2.15",
31
+ "otplib": "^12.0.1",
32
+ "react-datepicker": "^7.5.0",
32
33
  "react-qr-code": "^2.0.15"
33
34
  },
34
35
  "devDependencies": {
35
- "@next/eslint-plugin-next": "^14.2.9",
36
- "@types/node": "^22.5.4",
37
- "@types/react": "18.3.4",
38
- "@types/react-dom": "^18.3.0",
39
- "@typescript-eslint/eslint-plugin": "^8.5.0",
40
- "@typescript-eslint/parser": "^8.5.0",
41
- "eslint": "^9.9.1",
42
- "eslint-config-next": "^14.2.9",
36
+ "@next/eslint-plugin-next": "^14.2.15",
37
+ "@types/node": "^22.7.7",
38
+ "@types/react": "18.3.11",
39
+ "@types/react-dom": "^18.3.1",
40
+ "@typescript-eslint/eslint-plugin": "^8.10.0",
41
+ "@typescript-eslint/parser": "^8.10.0",
42
+ "eslint": "^9.13.0",
43
+ "eslint-config-next": "^14.2.15",
43
44
  "eslint-config-prettier": "^9.1.0",
44
45
  "eslint-plugin-prettier": "^5.2.1",
45
46
  "prettier": "^3.3.3",
46
47
  "react": "^18.3.1",
47
48
  "react-dom": "^18.3.1",
48
- "typescript": "^5.5.4"
49
+ "typescript": "^5.6.3"
49
50
  },
50
51
  "files": [
51
52
  "src"
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import React, { useState } from 'react'
2
4
  import { Box, Paper, BoxProps } from '@mui/material'
3
5
  import Typography from '../../../../components/Typography'
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import React, { useState } from 'react'
2
4
  import { Box, Paper, Switch } from '@mui/material'
3
5
  import { CardProps } from '../../index'
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import React, { useRef, useEffect } from 'react'
2
4
  import { Box } from '@mui/material'
3
5
  import CustomButton from '../../components/Button'
@@ -6,7 +6,7 @@ type ExtendedColumnConfig = Omit<columnconfig, 'component'> & {
6
6
  component?: columnconfig['component']
7
7
  }
8
8
 
9
- export interface ExtendedQRCodeProps extends Omit<QRCodeProps, 'sx'> {
9
+ export interface ExtendedQRCodeProps extends QRCodeProps {
10
10
  columnconfig?: ExtendedColumnConfig
11
11
  cellconfig?: cellconfig
12
12
  }
@@ -21,9 +21,11 @@ const useQRCode = (grid: {
21
21
  index: number
22
22
  ): columnconfig => {
23
23
  const {
24
- value,
24
+ username,
25
+ appName,
25
26
  size,
26
27
  title,
28
+ onSecretGenerated,
27
29
  columnconfig: itemColumnConfig,
28
30
  cellconfig,
29
31
  ...restProps
@@ -48,9 +50,11 @@ const useQRCode = (grid: {
48
50
  component: (
49
51
  <QRCodeComponent
50
52
  key={`qrcode-${index}`}
51
- value={value}
53
+ username={username}
54
+ appName={appName}
52
55
  size={size}
53
56
  title={title}
57
+ onSecretGenerated={onSecretGenerated}
54
58
  {...restProps}
55
59
  />
56
60
  ),
@@ -7,7 +7,7 @@ import 'react-datepicker/dist/react-datepicker.css'
7
7
  import CalendarTodayIcon from '@mui/icons-material/CalendarToday'
8
8
 
9
9
  export interface DateFieldProps extends Omit<TextFieldProps, 'onChange'> {
10
- onChange?: () => void
10
+ onChange?: (date: Date) => void
11
11
  backgroundcolor?: string
12
12
  outlinecolor?: string
13
13
  fontcolor?: string
@@ -58,7 +58,9 @@ const DateField: React.FC<DateFieldProps> = ({
58
58
 
59
59
  const handleChange = (dates: [Date | null, Date | null]) => {
60
60
  setDateRange(dates)
61
- onChange?.()
61
+ if (dates[0]) {
62
+ onChange?.(dates[0])
63
+ }
62
64
  }
63
65
 
64
66
  const formatDateRange = (range: [Date | null, Date | null]) => {
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import React, { useState } from 'react'
2
4
  import {
3
5
  Select,
@@ -247,7 +247,9 @@ const CustomGrid: React.FC<CustomGridProps> = ({
247
247
  : '100%',
248
248
  }}
249
249
  >
250
- {currentColumnConfig?.component || null}
250
+ {React.isValidElement(currentColumnConfig?.component)
251
+ ? currentColumnConfig?.component
252
+ : null}
251
253
  </Box>
252
254
  </Grid2>
253
255
  )
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import React, { useState } from 'react'
2
4
  import { IconButton } from '@mui/material'
3
5
  import FavoriteIcon from '@mui/icons-material/Favorite'
@@ -62,6 +62,7 @@ function VerticalVariant({
62
62
  }: VerticalVariantProps) {
63
63
  const router = useRouter()
64
64
  const [selectedNav, setSelectedNav] = useState<string | null>(null)
65
+ const [searchValue, setSearchValue] = useState('')
65
66
 
66
67
  const navOptions = items
67
68
  .filter((item): item is NavProps => 'title' in item && 'subnavs' in item)
@@ -92,6 +93,15 @@ function VerticalVariant({
92
93
  []
93
94
  )
94
95
 
96
+ const handleSearchChange = useCallback(
97
+ (e: React.ChangeEvent<HTMLInputElement>) => {
98
+ const newValue = e.target.value
99
+ setSearchValue(newValue)
100
+ console.log('Search value changed to:', newValue)
101
+ },
102
+ []
103
+ )
104
+
95
105
  const renderItem = useCallback(
96
106
  (
97
107
  item: NavProps | SubNav | View,
@@ -390,9 +400,8 @@ function VerticalVariant({
390
400
  outlinecolor="none"
391
401
  fontcolor={white.main}
392
402
  placeholder="Search..."
393
- onChange={() => {
394
- console.log('Search value changed')
395
- }}
403
+ value={searchValue}
404
+ onChange={handleSearchChange}
396
405
  />
397
406
  )}
398
407
  </Stack>
@@ -2,52 +2,58 @@ import React, { useMemo } from 'react'
2
2
  import QRCode from 'react-qr-code'
3
3
  import { Box, Typography, Paper, Theme, CircularProgress } from '@mui/material'
4
4
  import { SxProps } from '@mui/system'
5
+ import { authenticator } from 'otplib'
5
6
 
6
7
  /**
7
8
  * Props for the QRCodeComponent
8
9
  * @typedef {Object} QRCodeProps
9
- * @property {string} value - The value to be encoded in the QR code
10
+ * @property {string} username - The username for the MFA setup
11
+ * @property {string} appName - The name of the application for MFA
10
12
  * @property {number} [size] - The size of the QR code in pixels
11
13
  * @property {string} [title] - An optional title to display above the QR code
12
14
  * @property {SxProps<Theme>} [sx] - Custom styles to apply to the component
15
+ * @property {(secret: string) => void} [onSecretGenerated] - Callback function to receive the generated secret
13
16
  */
14
17
  export interface QRCodeProps {
15
- value: string
18
+ username: string
19
+ appName: string
16
20
  size?: number
17
21
  title?: string
18
22
  sx?: SxProps<Theme>
23
+ onSecretGenerated?: (secret: string) => void
19
24
  }
20
25
 
21
26
  /**
22
- * A component that displays a QR code with Material-UI styling
27
+ * A component that displays a QR code for MFA setup with Material-UI styling
23
28
  * @param {QRCodeProps} props - The props for the component
24
29
  * @returns {React.ReactElement} The rendered QR code component
25
30
  */
26
31
  const QRCodeComponent: React.FC<QRCodeProps> = React.memo(
27
- ({ value, size = 256, title, sx }) => {
28
- // Validate the QR code value
29
- const isValidValue = useMemo(() => {
30
- if (!value) return false
31
- try {
32
- // Check if the value is a valid URL
33
- new URL(value)
34
- return true
35
- } catch {
36
- // If not a URL, check if it's a non-empty string
37
- return typeof value === 'string' && value.trim().length > 0
32
+ ({ username, appName, size = 256, title, sx, onSecretGenerated }) => {
33
+ // Generate the secret and OTP auth URL
34
+ const { secret, otpAuth } = useMemo(() => {
35
+ const generatedSecret = authenticator.generateSecret()
36
+ const otpAuthUrl = authenticator.keyuri(
37
+ encodeURIComponent(username),
38
+ encodeURIComponent(appName),
39
+ generatedSecret
40
+ )
41
+ if (onSecretGenerated) {
42
+ onSecretGenerated(generatedSecret)
38
43
  }
39
- }, [value])
44
+ return { secret: generatedSecret, otpAuth: otpAuthUrl }
45
+ }, [username, appName, onSecretGenerated])
40
46
 
41
47
  // Calculate responsive size
42
48
  const responsiveSize = useMemo(() => {
43
49
  return Math.min(size, window.innerWidth - 32) // 32px for padding
44
50
  }, [size])
45
51
 
46
- if (!isValidValue) {
52
+ if (!otpAuth) {
47
53
  return (
48
54
  <Box sx={{ ...sx, p: 2 }} role="alert">
49
55
  <Typography color="error">
50
- Error: Invalid or empty QR code value
56
+ Error: Failed to generate QR code
51
57
  </Typography>
52
58
  </Box>
53
59
  )
@@ -88,13 +94,16 @@ const QRCodeComponent: React.FC<QRCodeProps> = React.memo(
88
94
  }
89
95
  >
90
96
  <QRCode
91
- value={value}
97
+ value={otpAuth}
92
98
  size={responsiveSize}
93
99
  style={{ height: 'auto', maxWidth: '100%', width: '100%' }}
94
- aria-label={`QR Code for ${title || value}`}
100
+ aria-label={`QR Code for ${title || 'MFA Setup'}`}
95
101
  />
96
102
  </React.Suspense>
97
103
  </Box>
104
+ <Typography variant="body2" align="center" sx={{ mt: 2 }}>
105
+ Secret: {secret}
106
+ </Typography>
98
107
  </Paper>
99
108
  )
100
109
  }
@@ -103,3 +112,22 @@ const QRCodeComponent: React.FC<QRCodeProps> = React.memo(
103
112
  QRCodeComponent.displayName = 'QRCodeComponent'
104
113
 
105
114
  export default QRCodeComponent
115
+
116
+ /**
117
+ * Verifies a MFA token against a secret.
118
+ *
119
+ * @param token - The token to verify.
120
+ * @param secret - The secret key to verify against.
121
+ * @returns A boolean indicating whether the token is valid.
122
+ * @throws Error if inputs are invalid.
123
+ */
124
+ export function verifyMFAToken(token: string, secret: string): boolean {
125
+ if (!token || typeof token !== 'string') {
126
+ throw new Error('Invalid token')
127
+ }
128
+ if (!secret || typeof secret !== 'string') {
129
+ throw new Error('Invalid secret')
130
+ }
131
+
132
+ return authenticator.verify({ token, secret })
133
+ }
@@ -9,13 +9,14 @@ export interface SearchbarProps {
9
9
  outlinecolor?: string
10
10
  fontcolor?: string
11
11
  placeholder?: string
12
- onChange?: () => void
12
+ value: string
13
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
13
14
  }
14
15
 
15
16
  const StyledTextField = styled(TextField)<{
16
17
  backgroundcolor?: string
17
18
  outlinecolor?: string
18
- $fontcolor?: string // Use $ prefix to avoid passing it as DOM attribute
19
+ $fontcolor?: string
19
20
  }>(({ theme, backgroundcolor, outlinecolor, $fontcolor }) => ({
20
21
  '& .MuiOutlinedInput-root': {
21
22
  backgroundColor: backgroundcolor || theme.palette.background.paper,
@@ -47,12 +48,9 @@ const Searchbar: React.FC<SearchbarProps> = ({
47
48
  outlinecolor,
48
49
  fontcolor,
49
50
  placeholder,
51
+ value,
50
52
  onChange,
51
53
  }) => {
52
- const handleChange = () => {
53
- onChange?.()
54
- }
55
-
56
54
  return (
57
55
  <StyledTextField
58
56
  label={label}
@@ -62,14 +60,17 @@ const Searchbar: React.FC<SearchbarProps> = ({
62
60
  backgroundcolor={backgroundcolor}
63
61
  outlinecolor={outlinecolor}
64
62
  $fontcolor={fontcolor}
65
- InputProps={{
66
- startAdornment: (
67
- <InputAdornment position="start">
68
- <SearchIcon style={{ color: iconcolor }} />
69
- </InputAdornment>
70
- ),
63
+ value={value}
64
+ onChange={onChange}
65
+ slotProps={{
66
+ input: {
67
+ startAdornment: (
68
+ <InputAdornment position="start">
69
+ <SearchIcon style={{ color: iconcolor }} />
70
+ </InputAdornment>
71
+ ),
72
+ },
71
73
  }}
72
- onChange={handleChange}
73
74
  />
74
75
  )
75
76
  }
@@ -20,7 +20,7 @@ const VerticalDivider = styled(Box)({
20
20
  export interface ToolbarProps {
21
21
  buttons?: CustomButtonProps[]
22
22
  dropdowns?: DropdownProps[]
23
- searchbarProps?: SearchbarProps
23
+ searchbarProps?: Partial<SearchbarProps>
24
24
  }
25
25
 
26
26
  /**
@@ -106,7 +106,8 @@ function CustomToolbar({ buttons, dropdowns, searchbarProps }: ToolbarProps) {
106
106
  label="Search the DataGrid"
107
107
  fontcolor={black.main}
108
108
  iconcolor={black.main}
109
- onChange={() => console.log('Search changed')}
109
+ value={searchbarProps?.value || ''}
110
+ onChange={searchbarProps?.onChange || (() => {})}
110
111
  {...searchbarProps}
111
112
  />
112
113
  </Box>
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import React from 'react'
3
+ import React, { useEffect } from 'react'
4
4
  import Grid from '@mui/material/Grid'
5
5
  import List from '@mui/material/List'
6
6
  import ListItemButton from '@mui/material/ListItemButton'
@@ -21,7 +21,7 @@ function intersection(a: readonly string[], b: readonly string[]) {
21
21
  export interface TransferListProps {
22
22
  leftItems: readonly string[]
23
23
  rightItems: readonly string[]
24
- onChange?: () => void
24
+ onChange: (leftItems: string[], rightItems: string[]) => void
25
25
  }
26
26
 
27
27
  const TransferList: React.FC<TransferListProps> = ({
@@ -33,6 +33,11 @@ const TransferList: React.FC<TransferListProps> = ({
33
33
  const [left, setLeft] = React.useState<readonly string[]>(leftItems)
34
34
  const [right, setRight] = React.useState<readonly string[]>(rightItems)
35
35
 
36
+ useEffect(() => {
37
+ setLeft(leftItems)
38
+ setRight(rightItems)
39
+ }, [leftItems, rightItems])
40
+
36
41
  const leftChecked = intersection(checked, left)
37
42
  const rightChecked = intersection(checked, right)
38
43
 
@@ -50,37 +55,35 @@ const TransferList: React.FC<TransferListProps> = ({
50
55
  }
51
56
 
52
57
  const handleAllRight = () => {
53
- setRight(right.concat(left))
58
+ const newRight = right.concat(left)
59
+ setRight(newRight)
54
60
  setLeft([])
55
- if (onChange) {
56
- onChange()
57
- }
61
+ onChange([], newRight)
58
62
  }
59
63
 
60
64
  const handleCheckedRight = () => {
61
- setRight(right.concat(leftChecked))
62
- setLeft(not(left, leftChecked))
65
+ const newRight = right.concat(leftChecked)
66
+ const newLeft = not(left, leftChecked)
67
+ setRight(newRight)
68
+ setLeft(newLeft)
63
69
  setChecked(not(checked, leftChecked))
64
- if (onChange) {
65
- onChange()
66
- }
70
+ onChange(newLeft, newRight)
67
71
  }
68
72
 
69
73
  const handleCheckedLeft = () => {
70
- setLeft(left.concat(rightChecked))
71
- setRight(not(right, rightChecked))
74
+ const newLeft = left.concat(rightChecked)
75
+ const newRight = not(right, rightChecked)
76
+ setLeft(newLeft)
77
+ setRight(newRight)
72
78
  setChecked(not(checked, rightChecked))
73
- if (onChange) {
74
- onChange()
75
- }
79
+ onChange(newLeft, newRight)
76
80
  }
77
81
 
78
82
  const handleAllLeft = () => {
79
- setLeft(left.concat(right))
83
+ const newLeft = left.concat(right)
84
+ setLeft(newLeft)
80
85
  setRight([])
81
- if (onChange) {
82
- onChange()
83
- }
86
+ onChange(newLeft, [])
84
87
  }
85
88
 
86
89
  const customList = (items: readonly string[]) => (
package/src/index.ts CHANGED
@@ -40,7 +40,7 @@ import QRCodeComponent, { QRCodeProps } from './components/QRCode'
40
40
 
41
41
  // New imports
42
42
  import DateField from './components/DateField'
43
- import Dropdown from './components/Dropdown'
43
+ import Dropdown, { DropdownOption } from './components/Dropdown'
44
44
  import IncrementNumberField from './components/IncrementNumberField'
45
45
  import NumberField from './components/NumberField'
46
46
  import PasswordField from './components/PasswordField'
@@ -71,7 +71,7 @@ import { ExtendedImageProps } from './components/Content/Structure/image/useImag
71
71
  import { ExtendedConfirmationCodeInputsProps } from './components/Content/Structure/confirmationinput/useConfirmationInput'
72
72
  import { ExtendedRadioGroupProps } from './components/Content/Structure/radiogroup/useRadioGroup'
73
73
  import { ExtendedPhoneNumberFieldProps } from './components/Content/Structure/phoneNumber/usePhoneNumber'
74
-
74
+ import { CustomTextFieldProps } from './components/TextField'
75
75
  // Colors
76
76
  import {
77
77
  moss,
@@ -267,6 +267,8 @@ export type { CustomStepperComponentProps }
267
267
  export type { CustomToolbarComponentProps }
268
268
  export type { TransferListComponentProps }
269
269
  export type { StyledTooltipComponentProps }
270
+ export type { DropdownOption }
271
+ export type { CustomTextFieldProps }
270
272
 
271
273
  // New type exports
272
274
  export type { DateFieldProps }