diff --git a/src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.tsx b/src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.tsx index b5dbbdd6b..4de9e7c0d 100644 --- a/src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.tsx +++ b/src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.tsx @@ -379,6 +379,7 @@ const ServiceProvider: FC = (props: ServiceProviderProps) type='text' error={formErrors.serviceProviderName} dirty + forceUpdateValue />
{!isEditMode && } diff --git a/src/apps/accounts/src/settings/tabs/tools/software/Software.tsx b/src/apps/accounts/src/settings/tabs/tools/software/Software.tsx index 044786715..340b48bfd 100644 --- a/src/apps/accounts/src/settings/tabs/tools/software/Software.tsx +++ b/src/apps/accounts/src/settings/tabs/tools/software/Software.tsx @@ -313,6 +313,7 @@ const Software: FC = (props: SoftwareProps) => { name='softwareName' label='Software Name *' value={selectedSoftwareName} + forceUpdateValue onChange={handleSoftwareNameChange} placeholder='Type here the Software Name' tabIndex={0} diff --git a/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.tsx b/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.tsx index f41734988..491018f9b 100644 --- a/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.tsx +++ b/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.tsx @@ -297,6 +297,7 @@ const Subscriptions: FC = (props: SubscriptionsProps) => { type='text' error={formErrors.subscriptionName} dirty + forceUpdateValue />
{!isEditMode && } diff --git a/src/apps/admin/src/lib/utils/others.ts b/src/apps/admin/src/lib/utils/others.ts index 8c5d90738..d06b6c8df 100644 --- a/src/apps/admin/src/lib/utils/others.ts +++ b/src/apps/admin/src/lib/utils/others.ts @@ -39,7 +39,8 @@ export function validateS3URI( const parsedKey = key ?? undefined const isValid = Boolean(parsedBucket) && region === EnvironmentConfig.ADMIN.AWS_REGION - && parsedBucket === EnvironmentConfig.ADMIN.AWS_DMZ_BUCKET + && (parsedBucket === EnvironmentConfig.ADMIN.AWS_CLEAN_BUCKET + || parsedBucket === EnvironmentConfig.ADMIN.AWS_QUARANTINE_BUCKET) return { bucket: parsedBucket, diff --git a/src/apps/profiles/src/member-profile/local-info/MemberLocalInfo.module.scss b/src/apps/profiles/src/member-profile/local-info/MemberLocalInfo.module.scss index b3a22c8db..2d413c308 100644 --- a/src/apps/profiles/src/member-profile/local-info/MemberLocalInfo.module.scss +++ b/src/apps/profiles/src/member-profile/local-info/MemberLocalInfo.module.scss @@ -19,4 +19,16 @@ margin-left: auto; } } + + .tooltip { + display: flex; + margin-left: $sp-2; + position: relative; + flex-wrap: wrap; + + svg:global(.tooltip-icon) { + @include icon-xl; + cursor: pointer; + } + } } \ No newline at end of file diff --git a/src/apps/profiles/src/member-profile/local-info/MemberLocalInfo.tsx b/src/apps/profiles/src/member-profile/local-info/MemberLocalInfo.tsx index 0d14d9be8..4fe48e5a8 100644 --- a/src/apps/profiles/src/member-profile/local-info/MemberLocalInfo.tsx +++ b/src/apps/profiles/src/member-profile/local-info/MemberLocalInfo.tsx @@ -1,9 +1,10 @@ -import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' +import { Dispatch, FC, SetStateAction, useContext, useMemo, useState } from 'react' +import { trim } from 'lodash' import cityTimezones from 'city-timezones' import moment from 'moment-timezone' -import { useCountryName, UserProfile } from '~/libs/core' -import { IconSolid } from '~/libs/ui' +import { profileContext, ProfileContextData, useCountryName, UserProfile, UserRole } from '~/libs/core' +import { IconOutline, IconSolid, Tooltip } from '~/libs/ui' import { EditMemberPropertyBtn } from '../../components' @@ -21,7 +22,9 @@ const MemberLocalInfo: FC = (props: MemberLocalInfoProps) const memberCountry: string | undefined = useCountryName(props.profile?.homeCountryCode || props.profile?.competitionCountryCode) - const city: string | undefined = props.profile?.addresses?.[0]?.city + const address = props.profile?.addresses?.[0] + + const city: string | undefined = address?.city const memberCityTimezone: string | undefined = useMemo(() => { if (!city) { @@ -42,6 +45,10 @@ const MemberLocalInfo: FC = (props: MemberLocalInfoProps) const canEdit: boolean = props.authProfile?.handle === props.profile.handle + const { profile }: ProfileContextData = useContext(profileContext) + const isAdminOrTM = profile?.roles?.includes(UserRole.administrator) + || profile?.roles?.includes(UserRole.talentManager) + const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) @@ -76,11 +83,56 @@ const MemberLocalInfo: FC = (props: MemberLocalInfoProps) return 'Unknown location' }, [city, memberCountry]) + const hasDetailedAddress: boolean = useMemo(() => { + if (!address) return false + return !!( + trim(address.streetAddr1) + || trim(address.streetAddr2) + || trim(address.stateCode) + || trim(address.zip) + ) + }, [address]) + + const canSeeDetailedAddressIcon = canEdit || isAdminOrTM + + const tooltipContent = useMemo(() => { + if (!hasDetailedAddress) return undefined + + const addressLine = [ + trim(address?.streetAddr1), + trim(address?.streetAddr2), + trim(address?.city), + trim(address?.stateCode), + ].filter(Boolean) + .join(', ') + + const postalCode = trim(address?.zip) + + if (!postalCode) return addressLine + + return ( + <> +
{addressLine}
+
+ Zip/Postal code - + {postalCode} +
+ + ) + }, [address, hasDetailedAddress]) + return (
{locationDisplay} + {hasDetailedAddress && canSeeDetailedAddressIcon && ( +
+ + + +
+ )} { canEdit && ( void - onSave: () => void - profile: UserProfile + onClose: () => void + onSave: () => void + profile: UserProfile } const OMIT_ADDRESS_KEYS_ON_UPDATE = [ @@ -26,57 +26,92 @@ const ModifyLocationModal: FC = (props: ModifyLocation const countryLookup: CountryLookup[] | undefined = useCountryLookup() - const contries = useMemo(() => (countryLookup || []).map((cl: CountryLookup) => ({ - label: cl.country, - value: cl.countryCode, - })) - .sort((a, b) => a.label.localeCompare(b.label)), [countryLookup]) + const countries = useMemo( + () => (countryLookup || []).map((cl: CountryLookup) => ({ + label: cl.country, + value: cl.countryCode, + })) + .sort((a, b) => a.label.localeCompare(b.label)), + [countryLookup], + ) + + const existingAddress = props.profile.addresses ? props.profile.addresses[0] : {} const [formValues, setFormValues]: [any, Dispatch] = useState({ - country: props.profile.homeCountryCode || props.profile.competitionCountryCode, - ...props.profile.addresses ? props.profile.addresses[0] : {}, + country: props.profile.homeCountryCode, + ...existingAddress, }) - const [formSaveError, setFormSaveError]: [ - string | undefined, - Dispatch> - ] = useState() + const [formErrors, setFormErrors]: [ + { [key: string]: string }, + Dispatch> + ] = useState({}) - const [isSaving, setIsSaving]: [boolean, Dispatch>] - = useState(false) + const [formSaveError, setFormSaveError] = useState() + const [isSaving, setIsSaving] = useState(false) + const [isFormChanged, setIsFormChanged] = useState(false) - const [isFormChanged, setIsFormChanged]: [boolean, Dispatch>] - = useState(false) + const handleFormValueChange = useCallback( + (key: string) => (event: React.ChangeEvent): void => { + const value: string = event.target.value - function handleFormValueChange(key: string, event: React.ChangeEvent): void { - const oldFormValues = { ...formValues } + setFormValues({ + ...formValues, + [key]: value, + }) - setFormValues({ - ...oldFormValues, - [key]: event.target.value, - }) - setIsFormChanged(true) + setIsFormChanged(true) + + setFormErrors(prev => { + if (!prev[key]) return prev + const next = { ...prev } + delete next[key] + return next + }) + }, + [formValues], + ) + + function validate(): boolean { + const nextErrors: { [key: string]: string } = {} + + if (!trim(formValues.city)) nextErrors.city = 'Please select a city' + if (!formValues.country) nextErrors.country = 'Please select a country' + + setFormErrors(nextErrors) + return Object.keys(nextErrors).length === 0 } function handleLocationSave(): void { - updateMemberProfileAsync( - props.profile.handle, - { - addresses: [{ - ...props.profile.addresses ? omit(props.profile.addresses[0], OMIT_ADDRESS_KEYS_ON_UPDATE) : {}, + if (!validate()) return + + setIsSaving(true) + + const baseAddressPayload = props.profile.addresses + ? omit(props.profile.addresses[0], OMIT_ADDRESS_KEYS_ON_UPDATE) + : {} + + updateMemberProfileAsync(props.profile.handle, { + addresses: [ + { + ...baseAddressPayload, city: trim(formValues.city), - }], - competitionCountryCode: formValues.country, - homeCountryCode: formValues.country, - }, - ) + stateCode: trim(formValues.stateCode), + streetAddr1: trim(formValues.streetAddr1), + streetAddr2: trim(formValues.streetAddr2), + zip: trim(formValues.zip), + }, + ], + competitionCountryCode: formValues.country, + homeCountryCode: formValues.country, + }) .then(() => { toast.success('Your location has been updated.', { position: toast.POSITION.BOTTOM_RIGHT }) props.onSave() }) .catch((error: any) => { toast.error('Something went wrong. Please try again.', { position: toast.POSITION.BOTTOM_RIGHT }) - setFormSaveError(error.message || error) + setFormSaveError(error?.message || error) }) .finally(() => { setIsFormChanged(false) @@ -111,23 +146,77 @@ const ModifyLocationModal: FC = (props: ModifyLocation )} >

Provide details on your location.

+
+ + + + + + + + + diff --git a/src/apps/profiles/src/member-profile/phones/ModifyPhonesModal/ModifyPhonesModal.tsx b/src/apps/profiles/src/member-profile/phones/ModifyPhonesModal/ModifyPhonesModal.tsx index cb7f5b57f..97aa8d51a 100644 --- a/src/apps/profiles/src/member-profile/phones/ModifyPhonesModal/ModifyPhonesModal.tsx +++ b/src/apps/profiles/src/member-profile/phones/ModifyPhonesModal/ModifyPhonesModal.tsx @@ -91,8 +91,13 @@ const ModifyPhonesModal: FC = (props: ModifyPhonesModalP toast.success('Phone numbers updated successfully.', { position: toast.POSITION.BOTTOM_RIGHT }) props.onSave() }) - .catch(() => { - toast.error('Failed to update phone numbers.', { position: toast.POSITION.BOTTOM_RIGHT }) + .catch(error => { + const apiMessage + = error?.message || 'Failed to update phone numbers.' + + toast.error(apiMessage, { + position: toast.POSITION.BOTTOM_RIGHT, + }) setIsSaving(false) }) } diff --git a/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx b/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx index e5699b7b5..ccfed187a 100644 --- a/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx +++ b/src/apps/profiles/src/member-profile/profile-header/OpenForGigsModifyModal/OpenForGigsModifyModal.tsx @@ -132,7 +132,7 @@ const OpenForGigsModifyModal: FC = (props: OpenForG diff --git a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx index 5bf7ee428..6d204f7d2 100644 --- a/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx +++ b/src/apps/profiles/src/member-profile/tc-achievements/MemberTCAchievements.tsx @@ -47,7 +47,7 @@ const MemberTCAchievements: FC = (props: MemberTCAchi /> ), [memberStats, props.profile, tcoQualifications, tcoTrips, tcoWins]) - if (!memberStats?.challenges && !tcoWins && !tcoQualifications) { + if (!memberStats?.challenges && !tcoWins && !tcoQualifications && !memberBadges?.rows.length) { return <> } diff --git a/src/libs/ui/lib/components/modals/confirm/ConfirmModal.tsx b/src/libs/ui/lib/components/modals/confirm/ConfirmModal.tsx index 739a5e913..8ad0c4a9d 100644 --- a/src/libs/ui/lib/components/modals/confirm/ConfirmModal.tsx +++ b/src/libs/ui/lib/components/modals/confirm/ConfirmModal.tsx @@ -28,7 +28,7 @@ const ConfirmModal: FC = (props: ConfirmModalProps) => { props.onClose?.() } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isLoading]) + }, [isLoading, props.open]) return (