Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/apps/accounts/src/settings/tabs/account/AccountTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { UserProfile, UserTraits } from '~/libs/core'
import { AccountRole } from './account-role'
import { SecuritySection } from './security'
import { UserAndPassword } from './user-and-pass'
import { MemberAddress } from './address'
import styles from './AccountTab.module.scss'

interface AccountTabProps {
Expand All @@ -20,6 +21,8 @@ const AccountTab: FC<AccountTabProps> = (props: AccountTabProps) => (

<UserAndPassword profile={props.profile} memberTraits={props.memberTraits} />

<MemberAddress profile={props.profile} />

<SecuritySection profile={props.profile} />
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@import '@libs/ui/styles/includes';

.container {
margin: $sp-8 0;

.content {
display: grid;
grid-template-columns: repeat(2, 1fr);
margin-bottom: 0;

@include ltelg {
grid-template-columns: 1fr;
}

>p {
max-width: 380px;
}

.form {
.formCTAs {
margin-top: $sp-4;
padding-top: $sp-4;
border-top: 2px solid $black-10;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { Dispatch, FC, SetStateAction, useState } from 'react'
import { toast } from 'react-toastify'
import { bind, trim } from 'lodash'
import classNames from 'classnames'

import {
Button,
Collapsible, InputSelect, InputText,
} from '~/libs/ui'
import {
CountryLookup,
updateMemberProfileAsync,
useCountryLookup,
UserProfile,
} from '~/libs/core'

import styles from './MemberAddress.module.scss'

interface MemberAddressProps {
profile: UserProfile
}

const MemberAddress: FC<MemberAddressProps> = (props: MemberAddressProps) => {
const countryLookup: CountryLookup[] | undefined
= useCountryLookup()

const [formValues, setFormValues]: [any, Dispatch<any>] = useState({
country: props.profile.homeCountryCode || props.profile.competitionCountryCode,
...props.profile.addresses ? props.profile.addresses[0] : {},
})

const [formErrors, setFormErrors]: [
{ [key: string]: string },
Dispatch<SetStateAction<{ [key: string]: string }>>
]
= useState<{ [key: string]: string }>({})

const [isSaving, setIsSaving]: [boolean, Dispatch<SetStateAction<boolean>>]
= useState<boolean>(false)

const [isFormChanged, setIsFormChanged]: [boolean, Dispatch<SetStateAction<boolean>>]
= useState<boolean>(false)

function handleFormValueChange(key: string, event: React.ChangeEvent<HTMLInputElement>): void {
const oldFormValues = { ...formValues }

setFormValues({
...oldFormValues,
[key]: event.target.value,
})
setIsFormChanged(true)
}

function handleFormAction(): void {
if (!trim(formValues.city)) {
setFormErrors({ city: 'Please select a city' })
return
}

if (!formValues.country) {
setFormErrors({ country: 'Please select a country' })
return
}

setIsSaving(true)

updateMemberProfileAsync(
props.profile.handle,
{
addresses: [{
city: formValues.city,
stateCode: formValues.stateCode,
streetAddr1: formValues.streetAddr1,
streetAddr2: formValues.streetAddr2,
zip: formValues.zip,
}],
competitionCountryCode: formValues.country,
homeCountryCode: formValues.country,
},
)
.then(() => {
toast.success('Your account has been updated.', { position: toast.POSITION.BOTTOM_RIGHT })
setFormErrors({})
})
.catch(() => {
toast.error('Something went wrong. Please try again.', { position: toast.POSITION.BOTTOM_RIGHT })
})
.finally(() => {
setIsFormChanged(false)
setIsSaving(false)
})
}

return (
<Collapsible
header={<h3>Address</h3>}
containerClass={styles.container}
contentClass={styles.content}
>
<p>
By keeping this information up to date we may surprise you with a cool T-shirt.
Sharing your contact details will never result in robocalls about health insurance plans or junk mail.
</p>

<form
className={classNames(styles.formWrap)}
>
<div className={styles.form}>
<InputText
name='address'
label='Address'
error={formErrors.streetAddr1}
placeholder='Your address'
dirty
tabIndex={0}
type='text'
onChange={bind(handleFormValueChange, this, 'streetAddr1')}
value={formValues.streetAddr1}
/>
<InputText
name='address2'
label='Address 2'
error={formErrors.streetAddr2}
placeholder='Your address continued'
dirty
tabIndex={0}
type='text'
onChange={bind(handleFormValueChange, this, 'streetAddr2')}
value={formValues.streetAddr2}
/>
<InputText
name='city'
label='City *'
error={formErrors.city}
placeholder='Which city do you live in?'
dirty
tabIndex={0}
type='text'
onChange={bind(handleFormValueChange, this, 'city')}
value={formValues.city}
/>
<InputText
name='state'
label='State'
error={formErrors.stateCode}
placeholder='State'
dirty
tabIndex={0}
type='text'
onChange={bind(handleFormValueChange, this, 'stateCode')}
value={formValues.stateCode}
/>
<InputText
name='zip'
label='Zip/Postal Code'
error={formErrors.zip}
placeholder='Your Zip or Postal Code'
dirty
tabIndex={0}
type='text'
onChange={bind(handleFormValueChange, this, 'zip')}
value={formValues.zip}
/>
<InputSelect
options={(countryLookup || []).map((cl: CountryLookup) => ({
label: cl.country,
value: cl.countryCode,
}))}
value={formValues.country}
onChange={bind(handleFormValueChange, this, 'country')}
name='country'
label='Country *'
error={formErrors.country}
placeholder='Select a Country'
dirty
/>

<div className={styles.formCTAs}>
<Button
secondary
size='lg'
label='Save Changes'
onClick={handleFormAction}
disabled={isSaving || !isFormChanged}
/>
</div>
</div>
</form>
</Collapsible>
)
}

export default MemberAddress
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as MemberAddress } from './MemberAddress'
1 change: 1 addition & 0 deletions src/libs/core/lib/profile/data-providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './useMemberMFAStatus'
export * from './useDiceIdConnection'
export * from './useMemberTraits'
export * from './useMemberDevicesLookup'
export * from './useCountryLookup'
10 changes: 10 additions & 0 deletions src/libs/core/lib/profile/data-providers/useCountryLookup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SWRResponse } from 'swr'
import useSWRImmutable from 'swr/immutable'

import { CountryLookup, countryLookupURL } from '~/libs/core'

export function useCountryLookup(): CountryLookup[] | undefined {
const { data }: SWRResponse = useSWRImmutable(countryLookupURL)

return data ? data.result?.content : undefined
}
9 changes: 9 additions & 0 deletions src/libs/core/lib/profile/modify-user-profile.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { TC_TRACKS } from './user-profile.model'

export interface UpdateProfileRequest {
addresses?: Array<{
city?: string
stateCode?: string
streetAddr1?: string
streetAddr2?: string
zip?: string
}>
competitionCountryCode?: string
homeCountryCode?: string
firstName?: string
lastName?: string
tracks?: TC_TRACKS[],
Expand Down
4 changes: 4 additions & 0 deletions src/libs/core/lib/profile/user-profile.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export type TC_TRACKS = 'DEVELOP' | 'DESIGN' | 'DATA_SCIENCE'
export interface UserProfile {
addresses?: Array<{
city?: string
stateCode?: string
streetAddr1?: string
streetAddr2?: string
zip?: string
}>
competitionCountryCode: string
createdAt: number
Expand Down