Skip to content

Commit

Permalink
port over access code components and add story
Browse files Browse the repository at this point in the history
  • Loading branch information
jonesmac committed Oct 7, 2024
1 parent a971513 commit 720a46e
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Alert, Typography } from '@mui/material'
import type { Meta, StoryFn } from '@storybook/react'
import { FlexCol } from '@xylabs/react-flexbox'
import React, { useState } from 'react'

import { AccessCodeGateFlexbox } from './AccessCodeGateFlexbox.tsx'

export default {
component: AccessCodeGateFlexbox,
title: 'access/AccessCodeGateFlexbox',
} as Meta

const Template: StoryFn<typeof AccessCodeGateFlexbox> = args => (
<AccessCodeGateFlexbox {...args} />
)

const TemplateWithAccessCodes: StoryFn<typeof AccessCodeGateFlexbox> = (args) => {
const validAccessCodes = ['100519']
const [validated, setValidated] = useState(false)
const onAccessCodeSuccess = () => {
setValidated(true)
}
const validateFunction = (code?: string) => code?.length === 6

return validated
? <Alert severity="success">Success!</Alert>
: (
<FlexCol gap={2}>
<AccessCodeGateFlexbox
onAccessCodeSuccess={onAccessCodeSuccess}
validAccessCodes={validAccessCodes}
validateFunction={validateFunction}
/>
<Typography variant="caption">Hint: 100519</Typography>
</FlexCol>
)
}

const Default = Template.bind({})
const WithAccessCodes = TemplateWithAccessCodes.bind({})

export { Default, WithAccessCodes }
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable @eslint-react/hooks-extra/no-direct-set-state-in-use-effect */
import { FormControl } from '@mui/material'
import { ButtonEx } from '@xylabs/react-button'
import type { FlexBoxProps } from '@xylabs/react-flexbox'
import { FlexGrowCol, FlexGrowRow } from '@xylabs/react-flexbox'
import type { WithChildren } from '@xylabs/react-shared'
import React, {
useCallback, useEffect, useState,
} from 'react'

import { CodeTextField } from './CodeTextField.tsx'

export interface AccessCodeGateFlexbox extends WithChildren, FlexBoxProps {
onAccessCodeSuccess?: (code?: string) => void
textFieldHelperText?: string
userAccessCodes?: string[]
validAccessCodes?: string[]
validateFunction?: (codeInput?: string) => boolean
}

export const AccessCodeGateFlexbox: React.FC<AccessCodeGateFlexbox> = ({
children,
onAccessCodeSuccess,
userAccessCodes,
validAccessCodes,
validateFunction,
...props
}) => {
const [initialized, setInitialized] = useState(false)
const [accessGranted, setAccessGranted] = useState(false)
const [codeInput, setCodeInput] = useState<string>()
const [validCode, setValidCode] = useState<boolean | null>(null)

const disabled = validateFunction ? !validateFunction(codeInput) : !codeInput
const validateCode = useCallback((accessCode: string) => (accessCode ? validAccessCodes?.includes(accessCode) : false), [validAccessCodes])

const onEnter = () => {
if (codeInput) {
const granted = validateCode(codeInput)
if (granted) {
setValidCode(true)
// delay success callback to ensure the ui shows success before next action
setTimeout(() => {
setAccessGranted(granted)
onAccessCodeSuccess?.(codeInput)
}, 1500)
} else {
setValidCode(false)
}
}
}

useEffect(() => {
// whenever a code changes, reset the success/failure warning
setValidCode(null)
}, [codeInput])

useEffect(() => {
if (userAccessCodes) {
const granted = userAccessCodes.some(code => validateCode(code))
setAccessGranted(granted)
if (granted) {
onAccessCodeSuccess?.()
}
}
setInitialized(true)
}, [onAccessCodeSuccess, userAccessCodes, validateCode])

return (
<>
{initialized
? accessGranted
? children
: (
<FlexGrowCol gap={2} {...props}>
<FlexGrowRow gap={2} alignItems="start">
<FormControl>
<CodeTextField
codeInput={codeInput}
disabled={disabled}
label="Enter Access Code"
setCodeInput={setCodeInput}
validCode={validCode}
onEnter={onEnter}
/>
</FormControl>
<FormControl>
<ButtonEx disabled={disabled} onClick={onEnter} variant="contained">
Enter
</ButtonEx>
</FormControl>
</FlexGrowRow>
</FlexGrowCol>
)

: null}
</>
)
}
45 changes: 45 additions & 0 deletions packages/sdk/packages/access-gate/src/components/CodeTextField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CheckCircleOutline, ErrorOutline } from '@mui/icons-material'
import type { TextFieldProps } from '@mui/material'
import {
InputAdornment, styled, TextField,
} from '@mui/material'
import type { Dispatch, SetStateAction } from 'react'
import React from 'react'

export type CodeTextFieldProps = TextFieldProps & {
codeInput?: string
disabled?: boolean
onEnter?: () => void
setCodeInput?: Dispatch<SetStateAction<string | undefined>>
validCode?: boolean | null
}

export const CodeTextField: React.FC<CodeTextFieldProps> = ({
codeInput, disabled, onEnter, setCodeInput, validCode, ...props
}) => (
<StyledTextField
InputProps={{
endAdornment: (
<InputAdornment position="start">
{/* Having a display block element for all 3 states (null, false, true) means the icon coming in and out
does not affect the overall width */}
<CheckCircleOutline sx={{ display: validCode === null ? 'block' : 'hidden', visibility: 'hidden' }} />
<CheckCircleOutline
color="success"
fontSize="medium"
sx={{ position: 'absolute', visibility: validCode === true ? 'visible' : 'hidden' }}
/>
<ErrorOutline color="error" fontSize="medium" sx={{ position: 'absolute', visibility: validCode === false ? 'visible' : 'hidden' }} />
</InputAdornment>
),
}}
onKeyUp={event => (event.key === 'Enter' && !disabled ? onEnter?.() : null)}
autoFocus
size="small"
value={codeInput ?? ''}
onChange={event => setCodeInput?.(event.target.value)}
{...props}
/>
)

const StyledTextField = styled(TextField, { name: 'StyledTextField' })(() => ({ '& .MuiInputBase-root': { paddingRight: 0 } }))
2 changes: 1 addition & 1 deletion packages/sdk/packages/access-gate/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export {}
export * from './AccessCodeGateFlexbox.tsx'
1 change: 1 addition & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from '@xyo-network/react-access-gate'
export * from '@xyo-network/react-address'
export * from '@xyo-network/react-app-settings'
export * from '@xyo-network/react-appbar'
Expand Down

0 comments on commit 720a46e

Please sign in to comment.