Skip to content

Commit c7e3700

Browse files
authored
Merge pull request #780 from topcoder-platform/accounts-app
Accounts app -> dev
2 parents 83aa2ae + 1988214 commit c7e3700

File tree

8 files changed

+248
-0
lines changed

8 files changed

+248
-0
lines changed

src/apps/accounts/src/settings/tabs/account/AccountTab.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { UserProfile, UserTraits } from '~/libs/core'
55
import { AccountRole } from './account-role'
66
import { SecuritySection } from './security'
77
import { UserAndPassword } from './user-and-pass'
8+
import { MemberAddress } from './address'
89
import styles from './AccountTab.module.scss'
910

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

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

24+
<MemberAddress profile={props.profile} />
25+
2326
<SecuritySection profile={props.profile} />
2427
</div>
2528
)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.container {
4+
margin: $sp-8 0;
5+
6+
.content {
7+
display: grid;
8+
grid-template-columns: repeat(2, 1fr);
9+
margin-bottom: 0;
10+
11+
@include ltelg {
12+
grid-template-columns: 1fr;
13+
}
14+
15+
>p {
16+
max-width: 380px;
17+
}
18+
19+
.form {
20+
.formCTAs {
21+
margin-top: $sp-4;
22+
padding-top: $sp-4;
23+
border-top: 2px solid $black-10;
24+
}
25+
}
26+
}
27+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import { Dispatch, FC, SetStateAction, useState } from 'react'
2+
import { toast } from 'react-toastify'
3+
import { bind, trim } from 'lodash'
4+
import classNames from 'classnames'
5+
6+
import {
7+
Button,
8+
Collapsible, InputSelect, InputText,
9+
} from '~/libs/ui'
10+
import {
11+
CountryLookup,
12+
updateMemberProfileAsync,
13+
useCountryLookup,
14+
UserProfile,
15+
} from '~/libs/core'
16+
17+
import styles from './MemberAddress.module.scss'
18+
19+
interface MemberAddressProps {
20+
profile: UserProfile
21+
}
22+
23+
const MemberAddress: FC<MemberAddressProps> = (props: MemberAddressProps) => {
24+
const countryLookup: CountryLookup[] | undefined
25+
= useCountryLookup()
26+
27+
const [formValues, setFormValues]: [any, Dispatch<any>] = useState({
28+
country: props.profile.homeCountryCode || props.profile.competitionCountryCode,
29+
...props.profile.addresses ? props.profile.addresses[0] : {},
30+
})
31+
32+
const [formErrors, setFormErrors]: [
33+
{ [key: string]: string },
34+
Dispatch<SetStateAction<{ [key: string]: string }>>
35+
]
36+
= useState<{ [key: string]: string }>({})
37+
38+
const [isSaving, setIsSaving]: [boolean, Dispatch<SetStateAction<boolean>>]
39+
= useState<boolean>(false)
40+
41+
const [isFormChanged, setIsFormChanged]: [boolean, Dispatch<SetStateAction<boolean>>]
42+
= useState<boolean>(false)
43+
44+
function handleFormValueChange(key: string, event: React.ChangeEvent<HTMLInputElement>): void {
45+
const oldFormValues = { ...formValues }
46+
47+
setFormValues({
48+
...oldFormValues,
49+
[key]: event.target.value,
50+
})
51+
setIsFormChanged(true)
52+
}
53+
54+
function handleFormAction(): void {
55+
if (!trim(formValues.city)) {
56+
setFormErrors({ city: 'Please select a city' })
57+
return
58+
}
59+
60+
if (!formValues.country) {
61+
setFormErrors({ country: 'Please select a country' })
62+
return
63+
}
64+
65+
setIsSaving(true)
66+
67+
updateMemberProfileAsync(
68+
props.profile.handle,
69+
{
70+
addresses: [{
71+
city: formValues.city,
72+
stateCode: formValues.stateCode,
73+
streetAddr1: formValues.streetAddr1,
74+
streetAddr2: formValues.streetAddr2,
75+
zip: formValues.zip,
76+
}],
77+
competitionCountryCode: formValues.country,
78+
homeCountryCode: formValues.country,
79+
},
80+
)
81+
.then(() => {
82+
toast.success('Your account has been updated.', { position: toast.POSITION.BOTTOM_RIGHT })
83+
setFormErrors({})
84+
})
85+
.catch(() => {
86+
toast.error('Something went wrong. Please try again.', { position: toast.POSITION.BOTTOM_RIGHT })
87+
})
88+
.finally(() => {
89+
setIsFormChanged(false)
90+
setIsSaving(false)
91+
})
92+
}
93+
94+
return (
95+
<Collapsible
96+
header={<h3>Address</h3>}
97+
containerClass={styles.container}
98+
contentClass={styles.content}
99+
>
100+
<p>
101+
By keeping this information up to date we may surprise you with a cool T-shirt.
102+
Sharing your contact details will never result in robocalls about health insurance plans or junk mail.
103+
</p>
104+
105+
<form
106+
className={classNames(styles.formWrap)}
107+
>
108+
<div className={styles.form}>
109+
<InputText
110+
name='address'
111+
label='Address'
112+
error={formErrors.streetAddr1}
113+
placeholder='Your address'
114+
dirty
115+
tabIndex={0}
116+
type='text'
117+
onChange={bind(handleFormValueChange, this, 'streetAddr1')}
118+
value={formValues.streetAddr1}
119+
/>
120+
<InputText
121+
name='address2'
122+
label='Address 2'
123+
error={formErrors.streetAddr2}
124+
placeholder='Your address continued'
125+
dirty
126+
tabIndex={0}
127+
type='text'
128+
onChange={bind(handleFormValueChange, this, 'streetAddr2')}
129+
value={formValues.streetAddr2}
130+
/>
131+
<InputText
132+
name='city'
133+
label='City *'
134+
error={formErrors.city}
135+
placeholder='Which city do you live in?'
136+
dirty
137+
tabIndex={0}
138+
type='text'
139+
onChange={bind(handleFormValueChange, this, 'city')}
140+
value={formValues.city}
141+
/>
142+
<InputText
143+
name='state'
144+
label='State'
145+
error={formErrors.stateCode}
146+
placeholder='State'
147+
dirty
148+
tabIndex={0}
149+
type='text'
150+
onChange={bind(handleFormValueChange, this, 'stateCode')}
151+
value={formValues.stateCode}
152+
/>
153+
<InputText
154+
name='zip'
155+
label='Zip/Postal Code'
156+
error={formErrors.zip}
157+
placeholder='Your Zip or Postal Code'
158+
dirty
159+
tabIndex={0}
160+
type='text'
161+
onChange={bind(handleFormValueChange, this, 'zip')}
162+
value={formValues.zip}
163+
/>
164+
<InputSelect
165+
options={(countryLookup || []).map((cl: CountryLookup) => ({
166+
label: cl.country,
167+
value: cl.countryCode,
168+
}))}
169+
value={formValues.country}
170+
onChange={bind(handleFormValueChange, this, 'country')}
171+
name='country'
172+
label='Country *'
173+
error={formErrors.country}
174+
placeholder='Select a Country'
175+
dirty
176+
/>
177+
178+
<div className={styles.formCTAs}>
179+
<Button
180+
secondary
181+
size='lg'
182+
label='Save Changes'
183+
onClick={handleFormAction}
184+
disabled={isSaving || !isFormChanged}
185+
/>
186+
</div>
187+
</div>
188+
</form>
189+
</Collapsible>
190+
)
191+
}
192+
193+
export default MemberAddress
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as MemberAddress } from './MemberAddress'

