@verifiedinc-public/shared-ui-elements 0.11.6 → 0.12.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/README.md +41 -0
- package/package.json +6 -24
- package/src/components/Alert/Alert.tsx +8 -0
- package/src/components/Alert/FullWidthAlert.tsx +27 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/Button/index.tsx +8 -0
- package/src/components/CredentialRequestsEditor/CredentialRequestsEditor.context.tsx +98 -0
- package/src/components/CredentialRequestsEditor/components/CredentialRequestsField.tsx +103 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldAccordion.tsx +337 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldDeleteModal.tsx +64 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldDescription.tsx +68 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldMandatory.tsx +84 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldMulti.tsx +74 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldOptionType.tsx +84 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldSection.tsx +48 -0
- package/src/components/CredentialRequestsEditor/components/DataFieldUserInput.tsx +71 -0
- package/src/components/CredentialRequestsEditor/components/RadioOption.tsx +89 -0
- package/src/components/CredentialRequestsEditor/contexts/CredentialRequestFieldContext.tsx +36 -0
- package/src/components/CredentialRequestsEditor/index.tsx +15 -0
- package/src/components/CredentialRequestsEditor/types/compositeCredentialSchema.ts +1 -0
- package/src/components/CredentialRequestsEditor/types/credentialSchemasDto.ts +3 -0
- package/src/components/CredentialRequestsEditor/types/form.ts +28 -0
- package/src/components/CredentialRequestsEditor/types/mandatoryEnum.ts +5 -0
- package/src/components/CredentialRequestsEditor/utils/buildDataFieldValue.ts +65 -0
- package/src/components/CredentialRequestsEditor/utils/prettyField.ts +16 -0
- package/src/components/Image.tsx +10 -0
- package/src/components/QRCodeDisplay/index.tsx +50 -0
- package/src/components/RequiredLabel/index.tsx +15 -0
- package/src/components/TextField/index.tsx +8 -0
- package/src/components/Tip/index.tsx +18 -0
- package/src/components/Typography/index.tsx +8 -0
- package/src/components/When.tsx +28 -0
- package/src/components/form/CountrySelector.tsx +96 -0
- package/src/components/form/DataFieldClearAdornment.tsx +28 -0
- package/src/components/form/DateInput.tsx +117 -0
- package/src/components/form/DefaultInput.tsx +28 -0
- package/src/components/form/InputMask.tsx +41 -0
- package/src/components/form/OTPInput.tsx +246 -0
- package/src/components/form/PhoneInput.tsx +153 -0
- package/src/components/form/SSNInput.tsx +100 -0
- package/src/components/form/SelectInput.tsx +89 -0
- package/src/components/form/TextMaskCustom.tsx +48 -0
- package/src/components/form/index.ts +5 -0
- package/src/components/form/styles/input.ts +24 -0
- package/src/components/index.ts +10 -0
- package/src/components/terms/AcceptTermsNotice.tsx +27 -0
- package/src/components/terms/LegalLink.tsx +22 -0
- package/src/components/verified/VerifiedImage.tsx +272 -0
- package/src/components/verified/VerifiedIncLogo.tsx +11 -0
- package/src/components/verified/index.ts +2 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useCallbackRef.ts +22 -0
- package/src/hooks/useCopyToClipboard.ts +76 -0
- package/src/hooks/useDisclosure.ts +96 -0
- package/src/hooks/useLocalStorage.ts +24 -0
- package/src/hooks/usePrevious.ts +17 -0
- package/src/hooks/useQRCode.ts +62 -0
- package/src/index.ts +5 -0
- package/src/stories/components/Alert.stories.tsx +41 -0
- package/src/stories/components/Button.stories.ts +49 -0
- package/src/stories/components/CredentialRequestsEditor.stories.tsx +98 -0
- package/src/stories/components/QRCodeDisplay.stories.tsx +60 -0
- package/src/stories/components/TextField.stories.ts +59 -0
- package/src/stories/components/Typography.stories.ts +140 -0
- package/src/stories/components/VerifiedImage.stories.tsx +32 -0
- package/src/stories/components/form/DateInput.stories.ts +39 -0
- package/src/stories/components/form/OTPInput.stories.tsx +90 -0
- package/src/stories/components/form/PhoneInput.stories.tsx +34 -0
- package/src/stories/components/form/SSNInput.stories.ts +30 -0
- package/src/stories/components/form/SelectInput.stories.ts +39 -0
- package/src/stories/hooks/useCopyToClipboard.stories.tsx +45 -0
- package/src/styles/colors.ts +34 -0
- package/src/styles/index.ts +2 -0
- package/src/styles/theme.ts +209 -0
- package/src/utils/date.ts +41 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/masks/index.ts +6 -0
- package/src/utils/omitProperty.ts +19 -0
- package/src/utils/phone.ts +76 -0
- package/src/utils/wrapPromise.ts +19 -0
- package/src/validations/birthDate.schema.ts +54 -0
- package/src/validations/date.schema.ts +10 -0
- package/src/validations/description.schema.ts +5 -0
- package/src/validations/email.schema.ts +3 -0
- package/src/validations/field.schema.ts +3 -0
- package/src/validations/index.ts +9 -0
- package/src/validations/phone.schema.ts +6 -0
- package/src/validations/ssn.schema.ts +6 -0
- package/src/validations/state.schema.ts +3 -0
- package/src/validations/unix.schema.ts +11 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
2
|
+
|
3
|
+
import { PhoneInput } from '../../../components/form/PhoneInput';
|
4
|
+
import { fn } from '@storybook/test';
|
5
|
+
|
6
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
7
|
+
const meta = {
|
8
|
+
title: 'Components/PhoneInput',
|
9
|
+
component: PhoneInput,
|
10
|
+
parameters: {
|
11
|
+
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
12
|
+
layout: 'centered',
|
13
|
+
},
|
14
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
15
|
+
tags: ['autodocs'],
|
16
|
+
args: {
|
17
|
+
shouldHaveClearButton: true,
|
18
|
+
},
|
19
|
+
} satisfies Meta<typeof PhoneInput>;
|
20
|
+
|
21
|
+
export default meta;
|
22
|
+
type Story = StoryObj<typeof meta>;
|
23
|
+
|
24
|
+
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
25
|
+
export const Default: Story = {
|
26
|
+
args: {
|
27
|
+
name: 'date',
|
28
|
+
label: 'Label',
|
29
|
+
onChange: fn(),
|
30
|
+
onValidPhone: fn(),
|
31
|
+
error: false,
|
32
|
+
helperText: 'Helper text',
|
33
|
+
},
|
34
|
+
};
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
2
|
+
|
3
|
+
import { fn } from '@storybook/test';
|
4
|
+
import { SSNInput } from '../../../components/form/SSNInput';
|
5
|
+
|
6
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
7
|
+
const meta = {
|
8
|
+
title: 'Components/SSNInput',
|
9
|
+
component: SSNInput,
|
10
|
+
parameters: {
|
11
|
+
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
12
|
+
layout: 'centered',
|
13
|
+
},
|
14
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
15
|
+
tags: ['autodocs'],
|
16
|
+
args: {
|
17
|
+
name: 'ssn',
|
18
|
+
label: 'SSN',
|
19
|
+
error: false,
|
20
|
+
helperText: 'Helper text',
|
21
|
+
shouldHaveCloseAdornment: false,
|
22
|
+
onChange: fn(),
|
23
|
+
},
|
24
|
+
};
|
25
|
+
|
26
|
+
export default meta;
|
27
|
+
type Story = StoryObj<typeof meta>;
|
28
|
+
|
29
|
+
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
30
|
+
export const Default: Story = {};
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
2
|
+
|
3
|
+
import { SelectInput } from '../../../components/form/SelectInput';
|
4
|
+
import { fn } from '@storybook/test';
|
5
|
+
|
6
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
7
|
+
const meta = {
|
8
|
+
title: 'Components/SelectInput',
|
9
|
+
component: SelectInput,
|
10
|
+
parameters: {
|
11
|
+
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
12
|
+
layout: 'centered',
|
13
|
+
},
|
14
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
15
|
+
tags: ['autodocs'],
|
16
|
+
args: {
|
17
|
+
InputProps: {
|
18
|
+
name: 'Select Input',
|
19
|
+
label: 'Select Input',
|
20
|
+
error: false,
|
21
|
+
helperText: 'Helper text',
|
22
|
+
sx: { width: '200px' },
|
23
|
+
},
|
24
|
+
onChange: fn(),
|
25
|
+
onClear: fn(),
|
26
|
+
options: [
|
27
|
+
{ label: 'Option 1', id: '1' },
|
28
|
+
{ label: 'Option 2', id: '2' },
|
29
|
+
{ label: 'Option 3', id: '3' },
|
30
|
+
],
|
31
|
+
defaultOption: { label: 'Option 1', id: '1' },
|
32
|
+
},
|
33
|
+
} satisfies Meta<typeof SelectInput>;
|
34
|
+
|
35
|
+
export default meta;
|
36
|
+
type Story = StoryObj<typeof meta>;
|
37
|
+
|
38
|
+
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
39
|
+
export const Default: Story = {};
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import type { ReactElement } from 'react';
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
3
|
+
|
4
|
+
import { Button } from '../../components/Button';
|
5
|
+
import { useCopyToClipboard } from '../../hooks/useCopyToClipboard';
|
6
|
+
|
7
|
+
// Render to test the hook implementation.
|
8
|
+
function HookRender(props: any): ReactElement {
|
9
|
+
const copyToClipboard = useCopyToClipboard({ type: 'text/plain' });
|
10
|
+
return (
|
11
|
+
<Button
|
12
|
+
onClick={() => {
|
13
|
+
copyToClipboard
|
14
|
+
.copy(props.content as string)
|
15
|
+
.then(() => undefined)
|
16
|
+
.catch(() => undefined);
|
17
|
+
}}
|
18
|
+
>
|
19
|
+
Copy
|
20
|
+
</Button>
|
21
|
+
);
|
22
|
+
}
|
23
|
+
|
24
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
25
|
+
const meta = {
|
26
|
+
title: 'Hooks/useCopyToClipboard',
|
27
|
+
component: HookRender,
|
28
|
+
parameters: {
|
29
|
+
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
30
|
+
layout: 'centered',
|
31
|
+
},
|
32
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
33
|
+
tags: ['autodocs'],
|
34
|
+
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
35
|
+
} satisfies Meta<typeof Button>;
|
36
|
+
|
37
|
+
export default meta;
|
38
|
+
type Story = StoryObj<typeof meta>;
|
39
|
+
|
40
|
+
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
41
|
+
export const TextPlain: Story = {
|
42
|
+
args: {
|
43
|
+
content: 'Clicked!',
|
44
|
+
},
|
45
|
+
};
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import { colors } from '@mui/material';
|
2
|
+
|
3
|
+
export const textDisabled = 'rgba(0,0,0,0.26)';
|
4
|
+
|
5
|
+
export const white = '#ffffff';
|
6
|
+
export const black = '#000000';
|
7
|
+
|
8
|
+
export const green = '#0dbc3d';
|
9
|
+
export const lightGreen = '#5ef06d';
|
10
|
+
export const darkGreen = '#008a01';
|
11
|
+
|
12
|
+
export const blue = '#164fd6';
|
13
|
+
export const lightBlue = '#657bff';
|
14
|
+
export const darkBlue = '#0028a3';
|
15
|
+
|
16
|
+
export const red = '#eb0d28';
|
17
|
+
export const lightRed = '#ff5952';
|
18
|
+
export const darkRed = '#b00000';
|
19
|
+
|
20
|
+
export const yellow = '#F5D328';
|
21
|
+
export const lightYellow = '#5ef06d';
|
22
|
+
export const darkYellow = '#bea008';
|
23
|
+
|
24
|
+
export const warningContrast = '#625410';
|
25
|
+
|
26
|
+
export const infoContrast = '#09225E';
|
27
|
+
|
28
|
+
export const lightGrey = '#F9F9FB';
|
29
|
+
export const grey = '#bdbdbd';
|
30
|
+
export const darkGrey = '#797979';
|
31
|
+
|
32
|
+
export const lightGreyContrast = colors.grey['400'];
|
33
|
+
export const greyContrast = colors.grey['500'];
|
34
|
+
export const darkGreyContrast = colors.grey['600'];
|
@@ -0,0 +1,209 @@
|
|
1
|
+
import { createTheme } from '@mui/material';
|
2
|
+
import * as colors from './colors';
|
3
|
+
|
4
|
+
declare module '@mui/material/styles' {
|
5
|
+
// custom palette
|
6
|
+
interface Palette {
|
7
|
+
neutral: Palette['primary'];
|
8
|
+
neutralContrast: Palette['primary'];
|
9
|
+
}
|
10
|
+
|
11
|
+
interface PaletteOptions {
|
12
|
+
neutral: PaletteOptions['primary'];
|
13
|
+
neutralContrast: PaletteOptions['primary'];
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
// add neutral color palette as color option for buttons
|
18
|
+
declare module '@mui/material' {
|
19
|
+
interface ButtonPropsColorOverrides {
|
20
|
+
neutral: true;
|
21
|
+
neutralContrast: true;
|
22
|
+
}
|
23
|
+
interface SvgIconPropsColorOverrides {
|
24
|
+
neutral: true;
|
25
|
+
neutralContrast: true;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
interface ThemeOptions {
|
30
|
+
primaryFontFace: Record<string, any>;
|
31
|
+
}
|
32
|
+
|
33
|
+
export const theme = ({ primaryFontFace }: ThemeOptions) =>
|
34
|
+
createTheme({
|
35
|
+
typography: {
|
36
|
+
fontFamily: primaryFontFace.style.fontFamily,
|
37
|
+
},
|
38
|
+
palette: {
|
39
|
+
text: {
|
40
|
+
disabled: colors.textDisabled,
|
41
|
+
},
|
42
|
+
primary: {
|
43
|
+
main: colors.green,
|
44
|
+
light: colors.lightGreen,
|
45
|
+
dark: colors.darkGreen,
|
46
|
+
contrastText: colors.white,
|
47
|
+
},
|
48
|
+
secondary: {
|
49
|
+
main: colors.blue,
|
50
|
+
light: colors.lightBlue,
|
51
|
+
dark: colors.darkBlue,
|
52
|
+
contrastText: colors.white,
|
53
|
+
},
|
54
|
+
error: {
|
55
|
+
main: colors.red,
|
56
|
+
light: colors.lightRed,
|
57
|
+
dark: colors.darkRed,
|
58
|
+
contrastText: colors.white,
|
59
|
+
},
|
60
|
+
warning: {
|
61
|
+
main: colors.yellow,
|
62
|
+
light: colors.yellow,
|
63
|
+
dark: colors.darkYellow,
|
64
|
+
contrastText: colors.white,
|
65
|
+
},
|
66
|
+
success: {
|
67
|
+
main: colors.green,
|
68
|
+
light: colors.green,
|
69
|
+
dark: colors.darkGreen,
|
70
|
+
contrastText: colors.white,
|
71
|
+
},
|
72
|
+
info: {
|
73
|
+
main: colors.blue,
|
74
|
+
light: colors.lightBlue,
|
75
|
+
dark: colors.darkBlue,
|
76
|
+
contrastText: colors.infoContrast,
|
77
|
+
},
|
78
|
+
neutral: {
|
79
|
+
main: colors.grey,
|
80
|
+
light: colors.lightGrey,
|
81
|
+
dark: colors.darkGrey,
|
82
|
+
},
|
83
|
+
neutralContrast: {
|
84
|
+
main: colors.greyContrast,
|
85
|
+
light: colors.lightGreyContrast,
|
86
|
+
dark: colors.darkGreyContrast,
|
87
|
+
},
|
88
|
+
},
|
89
|
+
components: {
|
90
|
+
MuiListItemIcon: {
|
91
|
+
styleOverrides: {
|
92
|
+
root: {
|
93
|
+
minWidth: 34,
|
94
|
+
},
|
95
|
+
},
|
96
|
+
},
|
97
|
+
MuiListItemText: {
|
98
|
+
styleOverrides: {
|
99
|
+
primary: {
|
100
|
+
fontWeight: 800,
|
101
|
+
},
|
102
|
+
},
|
103
|
+
},
|
104
|
+
MuiFab: {
|
105
|
+
styleOverrides: {
|
106
|
+
circular: {
|
107
|
+
width: 42,
|
108
|
+
height: 42,
|
109
|
+
},
|
110
|
+
sizeSmall: {
|
111
|
+
width: 40,
|
112
|
+
height: 40,
|
113
|
+
},
|
114
|
+
},
|
115
|
+
},
|
116
|
+
MuiIconButton: {
|
117
|
+
styleOverrides: {
|
118
|
+
root: {
|
119
|
+
'&.Mui-disabled': {
|
120
|
+
svg: {
|
121
|
+
opacity: 0.4,
|
122
|
+
},
|
123
|
+
},
|
124
|
+
},
|
125
|
+
},
|
126
|
+
},
|
127
|
+
MuiButton: {
|
128
|
+
styleOverrides: {
|
129
|
+
root: {
|
130
|
+
fontWeight: 800,
|
131
|
+
},
|
132
|
+
},
|
133
|
+
},
|
134
|
+
MuiDialog: {
|
135
|
+
defaultProps: {
|
136
|
+
maxWidth: 'xs',
|
137
|
+
},
|
138
|
+
},
|
139
|
+
MuiDialogTitle: {
|
140
|
+
styleOverrides: {
|
141
|
+
root: {
|
142
|
+
fontSize: 20,
|
143
|
+
fontWeight: 800,
|
144
|
+
textAlign: 'center',
|
145
|
+
},
|
146
|
+
},
|
147
|
+
},
|
148
|
+
MuiDialogContent: {
|
149
|
+
styleOverrides: {
|
150
|
+
root: {
|
151
|
+
paddingTop: '8px!important',
|
152
|
+
paddingBottom: 8,
|
153
|
+
},
|
154
|
+
},
|
155
|
+
},
|
156
|
+
MuiDialogActions: {
|
157
|
+
styleOverrides: {
|
158
|
+
root: {
|
159
|
+
paddingLeft: 24,
|
160
|
+
paddingRight: 24,
|
161
|
+
paddingBottom: 24,
|
162
|
+
justifyContent: 'space-between',
|
163
|
+
'& .MuiButton-root': {
|
164
|
+
marginTop: 0,
|
165
|
+
},
|
166
|
+
},
|
167
|
+
},
|
168
|
+
},
|
169
|
+
MuiAlert: {
|
170
|
+
styleOverrides: {
|
171
|
+
root: {
|
172
|
+
maxWidth: '339px',
|
173
|
+
},
|
174
|
+
action: {
|
175
|
+
padding: '8px 0',
|
176
|
+
marginRight: 0,
|
177
|
+
alignItems: 'center',
|
178
|
+
'& button, & a': {
|
179
|
+
lineHeight: '0',
|
180
|
+
},
|
181
|
+
},
|
182
|
+
},
|
183
|
+
},
|
184
|
+
MuiRadio: {
|
185
|
+
styleOverrides: {
|
186
|
+
root: {
|
187
|
+
'&.Mui-disabled': {
|
188
|
+
color: `${colors.grey} !important`,
|
189
|
+
},
|
190
|
+
},
|
191
|
+
},
|
192
|
+
},
|
193
|
+
MuiTableRow: {
|
194
|
+
styleOverrides: {
|
195
|
+
root: {
|
196
|
+
'& th.MuiTableCell-root': {
|
197
|
+
fontSize: 12,
|
198
|
+
fontWeight: 700,
|
199
|
+
letterSpacing: 1,
|
200
|
+
},
|
201
|
+
'& td.MuiTableCell-root': {
|
202
|
+
fontSize: 20,
|
203
|
+
fontWeight: 300,
|
204
|
+
},
|
205
|
+
},
|
206
|
+
},
|
207
|
+
},
|
208
|
+
},
|
209
|
+
});
|
@@ -0,0 +1,41 @@
|
|
1
|
+
/**
|
2
|
+
* Formats a timestamp into a pretty format of MM/DD/YYYY.
|
3
|
+
* @param timestamp
|
4
|
+
* @param separator
|
5
|
+
*/
|
6
|
+
export const formatDateMMDDYYYY = (timestamp?: string): string => {
|
7
|
+
let date = new Date(Number(timestamp));
|
8
|
+
|
9
|
+
if (!timestamp) {
|
10
|
+
const nowDate = new Date();
|
11
|
+
date = new Date(
|
12
|
+
nowDate.getFullYear(),
|
13
|
+
nowDate.getMonth(),
|
14
|
+
nowDate.getDate(),
|
15
|
+
);
|
16
|
+
}
|
17
|
+
|
18
|
+
const day = String(date.getDate()).padStart(2, '0');
|
19
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
20
|
+
const year = date.getFullYear();
|
21
|
+
return [month, day, year].join('/');
|
22
|
+
};
|
23
|
+
|
24
|
+
// Get the minimum date instance with the given date, month, and year.
|
25
|
+
export const getMinDateInstance = (
|
26
|
+
minDate = 1,
|
27
|
+
minMonth = 1,
|
28
|
+
minYear = 1900,
|
29
|
+
): Date => {
|
30
|
+
return new Date(minYear, minMonth - 1, minDate, 0, 0, 0, 0);
|
31
|
+
};
|
32
|
+
|
33
|
+
// Get the maximum date instance.
|
34
|
+
export const getMaxDateInstance = (allowFutureDates = true): Date => {
|
35
|
+
const nowDate = new Date();
|
36
|
+
const maxDate = allowFutureDates ? 31 : nowDate.getDate();
|
37
|
+
const maxMonth = allowFutureDates ? 12 : nowDate.getMonth() + 1;
|
38
|
+
const maxYear = allowFutureDates ? 2200 : nowDate.getFullYear();
|
39
|
+
|
40
|
+
return new Date(maxYear, maxMonth - 1, maxDate, 23, 59, 59, 999);
|
41
|
+
};
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import _ from 'lodash';
|
2
|
+
|
3
|
+
export function omitProperties<T>(obj: T, propertiesToOmit: string[]): T {
|
4
|
+
if (_.isArray(obj)) {
|
5
|
+
// If obj is an array, apply the function to each element
|
6
|
+
return obj.map((item) => omitProperties(item, propertiesToOmit)) as T;
|
7
|
+
} else if (_.isObject(obj)) {
|
8
|
+
// If obj is an object, omit the specified properties
|
9
|
+
let omittedObject: any = _.omit(obj, propertiesToOmit);
|
10
|
+
// Recursively apply the function to each property in the object
|
11
|
+
omittedObject = _.mapValues(omittedObject, (value) =>
|
12
|
+
omitProperties(value, propertiesToOmit),
|
13
|
+
);
|
14
|
+
return omittedObject as T;
|
15
|
+
} else {
|
16
|
+
// If obj is neither an array nor an object, return it as is
|
17
|
+
return obj;
|
18
|
+
}
|
19
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import parsePhoneNumber, { isValidPhoneNumber } from 'libphonenumber-js';
|
2
|
+
import find from 'lodash/find';
|
3
|
+
|
4
|
+
interface CountryData {
|
5
|
+
countryName: string;
|
6
|
+
countryCode: string;
|
7
|
+
emoji: string;
|
8
|
+
mask: string;
|
9
|
+
}
|
10
|
+
|
11
|
+
export const countries: CountryData[] = [
|
12
|
+
{
|
13
|
+
countryName: 'Canada',
|
14
|
+
countryCode: 'CA',
|
15
|
+
emoji: '🇨🇦',
|
16
|
+
mask: '{+}{1} (000) 000-0000',
|
17
|
+
},
|
18
|
+
{
|
19
|
+
countryName: 'United States',
|
20
|
+
countryCode: 'US',
|
21
|
+
emoji: '🇺🇸',
|
22
|
+
mask: '{+}{1} (000) 000-0000',
|
23
|
+
},
|
24
|
+
{
|
25
|
+
countryName: 'Brazil',
|
26
|
+
countryCode: 'BR',
|
27
|
+
emoji: '🇧🇷',
|
28
|
+
mask: '{+}{55} (00) 00000-0000',
|
29
|
+
},
|
30
|
+
];
|
31
|
+
|
32
|
+
export function parseToPhoneNational(internationalPhone: string): string {
|
33
|
+
const phoneMeta = parsePhoneNumber(internationalPhone);
|
34
|
+
if (!phoneMeta) return internationalPhone;
|
35
|
+
return `+${phoneMeta.countryCallingCode} ${phoneMeta.formatNational()}`;
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Get phone data by the international phone string.
|
40
|
+
* @param internationalPhone
|
41
|
+
*/
|
42
|
+
export function getPhoneData(
|
43
|
+
internationalPhone: string,
|
44
|
+
): CountryData | undefined {
|
45
|
+
const phoneMeta = parsePhoneNumber(internationalPhone);
|
46
|
+
return countries.find((c) => c.countryCode === phoneMeta?.country);
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Get phone data by property and value match.
|
51
|
+
* @param fieldName
|
52
|
+
* @param value
|
53
|
+
*/
|
54
|
+
export function getPhoneDataByFieldName(
|
55
|
+
fieldName: keyof CountryData,
|
56
|
+
value: string,
|
57
|
+
): CountryData | undefined {
|
58
|
+
return find(countries, { [fieldName]: value });
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Helper to sort countries by country name.
|
63
|
+
* @param a
|
64
|
+
* @param b
|
65
|
+
*/
|
66
|
+
export const sortByCountryName = (a: CountryData, b: CountryData) =>
|
67
|
+
a.countryName.localeCompare(b.countryName);
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Validate phone when it is valid and contains a country.
|
71
|
+
* @param internationalPhone
|
72
|
+
*/
|
73
|
+
export const validatePhone = (internationalPhone: string) => {
|
74
|
+
const phoneMeta = parsePhoneNumber(internationalPhone);
|
75
|
+
return isValidPhoneNumber(internationalPhone) && !!phoneMeta?.country;
|
76
|
+
};
|
@@ -0,0 +1,19 @@
|
|
1
|
+
type WrappedPromiseEitherResponse<D, Error> =
|
2
|
+
| [Awaited<D>, null]
|
3
|
+
| [null, Error];
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Wrap a promise to return an array with the data and the error.
|
7
|
+
* @param promise The promise to wrap
|
8
|
+
* @returns
|
9
|
+
*/
|
10
|
+
export async function wrapPromise<D, Error = any>(
|
11
|
+
promise: PromiseLike<D>,
|
12
|
+
): Promise<WrappedPromiseEitherResponse<D, Error>> {
|
13
|
+
try {
|
14
|
+
const data = await promise;
|
15
|
+
return [data, null];
|
16
|
+
} catch (error) {
|
17
|
+
return [null, error as Error];
|
18
|
+
}
|
19
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import * as zod from 'zod';
|
2
|
+
|
3
|
+
const validate = (value: string) => {
|
4
|
+
const now = new Date();
|
5
|
+
const minDate = new Date('1900-01-01');
|
6
|
+
const maxDate = new Date(
|
7
|
+
now.getFullYear(),
|
8
|
+
now.getMonth(),
|
9
|
+
now.getDate(),
|
10
|
+
23,
|
11
|
+
59,
|
12
|
+
59,
|
13
|
+
999,
|
14
|
+
);
|
15
|
+
// Safari doesn't allow date strings with hyphens
|
16
|
+
const formattedValue = value.replace(/-/g, '/');
|
17
|
+
const valueDate = new Date(formattedValue);
|
18
|
+
|
19
|
+
if (valueDate >= minDate && valueDate <= maxDate) {
|
20
|
+
const date = Date.parse(String(new Date(formattedValue)));
|
21
|
+
return !isNaN(date);
|
22
|
+
}
|
23
|
+
|
24
|
+
return false;
|
25
|
+
};
|
26
|
+
|
27
|
+
export const birthDateSchema = zod.string().refine((value: string) => {
|
28
|
+
const regex = /\d{2}-\d{2}-\d{4}/;
|
29
|
+
if (regex.test(value)) {
|
30
|
+
return validate(value);
|
31
|
+
}
|
32
|
+
return false;
|
33
|
+
}, 'Date of Birth is invalid');
|
34
|
+
|
35
|
+
export const simpleBirthDateSchema = zod.string().refine((value: string) => {
|
36
|
+
const regex = /^\d{2}\d{2}\d{4}$/;
|
37
|
+
if (regex.test(value)) {
|
38
|
+
const formattedValue = `${value.slice(0, 2)}/${value.slice(
|
39
|
+
2,
|
40
|
+
4,
|
41
|
+
)}/${value.slice(4, 8)}`;
|
42
|
+
return validate(formattedValue);
|
43
|
+
}
|
44
|
+
return false;
|
45
|
+
}, 'Date of Birth is invalid');
|
46
|
+
|
47
|
+
export const shortenBirthDateSchema = zod.string().refine((value: string) => {
|
48
|
+
const regex = /^\d{2}\d{2}$/;
|
49
|
+
if (regex.test(value)) {
|
50
|
+
const formattedValue = `${value.slice(0, 2)}/${value.slice(2, 4)}/1970`;
|
51
|
+
return validate(formattedValue);
|
52
|
+
}
|
53
|
+
return false;
|
54
|
+
}, 'Date of Birth is invalid');
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import * as zod from 'zod';
|
2
|
+
|
3
|
+
export const USDateSchema = zod.string().refine((value: string) => {
|
4
|
+
const regex = /^\d{2}\/\d{2}\/\d{4}$/;
|
5
|
+
if (regex.test(value)) {
|
6
|
+
const date = Date.parse(String(new Date(value)));
|
7
|
+
return !isNaN(date);
|
8
|
+
}
|
9
|
+
return false;
|
10
|
+
}, 'Date is invalid');
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export * from './birthDate.schema';
|
2
|
+
export * from './date.schema';
|
3
|
+
export * from './description.schema';
|
4
|
+
export * from './email.schema';
|
5
|
+
export * from './field.schema';
|
6
|
+
export * from './phone.schema';
|
7
|
+
export * from './ssn.schema';
|
8
|
+
export * from './state.schema';
|
9
|
+
export * from './unix.schema';
|