From d54bd280d9805daf3b17add86265eba2a4a906f5 Mon Sep 17 00:00:00 2001 From: camilovegag Date: Thu, 8 Feb 2024 11:17:10 -0500 Subject: [PATCH 1/5] First checkout iteration --- .../components/checkbox/Checkbox.styled.tsx | 53 +++++++++++++++++++ .../src/components/checkbox/Checkbox.tsx | 41 ++++++++++++++ .../berlin/src/components/checkbox/index.ts | 1 + packages/berlin/src/pages/Account.tsx | 5 +- 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 packages/berlin/src/components/checkbox/Checkbox.styled.tsx create mode 100644 packages/berlin/src/components/checkbox/Checkbox.tsx create mode 100644 packages/berlin/src/components/checkbox/index.ts diff --git a/packages/berlin/src/components/checkbox/Checkbox.styled.tsx b/packages/berlin/src/components/checkbox/Checkbox.styled.tsx new file mode 100644 index 00000000..be3146cf --- /dev/null +++ b/packages/berlin/src/components/checkbox/Checkbox.styled.tsx @@ -0,0 +1,53 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + display: grid; + grid-template-columns: 1.5rem 1fr; + column-gap: 0.5rem; +`; + +export const CheckboxContainer = styled.div` + align-items: center; + display: flex; + height: 1.5rem; + justify-content: center; + width: 1.5rem; +`; + +export const HiddenCheckbox = styled.input` + border: 0; + clip-path: inset(50%); + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; + width: 1px; + + &:focus-within { + outline: 2px solid red; + } +`; + +export const Icon = styled.svg` + fill: none; + stroke: var(--color-black); + stroke-width: 3px; +`; + +export const StyledCheckbox = styled.div<{ $checked: boolean }>` + align-items: center; + border-radius: 3px; + border: 1.25px solid var(--color-black); + cursor: pointer; + display: flex; + height: 1.125rem; + justify-content: center; + width: 1.125rem; + + ${Icon} { + visibility: ${(props) => (props.$checked ? 'visible' : 'hidden')}; + } +`; diff --git a/packages/berlin/src/components/checkbox/Checkbox.tsx b/packages/berlin/src/components/checkbox/Checkbox.tsx new file mode 100644 index 00000000..22c01294 --- /dev/null +++ b/packages/berlin/src/components/checkbox/Checkbox.tsx @@ -0,0 +1,41 @@ +import { useState } from 'react'; +import Label from '../typography/Label'; +import { + CheckboxContainer, + Container, + HiddenCheckbox, + Icon, + StyledCheckbox, +} from './Checkbox.styled'; + +type CheckboxProps = { + text: string; + $required?: boolean; +}; + +function Checkbox({ text, $required }: CheckboxProps) { + const [checked, setChecked] = useState(false); + return ( + + + + setChecked(!checked)}> + + + + + + + + ); +} + +export default Checkbox; diff --git a/packages/berlin/src/components/checkbox/index.ts b/packages/berlin/src/components/checkbox/index.ts new file mode 100644 index 00000000..dd6385d4 --- /dev/null +++ b/packages/berlin/src/components/checkbox/index.ts @@ -0,0 +1 @@ +export { default } from './Checkbox'; diff --git a/packages/berlin/src/pages/Account.tsx b/packages/berlin/src/pages/Account.tsx index 7a898fc5..a52c3f86 100644 --- a/packages/berlin/src/pages/Account.tsx +++ b/packages/berlin/src/pages/Account.tsx @@ -34,6 +34,7 @@ import { formatGroups } from '../utils/formatGroups'; // Store import { useAppStore } from '../store'; +import Checkbox from '../components/checkbox'; const ACADEMIC_CREDENTIALS = ['Bachelors', 'Masters', 'PhD', 'JD', 'None']; @@ -123,7 +124,7 @@ function Account() { field: '', }, ], - } as UserAttributes + } as UserAttributes, ), }; @@ -381,6 +382,8 @@ function AccountForm({ icon={{ src: `/icons/add-${theme}.svg`, alt: 'Add icon' }} /> + + From c9421a6a22f65a7167285b716abdc7e8e5bd97c8 Mon Sep 17 00:00:00 2001 From: Diego Alzate Date: Thu, 8 Feb 2024 12:38:15 -0500 Subject: [PATCH 2/5] Add name and email notification fields to user type and update checkbox component --- packages/api/src/types/UserType.ts | 4 ++ packages/api/src/updateUserData.ts | 4 ++ .../src/components/checkbox/Checkbox.tsx | 55 ++++++++++--------- packages/berlin/src/pages/Account.tsx | 44 ++++++++++----- 4 files changed, 69 insertions(+), 38 deletions(-) diff --git a/packages/api/src/types/UserType.ts b/packages/api/src/types/UserType.ts index 81181086..641845d5 100644 --- a/packages/api/src/types/UserType.ts +++ b/packages/api/src/types/UserType.ts @@ -2,6 +2,8 @@ export type GetUserResponse = { id: string; username: string | null; email: string | null; + name: string | null; + emailNotification: boolean; createdAt: string; updatedAt: string; }; @@ -9,6 +11,8 @@ export type GetUserResponse = { export type PutUserRequest = { userId: string; username: string; + name?: string; + emailNotification?: boolean; email?: string; groupIds: string[]; userAttributes: Record; diff --git a/packages/api/src/updateUserData.ts b/packages/api/src/updateUserData.ts index 774855ca..0b7934f9 100644 --- a/packages/api/src/updateUserData.ts +++ b/packages/api/src/updateUserData.ts @@ -3,6 +3,8 @@ import { PutUserRequest, GetUserResponse } from './types/UserType'; async function updateUserData({ userId, username, + emailNotification, + name, email, groupIds, userAttributes, @@ -19,6 +21,8 @@ async function updateUserData({ username, email, userAttributes, + emailNotification, + name, }), }); diff --git a/packages/berlin/src/components/checkbox/Checkbox.tsx b/packages/berlin/src/components/checkbox/Checkbox.tsx index 22c01294..25e39091 100644 --- a/packages/berlin/src/components/checkbox/Checkbox.tsx +++ b/packages/berlin/src/components/checkbox/Checkbox.tsx @@ -1,4 +1,3 @@ -import { useState } from 'react'; import Label from '../typography/Label'; import { CheckboxContainer, @@ -7,35 +6,41 @@ import { Icon, StyledCheckbox, } from './Checkbox.styled'; +import React from 'react'; type CheckboxProps = { text: string; $required?: boolean; + onClick?: () => void; + value: boolean; }; -function Checkbox({ text, $required }: CheckboxProps) { - const [checked, setChecked] = useState(false); - return ( - - - - setChecked(!checked)}> - - - - - - - - ); -} +const Checkbox = React.forwardRef( + ({ text, $required, onClick, value }, ref) => { + return ( + + + {}} + /> + + + + + + + + + ); + }, +); export default Checkbox; diff --git a/packages/berlin/src/pages/Account.tsx b/packages/berlin/src/pages/Account.tsx index a52c3f86..a6c7b6a2 100644 --- a/packages/berlin/src/pages/Account.tsx +++ b/packages/berlin/src/pages/Account.tsx @@ -45,7 +45,6 @@ type CredentialsGroup = { }[]; type UserAttributes = { - name: string; institution: string; role: string; publications: { value: string }[]; @@ -55,6 +54,8 @@ type UserAttributes = { type InitialUser = { username: string; + name: string; + emailNotification: boolean; email: string; group: string; userAttributes: UserAttributes | undefined; @@ -87,9 +88,11 @@ function Account() { enabled: !!user?.id, }); - const initialUser = { + const initialUser: InitialUser = { username: user?.username || '', + name: user?.name || '', email: user?.email || '', + emailNotification: user?.emailNotification ?? true, group: (userGroups && userGroups[0]?.id) || '', userAttributes: userAttributes?.reduce( (acc, curr) => { @@ -112,7 +115,6 @@ function Account() { } }, { - name: '', institution: '', role: '', publications: [{ value: '' }], @@ -208,6 +210,8 @@ function AccountForm({ userId: user.id, username: value.username, email: value.email, + emailNotification: value.emailNotification, + name: value.name, groupIds: [value.group], userAttributes: { ...value.userAttributes, @@ -225,6 +229,16 @@ function AccountForm({ } }; + const credentialOnChange = (value: string, i: number) => { + if (value === 'None') { + setValue(`userAttributes.credentialsGroup.${i}.institution`, 'None'); + setValue(`userAttributes.credentialsGroup.${i}.field`, 'None'); + // Manually trigger validation + trigger(`userAttributes.credentialsGroup.${i}.institution`); + trigger(`userAttributes.credentialsGroup.${i}.field`); + } + }; + return ( Complete your registration @@ -237,7 +251,7 @@ function AccountForm({ {...register('username', { required: 'Username is required', minLength: 3 })} errors={errors.username ? [errors.username.message ?? ''] : []} /> - + { field.onChange(value); // Check if selected credential is 'None' and set default values accordingly - if (value === 'None') { - setValue(`userAttributes.credentialsGroup.${i}.institution`, 'None'); - setValue(`userAttributes.credentialsGroup.${i}.field`, 'None'); - // Manually trigger validation - trigger(`userAttributes.credentialsGroup.${i}.institution`); - trigger(`userAttributes.credentialsGroup.${i}.field`); - } + credentialOnChange(value, i); }} onBlur={field.onBlur} value={field.value} @@ -382,8 +390,18 @@ function AccountForm({ icon={{ src: `/icons/add-${theme}.svg`, alt: 'Add icon' }} /> - - + ( + field.onChange(!field.value)} + value={field.value} + /> + )} + /> From 0f91de20b9731ae06619129dbfcfe96ec9de843e Mon Sep 17 00:00:00 2001 From: camilovegag Date: Thu, 8 Feb 2024 14:02:33 -0500 Subject: [PATCH 3/5] Create textarea component --- .../components/textarea/Textarea.styled.tsx | 15 +++++++++++++ .../src/components/textarea/Textarea.tsx | 22 +++++++++++++++++++ .../berlin/src/components/textarea/index.ts | 1 + 3 files changed, 38 insertions(+) create mode 100644 packages/berlin/src/components/textarea/Textarea.styled.tsx create mode 100644 packages/berlin/src/components/textarea/Textarea.tsx create mode 100644 packages/berlin/src/components/textarea/index.ts diff --git a/packages/berlin/src/components/textarea/Textarea.styled.tsx b/packages/berlin/src/components/textarea/Textarea.styled.tsx new file mode 100644 index 00000000..7bd7bf20 --- /dev/null +++ b/packages/berlin/src/components/textarea/Textarea.styled.tsx @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +export const StyledTextarea = styled.textarea` + background-color: var(--color-white); + border-radius: 0.5rem; + border: 1px solid var(--color-black); + height: 12rem; + padding: 0.75rem 1rem; + resize: none; + width: 100%; + + &:disabled { + cursor: not-allowed; + } +`; diff --git a/packages/berlin/src/components/textarea/Textarea.tsx b/packages/berlin/src/components/textarea/Textarea.tsx new file mode 100644 index 00000000..a505fe76 --- /dev/null +++ b/packages/berlin/src/components/textarea/Textarea.tsx @@ -0,0 +1,22 @@ +// Components +import { FlexColumn } from '../containers/FlexColum.styled'; +import Label from '../typography/Label'; + +// Styled Components +import { StyledTextarea } from './Textarea.styled'; + +type TextareaProps = { + label: string; + $required?: boolean; +}; + +function Textarea({ label, $required, ...props }: TextareaProps) { + return ( + + {label && } + + + ); +} + +export default Textarea; diff --git a/packages/berlin/src/components/textarea/index.ts b/packages/berlin/src/components/textarea/index.ts new file mode 100644 index 00000000..20e92f83 --- /dev/null +++ b/packages/berlin/src/components/textarea/index.ts @@ -0,0 +1 @@ +export { default } from './Textarea'; From 5a44fb7dedcf0ddc17d4e54ea532386a98f98d0d Mon Sep 17 00:00:00 2001 From: camilovegag Date: Thu, 8 Feb 2024 14:03:11 -0500 Subject: [PATCH 4/5] Organize imports --- packages/berlin/src/pages/Account.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/berlin/src/pages/Account.tsx b/packages/berlin/src/pages/Account.tsx index a6c7b6a2..99e4fad7 100644 --- a/packages/berlin/src/pages/Account.tsx +++ b/packages/berlin/src/pages/Account.tsx @@ -18,10 +18,12 @@ import { FlexColumn } from '../components/containers/FlexColum.styled'; import { FlexRow } from '../components/containers/FlexRow.styled'; import { Title } from '../components/typography/Title.styled'; import Button from '../components/button'; +import Checkbox from '../components/checkbox'; import IconButton from '../components/iconButton'; import Input from '../components/input'; import Label from '../components/typography/Label'; import Select from '../components/select'; +import Textarea from '../components/textarea'; // Hooks import useUser from '../hooks/useUser'; @@ -34,7 +36,6 @@ import { formatGroups } from '../utils/formatGroups'; // Store import { useAppStore } from '../store'; -import Checkbox from '../components/checkbox'; const ACADEMIC_CREDENTIALS = ['Bachelors', 'Masters', 'PhD', 'JD', 'None']; From 6f0cee742a0dc37690a1e4770ad5c7c5915685d6 Mon Sep 17 00:00:00 2001 From: Diego Alzate Date: Thu, 8 Feb 2024 15:42:35 -0500 Subject: [PATCH 5/5] Refactor Textarea component and add TextAreaInput component --- .../src/components/textarea/Textarea.tsx | 27 ++++--- packages/berlin/src/pages/Register.tsx | 75 ++++++++++++++++++- 2 files changed, 88 insertions(+), 14 deletions(-) diff --git a/packages/berlin/src/components/textarea/Textarea.tsx b/packages/berlin/src/components/textarea/Textarea.tsx index a505fe76..00bdb602 100644 --- a/packages/berlin/src/components/textarea/Textarea.tsx +++ b/packages/berlin/src/components/textarea/Textarea.tsx @@ -1,22 +1,29 @@ // Components +import React from 'react'; import { FlexColumn } from '../containers/FlexColum.styled'; import Label from '../typography/Label'; // Styled Components import { StyledTextarea } from './Textarea.styled'; -type TextareaProps = { +interface TextareaProps extends React.InputHTMLAttributes { label: string; $required?: boolean; -}; - -function Textarea({ label, $required, ...props }: TextareaProps) { - return ( - - {label && } - - - ); } +const Textarea = React.forwardRef( + ({ label, $required, ...props }, ref) => { + return ( + + {label && ( + + )} + + + ); + }, +); + export default Textarea; diff --git a/packages/berlin/src/pages/Register.tsx b/packages/berlin/src/pages/Register.tsx index dcc8ded4..6799f4bf 100644 --- a/packages/berlin/src/pages/Register.tsx +++ b/packages/berlin/src/pages/Register.tsx @@ -30,6 +30,7 @@ import Select from '../components/select'; import { Error } from '../components/typography/Error.styled'; import CharacterCounter from '../components/typography/CharacterCount.styled'; import { z } from 'zod'; +import Textarea from '../components/textarea'; function Register() { const { user, isLoading } = useUser(); @@ -95,7 +96,7 @@ function RegisterForm(props: { acc[curr.registrationFieldId] = curr.value; return acc; }, - {} as Record + {} as Record, ), mode: 'onBlur', }); @@ -187,7 +188,7 @@ function FormField({ id: string; name: string; required: boolean | null; - type: 'TEXT' | 'SELECT' | 'NUMBER' | 'DATE' | 'BOOLEAN'; + type: 'TEXT' | 'SELECT' | 'NUMBER' | 'DATE' | 'BOOLEAN' | 'TEXTAREA'; options: RegistrationFieldOption[]; disabled: boolean; register: UseFormRegister>; @@ -221,6 +222,18 @@ function FormField({ control={control} /> ); + case 'TEXTAREA': + return ( + + ); default: return null; } @@ -262,11 +275,66 @@ function TextInput(props: { if (props.characterLimit > 0 && value.length > props.characterLimit) { return `Character count of ${charCount} exceeds character limit of ${props.characterLimit}`; } - // validate required + + const v = z.string().min(1, 'Value is required').safeParse(value); + + if (v.success) { + return true; + } + + return v.error.errors[0].message; + }, + })} + disabled={props.disabled} + onChange={handleInputChange} + /> + {props.errors?.[props.id] ? ( + {props.errors?.[props.id]?.message} + ) : ( + props.characterLimit > 0 && ( + + ) + )} + + ); +} + +function TextAreaInput(props: { + id: string; + name: string; + required: boolean | null; + characterLimit: number; + disabled: boolean; + register: UseFormRegister>; + errors: FieldErrors>; +}) { + const [charCount, setCharCount] = useState(0); + + const handleInputChange = (event: React.ChangeEvent) => { + const inputValue = event.target.value; + setCharCount(inputValue.length); + props.register(props.id, { + value: inputValue, + }); + }; + + return ( + +