src/libs/core/lib/profile/data-providers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './useMemberMFAStatus'
1010
export * from './useDiceIdConnection'
1111
export * from './useMemberTraits'
1212
export * from './useMemberDevicesLookup'
13+
export * from './useCountryLookup'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { SWRResponse } from 'swr'
2+
import useSWRImmutable from 'swr/immutable'
3+
4+
import { CountryLookup, countryLookupURL } from '~/libs/core'
5+
6+
export function useCountryLookup(): CountryLookup[] | undefined {
7+
const { data }: SWRResponse = useSWRImmutable(countryLookupURL)
8+
9+
return data ? data.result?.content : undefined
10+
}

src/libs/core/lib/profile/modify-user-profile.model.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { TC_TRACKS } from './user-profile.model'
22

33
export interface UpdateProfileRequest {
4+
addresses?: Array<{
5+
city?: string
6+
stateCode?: string
7+
streetAddr1?: string
8+
streetAddr2?: string
9+
zip?: string
10+
}>
11+
competitionCountryCode?: string
12+
homeCountryCode?: string
413
firstName?: string
514
lastName?: string
615
tracks?: TC_TRACKS[],

src/libs/core/lib/profile/user-profile.model.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export type TC_TRACKS = 'DEVELOP' | 'DESIGN' | 'DATA_SCIENCE'
55
export interface UserProfile {
66
addresses?: Array<{
77
city?: string
8+
stateCode?: string
9+
streetAddr1?: string
10+
streetAddr2?: string
11+
zip?: string
812
}>
913
competitionCountryCode: string
1014
createdAt: number

0 commit comments

Comments
 (0)