From fde795d83b5947af9a110529d8705388f283f144 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 7 Jun 2023 14:56:12 +0300 Subject: [PATCH 1/2] MP-35, MP-36, MP-37 inits --- src/apps/accounts/src/lib/assets/index.ts | 1 + .../assets/tools/Financial Institution.svg | 6 + .../tools/Internet Service Provider.svg | 6 + .../src/lib/assets/tools/Mobile Carrier.svg | 6 + .../src/lib/assets/tools/Television.svg | 16 + .../accounts/src/lib/assets/tools/console.svg | 1 + .../accounts/src/lib/assets/tools/desktop.svg | 4 + .../accounts/src/lib/assets/tools/index.ts | 31 ++ .../accounts/src/lib/assets/tools/laptop.svg | 1 + .../src/lib/assets/tools/other_device.svg | 9 + .../assets/tools/other_service_provider.svg | 17 + .../src/lib/assets/tools/smartphone.svg | 4 + .../src/lib/assets/tools/software.svg | 8 + .../src/lib/assets/tools/subscription.svg | 3 + .../accounts/src/lib/assets/tools/tablet.svg | 1 + .../src/lib/assets/tools/wearable.svg | 1 + .../setting-section/SettingSection.tsx | 2 +- .../src/settings/tabs/AccountSettingsTabs.tsx | 14 +- .../DiceSetupModal.module.scss | 6 +- .../dice-setup-modal/DiceSetupModal.tsx | 18 +- .../config/account-settings-tabs-config.ts | 13 +- .../settings/tabs/tools/ToolsTab.module.scss | 12 + .../src/settings/tabs/tools/ToolsTab.tsx | 42 +++ .../tabs/tools/devices/Devices.module.scss | 45 +++ .../settings/tabs/tools/devices/Devices.tsx | 97 +++++ .../src/settings/tabs/tools/devices/index.ts | 1 + .../accounts/src/settings/tabs/tools/index.ts | 1 + .../ServiceProvider.module.scss | 60 ++++ .../service-provider/ServiceProvider.tsx | 337 ++++++++++++++++++ .../tabs/tools/service-provider/index.ts | 1 + .../service-provider-types.config.ts | 23 ++ .../tabs/tools/software/Software.module.scss | 60 ++++ .../settings/tabs/tools/software/Software.tsx | 316 ++++++++++++++++ .../src/settings/tabs/tools/software/index.ts | 1 + .../tools/software/software-types.config.ts | 23 ++ .../subscriptions/Subscriptions.module.scss | 60 ++++ .../tools/subscriptions/Subscriptions.tsx | 291 +++++++++++++++ .../tabs/tools/subscriptions/index.ts | 1 + 38 files changed, 1525 insertions(+), 14 deletions(-) create mode 100644 src/apps/accounts/src/lib/assets/tools/Financial Institution.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/Internet Service Provider.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/Mobile Carrier.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/Television.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/console.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/desktop.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/index.ts create mode 100644 src/apps/accounts/src/lib/assets/tools/laptop.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/other_device.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/other_service_provider.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/smartphone.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/software.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/subscription.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/tablet.svg create mode 100644 src/apps/accounts/src/lib/assets/tools/wearable.svg create mode 100644 src/apps/accounts/src/settings/tabs/tools/ToolsTab.module.scss create mode 100644 src/apps/accounts/src/settings/tabs/tools/ToolsTab.tsx create mode 100644 src/apps/accounts/src/settings/tabs/tools/devices/Devices.module.scss create mode 100644 src/apps/accounts/src/settings/tabs/tools/devices/Devices.tsx create mode 100644 src/apps/accounts/src/settings/tabs/tools/devices/index.ts create mode 100644 src/apps/accounts/src/settings/tabs/tools/index.ts create mode 100644 src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.module.scss create mode 100644 src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.tsx create mode 100644 src/apps/accounts/src/settings/tabs/tools/service-provider/index.ts create mode 100644 src/apps/accounts/src/settings/tabs/tools/service-provider/service-provider-types.config.ts create mode 100644 src/apps/accounts/src/settings/tabs/tools/software/Software.module.scss create mode 100644 src/apps/accounts/src/settings/tabs/tools/software/Software.tsx create mode 100644 src/apps/accounts/src/settings/tabs/tools/software/index.ts create mode 100644 src/apps/accounts/src/settings/tabs/tools/software/software-types.config.ts create mode 100644 src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.module.scss create mode 100644 src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.tsx create mode 100644 src/apps/accounts/src/settings/tabs/tools/subscriptions/index.ts diff --git a/src/apps/accounts/src/lib/assets/index.ts b/src/apps/accounts/src/lib/assets/index.ts index 0ffc9cc43..093847926 100644 --- a/src/apps/accounts/src/lib/assets/index.ts +++ b/src/apps/accounts/src/lib/assets/index.ts @@ -1,3 +1,4 @@ export * from './security' export * from './preferences' export * from './payments' +export * from './tools' diff --git a/src/apps/accounts/src/lib/assets/tools/Financial Institution.svg b/src/apps/accounts/src/lib/assets/tools/Financial Institution.svg new file mode 100644 index 000000000..5736f8c0f --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/Financial Institution.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/apps/accounts/src/lib/assets/tools/Internet Service Provider.svg b/src/apps/accounts/src/lib/assets/tools/Internet Service Provider.svg new file mode 100644 index 000000000..5736f8c0f --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/Internet Service Provider.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/apps/accounts/src/lib/assets/tools/Mobile Carrier.svg b/src/apps/accounts/src/lib/assets/tools/Mobile Carrier.svg new file mode 100644 index 000000000..eb23554ab --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/Mobile Carrier.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/apps/accounts/src/lib/assets/tools/Television.svg b/src/apps/accounts/src/lib/assets/tools/Television.svg new file mode 100644 index 000000000..671a335c5 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/Television.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/apps/accounts/src/lib/assets/tools/console.svg b/src/apps/accounts/src/lib/assets/tools/console.svg new file mode 100644 index 000000000..237abfc97 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/console.svg @@ -0,0 +1 @@ +004-consoleCreated with Sketch. diff --git a/src/apps/accounts/src/lib/assets/tools/desktop.svg b/src/apps/accounts/src/lib/assets/tools/desktop.svg new file mode 100644 index 000000000..1755a7581 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/desktop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/accounts/src/lib/assets/tools/index.ts b/src/apps/accounts/src/lib/assets/tools/index.ts new file mode 100644 index 000000000..160c83175 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/index.ts @@ -0,0 +1,31 @@ +import { ReactComponent as SoftwareIcon } from './software.svg' +import { ReactComponent as DesktopIncon } from './desktop.svg' +import { ReactComponent as SmartphoneIcon } from './smartphone.svg' +import { ReactComponent as OtherDeviceIcon } from './other_device.svg' +import { ReactComponent as LaptopIcon } from './laptop.svg' +import { ReactComponent as ConsoleIcon } from './console.svg' +import { ReactComponent as TabletIcon } from './tablet.svg' +import { ReactComponent as WearableIcon } from './wearable.svg' +import { ReactComponent as FinancialInstitutionIcon } from './Financial Institution.svg' +import { ReactComponent as OtherServiceProviderIcon } from './other_service_provider.svg' +import { ReactComponent as TelevisionServiceProviderIcon } from './Television.svg' +import { ReactComponent as MobileCarierServiceProviderIcon } from './Mobile Carrier.svg' +import { ReactComponent as InternetServiceProviderIcon } from './Internet Service Provider.svg' +import { ReactComponent as SubscriptionsIcon } from './subscription.svg' + +export { + ConsoleIcon, + DesktopIncon, + FinancialInstitutionIcon, + InternetServiceProviderIcon, + MobileCarierServiceProviderIcon, + LaptopIcon, + OtherDeviceIcon, + OtherServiceProviderIcon, + SmartphoneIcon, + SoftwareIcon, + SubscriptionsIcon, + TabletIcon, + TelevisionServiceProviderIcon, + WearableIcon, +} diff --git a/src/apps/accounts/src/lib/assets/tools/laptop.svg b/src/apps/accounts/src/lib/assets/tools/laptop.svg new file mode 100644 index 000000000..d54366f39 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/laptop.svg @@ -0,0 +1 @@ +notebookCreated with Sketch. diff --git a/src/apps/accounts/src/lib/assets/tools/other_device.svg b/src/apps/accounts/src/lib/assets/tools/other_device.svg new file mode 100644 index 000000000..1fca19866 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/other_device.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/apps/accounts/src/lib/assets/tools/other_service_provider.svg b/src/apps/accounts/src/lib/assets/tools/other_service_provider.svg new file mode 100644 index 000000000..659b14dae --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/other_service_provider.svg @@ -0,0 +1,17 @@ + + + + + +64px_wifi off + + + + + + + diff --git a/src/apps/accounts/src/lib/assets/tools/smartphone.svg b/src/apps/accounts/src/lib/assets/tools/smartphone.svg new file mode 100644 index 000000000..c8b32fa44 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/smartphone.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/accounts/src/lib/assets/tools/software.svg b/src/apps/accounts/src/lib/assets/tools/software.svg new file mode 100644 index 000000000..266fc1aaf --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/software.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/apps/accounts/src/lib/assets/tools/subscription.svg b/src/apps/accounts/src/lib/assets/tools/subscription.svg new file mode 100644 index 000000000..eafb3cfa9 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/subscription.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/accounts/src/lib/assets/tools/tablet.svg b/src/apps/accounts/src/lib/assets/tools/tablet.svg new file mode 100644 index 000000000..fa1a0d651 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/tablet.svg @@ -0,0 +1 @@ + diff --git a/src/apps/accounts/src/lib/assets/tools/wearable.svg b/src/apps/accounts/src/lib/assets/tools/wearable.svg new file mode 100644 index 000000000..646ae6854 --- /dev/null +++ b/src/apps/accounts/src/lib/assets/tools/wearable.svg @@ -0,0 +1 @@ +005-smart-watchCreated with Sketch. diff --git a/src/apps/accounts/src/lib/components/setting-section/SettingSection.tsx b/src/apps/accounts/src/lib/components/setting-section/SettingSection.tsx index d0f15322a..380286fdf 100644 --- a/src/apps/accounts/src/lib/components/setting-section/SettingSection.tsx +++ b/src/apps/accounts/src/lib/components/setting-section/SettingSection.tsx @@ -6,7 +6,7 @@ import styles from './SettingSection.module.scss' interface SettingSectionProps { containerClassName?: string readonly title: string - readonly infoText: string + readonly infoText?: string actionElement?: React.ReactNode leftElement?: React.ReactNode } diff --git a/src/apps/accounts/src/settings/tabs/AccountSettingsTabs.tsx b/src/apps/accounts/src/settings/tabs/AccountSettingsTabs.tsx index 2c2b006d4..c2a70aa22 100644 --- a/src/apps/accounts/src/settings/tabs/AccountSettingsTabs.tsx +++ b/src/apps/accounts/src/settings/tabs/AccountSettingsTabs.tsx @@ -2,12 +2,13 @@ import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' import { useLocation } from 'react-router-dom' import { useMemberTraits, UserProfile, UserTraits } from '~/libs/core' -import { TabsNavbar } from '~/libs/ui' +import { PageTitle, TabsNavbar, TabsNavItem } from '~/libs/ui' import { AccountSettingsTabsConfig, AccountSettingsTabViews, getHashFromTabId, getTabIdFromHash } from './config' import { AccountTab } from './account' import { PreferencesTab } from './preferences' import { PaymentsTab } from './payments' +import { ToolsTab } from './tools' import styles from './AccountSettingsTabs.module.scss' interface AccountSettingsTabsProps { @@ -37,6 +38,17 @@ const AccountSettingsTabs: FC = (props: AccountSetting tabs={AccountSettingsTabsConfig} /> + + {[ + AccountSettingsTabsConfig.find((tab: TabsNavItem) => tab.id === activeTab)?.title, + 'Account Settings', + 'Topcoder'].join(' | ')} + + + {activeTab === AccountSettingsTabViews.tools && ( + + )} + {activeTab === AccountSettingsTabViews.account && ( )} diff --git a/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.module.scss b/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.module.scss index 5855168b2..5dee26af8 100644 --- a/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.module.scss +++ b/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.module.scss @@ -1,6 +1,10 @@ @import '@libs/ui/styles/includes'; .diceModal { + :global(.modal-body) { + align-items: flex-start; + } + :global(.react-responsive-modal-closeButton) { display: flex; } @@ -9,11 +13,11 @@ display: flex; justify-content: space-evenly; margin: $sp-4 0; + width: 100%; .appStoreCard { display: flex; flex-direction: column; - align-items: center; } } diff --git a/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.tsx b/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.tsx index 46e5e940c..e03b63135 100644 --- a/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.tsx +++ b/src/apps/accounts/src/settings/tabs/account/security/dice-setup-modal/DiceSetupModal.tsx @@ -41,18 +41,19 @@ const DiceSetupModal: FC = (props: DiceSetupModalProps) => ]) function handleSecondaryButtonClick(): void { - if (step === 1 || step === 4) { - props.onClose() - } else { - setStep(step - 1) + switch (step) { + case 2: return setStep(step - 1) + default: return props.onClose() } } function handlePrimaryButtonClick(): void { - if (step >= 3) { - props.onClose() - } else { - setStep(step + 1) + switch (step) { + case 1: + case 2: + case 3: + return setStep(step + 1) + default: return props.onClose() } } @@ -240,6 +241,7 @@ const DiceSetupModal: FC = (props: DiceSetupModalProps) => DICE ID Logo

For more information on DICE ID, please visit + {' '} = (props: ToolsTabProps) => ( +

+) + +export default ToolsTab diff --git a/src/apps/accounts/src/settings/tabs/tools/devices/Devices.module.scss b/src/apps/accounts/src/settings/tabs/tools/devices/Devices.module.scss new file mode 100644 index 000000000..fb9e41691 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/devices/Devices.module.scss @@ -0,0 +1,45 @@ +@import '@libs/ui/styles/includes'; + +.container { + margin: $sp-8 0; + + .content { + display: flex; + flex-direction: column; + margin-bottom: 0; + + .imageWrap { + padding: $sp-4; + background-color: $black-5; + border-radius: 4px; + margin-right: $sp-2; + align-self: flex-start; + width: 64px; + height: 64px; + } + + .actionElements { + display: flex; + align-items: center; + + :global(button) { + padding: $sp-2; + + @include ltelg { + padding: $sp-1; + } + } + + svg { + width: 20px; + height: 20px; + } + } + + .deviceForm { + display: grid; + grid-template-columns: 1fr 1fr; + margin: $sp-4 0; + } + } +} \ No newline at end of file diff --git a/src/apps/accounts/src/settings/tabs/tools/devices/Devices.tsx b/src/apps/accounts/src/settings/tabs/tools/devices/Devices.tsx new file mode 100644 index 000000000..f84bdb9a0 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/devices/Devices.tsx @@ -0,0 +1,97 @@ +import { FC } from 'react' +import { bind, compact } from 'lodash' + +import { UserProfile, UserTrait } from '~/libs/core' +import { Button, Collapsible, IconOutline } from '~/libs/ui' +import { + ConsoleIcon, + DesktopIncon, + LaptopIcon, + OtherDeviceIcon, + SettingSection, + SmartphoneIcon, + TabletIcon, + WearableIcon, +} from '~/apps/accounts/src/lib' + +import styles from './Devices.module.scss' + +interface DevicesProps { + devicesTrait: UserTrait | undefined + profile: UserProfile +} + +const Devices: FC = (props: DevicesProps) => { + console.log('Devices', props.devicesTrait, props.profile) + + function handleEditBtnClick(trait: UserTrait): void { + console.log('handleCTABtnClick', trait) + } + + function handleTrashBtnClick(trait: UserTrait): void { + console.log('handleTrashBtnClick', trait) + } + + function renderDeviceImage(trait: UserTrait): JSX.Element { + switch (trait.deviceType) { + case 'Console': return + case 'Desktop': return + case 'Laptop': return + case 'Smartphone': return + case 'Tablet': return + case 'Wearable': return + default: return + } + } + + return ( + YOUR DEVICES} + containerClass={styles.container} + contentClass={styles.content} + > + { + props.devicesTrait?.traits.data.map((trait: UserTrait) => ( + + {renderDeviceImage(trait)} + + )} + title={trait.model} + infoText={ + compact([ + trait.manufacturer, trait.operatingSystem, trait.deviceType, + ]) + .join(' | ') + } + actionElement={( +
+
+ )} + /> + )) + } + +
+

Add a new device to your devices list

+ +
+
+ ) +} + +export default Devices diff --git a/src/apps/accounts/src/settings/tabs/tools/devices/index.ts b/src/apps/accounts/src/settings/tabs/tools/devices/index.ts new file mode 100644 index 000000000..58cf3683b --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/devices/index.ts @@ -0,0 +1 @@ +export { default as Devices } from './Devices' diff --git a/src/apps/accounts/src/settings/tabs/tools/index.ts b/src/apps/accounts/src/settings/tabs/tools/index.ts new file mode 100644 index 000000000..45b8e98a5 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/index.ts @@ -0,0 +1 @@ +export { default as ToolsTab } from './ToolsTab' diff --git a/src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.module.scss b/src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.module.scss new file mode 100644 index 000000000..1de4cfa45 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.module.scss @@ -0,0 +1,60 @@ +@import '@libs/ui/styles/includes'; + +.container { + margin: $sp-8 0; + + .content { + display: flex; + flex-direction: column; + margin-bottom: 0; + + .imageWrap { + padding: $sp-4; + background-color: $black-5; + border-radius: 4px; + margin-right: $sp-2; + align-self: flex-start; + width: 64px; + height: 64px; + } + + .actionElements { + display: flex; + align-items: center; + + :global(button) { + padding: $sp-2; + + @include ltelg { + padding: $sp-1; + } + } + + svg { + width: 20px; + height: 20px; + } + } + + .formWrap { + display: grid; + grid-template-columns: 1fr 1fr; + margin: $sp-13 0 $sp-4; + + .formCTAs { + display: flex; + align-items: center; + + svg { + width: 14px; + height: 14px; + margin-right: $sp-1; + } + + .ctaBtnCancel { + margin-left: $sp-8; + } + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..53644e14f --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/service-provider/ServiceProvider.tsx @@ -0,0 +1,337 @@ +import { Dispatch, FC, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react' +import { bind, isEmpty, reject, trim } from 'lodash' +import { toast } from 'react-toastify' + +import { updateMemberTraitsAsync, UserProfile, UserTrait } from '~/libs/core' +import { Button, Collapsible, ConfirmModal, IconOutline, InputSelect, InputText } from '~/libs/ui' +import { + FinancialInstitutionIcon, + InternetServiceProviderIcon, + MobileCarierServiceProviderIcon, + OtherServiceProviderIcon, + SettingSection, + TelevisionServiceProviderIcon, +} from '~/apps/accounts/src/lib' + +import { serviceProviderTypes } from './service-provider-types.config' +import styles from './ServiceProvider.module.scss' + +interface ServiceProviderProps { + serviceProviderTrait: UserTrait | undefined + profile: UserProfile +} + +const ServiceProvider: FC = (props: ServiceProviderProps) => { + const formElRef: MutableRefObject = useRef() + + const [serviceProviderTypesData, setServiceProviderTypesData]: [ + UserTrait[] | undefined, + Dispatch> + ] = useState() + + const [selectedServiceProviderType, setSelectedServiceProviderType]: [ + string | undefined, + Dispatch> + ] + = useState() + + const [selectedServiceProviderName, setSelectedServiceProviderName]: [ + string | undefined, + Dispatch> + ] + = useState() + + const [formErrors, setFormErrors]: [ + { [key: string]: string }, + Dispatch> + ] + = useState<{ [key: string]: string }>({}) + + const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) + + const [removeConfirmationOpen, setRemoveConfirmationOpen]: [boolean, Dispatch>] + = useState(false) + + const [itemToUpdate, setItemToUpdate]: [UserTrait | undefined, Dispatch>] + = useState() + + const [itemToRemove, setItemToRemove]: [UserTrait | undefined, Dispatch>] + = useState() + + useEffect(() => { + setServiceProviderTypesData(props.serviceProviderTrait?.traits.data) + }, [props.serviceProviderTrait]) + + function toggleRemoveConfirmation(): void { + setRemoveConfirmationOpen(!removeConfirmationOpen) + setItemToRemove(undefined) + } + + function handleEditBtnClick(trait: UserTrait): void { + setItemToUpdate(trait) + setIsEditMode(true) + setSelectedServiceProviderType(trait.serviceProviderType) + setSelectedServiceProviderName(trim(trait.name)) + setFormErrors({}) + } + + function handleTrashBtnClick(trait: UserTrait): void { + setRemoveConfirmationOpen(true) + setItemToRemove(trait) + } + + function handleServiceProviderTypeChange(event: React.ChangeEvent): void { + setSelectedServiceProviderType(event.target.value) + } + + function handleServiceProviderNameChange(event: React.ChangeEvent): void { + setSelectedServiceProviderName(event.target.value) + } + + function resetForm(): void { + setSelectedServiceProviderType(undefined) + setSelectedServiceProviderName(undefined) + formElRef.current.reset() + } + + function handleFormAction(): void { + // validate the form + const sN: string = trim(selectedServiceProviderName) + const updatedFormErrors: { [key: string]: string } = {} + const serviceProviderTypeUpdate: UserTrait = { + name: sN, + serviceProviderType: selectedServiceProviderType, + } + + if (serviceProviderTypesData?.find( + (trait: UserTrait) => trait.name === sN && trait.serviceProviderType === selectedServiceProviderType, + )) { + resetForm() + return + } + + if (!selectedServiceProviderType) { + updatedFormErrors.serviceProviderType = 'Service Provider type is required' + } + + if (!sN) { + updatedFormErrors.serviceProviderName = 'Service Provider name is required' + } + + if (isEmpty(updatedFormErrors)) { + // call the API to update the trait based on action type + if (isEditMode) { + const updatedServiceProviderTypesData: UserTrait[] = reject( + serviceProviderTypesData, + (trait: UserTrait) => ( + trait.name === itemToUpdate?.name + && trait.serviceProviderType === itemToUpdate?.serviceProviderType + ), + ) || [] + + updateMemberTraitsAsync( + props.profile.handle, + [{ + categoryName: 'Service Provider', + traitId: 'service_provider', + traits: { + data: [ + ...updatedServiceProviderTypesData || [], + serviceProviderTypeUpdate, + ], + }, + }], + ) + .then(() => { + toast.success('Service Provider updated successfully') + setServiceProviderTypesData([ + ...updatedServiceProviderTypesData || [], + serviceProviderTypeUpdate, + ]) + }) + .catch(() => { + toast.error('Error updating Service Provider') + }) + .finally(() => { + resetForm() + setIsEditMode(false) + }) + } else { + updateMemberTraitsAsync( + props.profile.handle, + [{ + categoryName: 'Service Provider', + traitId: 'service_provider', + traits: { + data: [ + ...serviceProviderTypesData || [], + serviceProviderTypeUpdate, + ], + }, + }], + ) + .then(() => { + toast.success('Service Provider added successfully') + setServiceProviderTypesData([ + ...serviceProviderTypesData || [], + serviceProviderTypeUpdate, + ]) + }) + .catch(() => { + toast.error('Error adding new Service Provider') + }) + .finally(() => { + resetForm() + }) + } + } + + setFormErrors(updatedFormErrors) + } + + function handleCancelEditMode(): void { + setIsEditMode(false) + resetForm() + setFormErrors({}) + } + + function onRemoveItemConfirm(): void { + const updatedServiceProviderTypesData: UserTrait[] = reject(serviceProviderTypesData, (trait: UserTrait) => ( + trait.name === itemToRemove?.name && trait.serviceProviderType === itemToRemove?.serviceProviderType + )) || [] + + updateMemberTraitsAsync( + props.profile.handle, + [{ + categoryName: 'Service Provider', + traitId: 'service_provider', + traits: { + data: updatedServiceProviderTypesData, + }, + }], + ) + .then(() => { + toast.success('Service Provider deleted successfully') + setServiceProviderTypesData(updatedServiceProviderTypesData) + }) + .catch(() => { + toast.error('Error deleting Service Provider') + }) + .finally(() => { + toggleRemoveConfirmation() + }) + } + + function renderServiceProviderIcon(trait: UserTrait): React.ReactNode { + switch (trait.serviceProviderType) { + case 'Financial Institution': return + case 'Internet Service Provider': return + case 'MobileCarierServiceProviderIcon': return + case 'Television': return + default: return + } + } + + return ( + Service Provider} + containerClass={styles.container} + contentClass={styles.content} + > + { + serviceProviderTypesData?.map((trait: UserTrait) => ( + + {renderServiceProviderIcon(trait)} + + )} + title={trait.name} + infoText={trait.serviceProviderType} + actionElement={( +
+
+ )} + /> + )) + } + + +
+ Are you sure you want to delete + {' '} + {itemToRemove?.name} + ? + {' '} + This action cannot be undone. +
+
+ +
+

Add a new service provider

+
+ + +
+ {!isEditMode && } +
+
+
+
+ ) +} + +export default ServiceProvider diff --git a/src/apps/accounts/src/settings/tabs/tools/service-provider/index.ts b/src/apps/accounts/src/settings/tabs/tools/service-provider/index.ts new file mode 100644 index 000000000..378284f99 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/service-provider/index.ts @@ -0,0 +1 @@ +export { default as ServiceProvider } from './ServiceProvider' diff --git a/src/apps/accounts/src/settings/tabs/tools/service-provider/service-provider-types.config.ts b/src/apps/accounts/src/settings/tabs/tools/service-provider/service-provider-types.config.ts new file mode 100644 index 000000000..dc9ca7dfb --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/service-provider/service-provider-types.config.ts @@ -0,0 +1,23 @@ +export const serviceProviderTypes: Array<{ + label: string, + value: string, +}> = [{ + label: 'Internet Service Provider', + value: 'Internet Service Provider', +}, +{ + label: 'Mobile Carrier', + value: 'Mobile Carrier', +}, +{ + label: 'Television', + value: 'Television', +}, +{ + label: 'Financial Institution', + value: 'Financial Institution', +}, +{ + label: 'Other', + value: 'Other', +}] diff --git a/src/apps/accounts/src/settings/tabs/tools/software/Software.module.scss b/src/apps/accounts/src/settings/tabs/tools/software/Software.module.scss new file mode 100644 index 000000000..1de4cfa45 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/software/Software.module.scss @@ -0,0 +1,60 @@ +@import '@libs/ui/styles/includes'; + +.container { + margin: $sp-8 0; + + .content { + display: flex; + flex-direction: column; + margin-bottom: 0; + + .imageWrap { + padding: $sp-4; + background-color: $black-5; + border-radius: 4px; + margin-right: $sp-2; + align-self: flex-start; + width: 64px; + height: 64px; + } + + .actionElements { + display: flex; + align-items: center; + + :global(button) { + padding: $sp-2; + + @include ltelg { + padding: $sp-1; + } + } + + svg { + width: 20px; + height: 20px; + } + } + + .formWrap { + display: grid; + grid-template-columns: 1fr 1fr; + margin: $sp-13 0 $sp-4; + + .formCTAs { + display: flex; + align-items: center; + + svg { + width: 14px; + height: 14px; + margin-right: $sp-1; + } + + .ctaBtnCancel { + margin-left: $sp-8; + } + } + } + } +} \ No newline at end of file diff --git a/src/apps/accounts/src/settings/tabs/tools/software/Software.tsx b/src/apps/accounts/src/settings/tabs/tools/software/Software.tsx new file mode 100644 index 000000000..338db50f3 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/software/Software.tsx @@ -0,0 +1,316 @@ +import { Dispatch, FC, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react' +import { bind, isEmpty, reject, trim } from 'lodash' +import { toast } from 'react-toastify' + +import { updateMemberTraitsAsync, UserProfile, UserTrait } from '~/libs/core' +import { Button, Collapsible, ConfirmModal, IconOutline, InputSelect, InputText } from '~/libs/ui' +import { SettingSection, SoftwareIcon } from '~/apps/accounts/src/lib' + +import { softwareTypes } from './software-types.config' +import styles from './Software.module.scss' + +interface SoftwareProps { + softwareTrait: UserTrait | undefined + profile: UserProfile +} + +const Software: FC = (props: SoftwareProps) => { + const formElRef: MutableRefObject = useRef() + + const [softwareTypesData, setSoftwareTypesData]: [ + UserTrait[] | undefined, + Dispatch> + ] = useState() + + const [selectedSoftwareType, setSelectedSoftwareType]: [ + string | undefined, + Dispatch> + ] + = useState() + + const [selectedSoftwareName, setSelectedSoftwareName]: [ + string | undefined, + Dispatch> + ] + = useState() + + const [formErrors, setFormErrors]: [ + { [key: string]: string }, + Dispatch> + ] + = useState<{ [key: string]: string }>({}) + + const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) + + const [removeConfirmationOpen, setRemoveConfirmationOpen]: [boolean, Dispatch>] + = useState(false) + + const [itemToUpdate, setItemToUpdate]: [UserTrait | undefined, Dispatch>] + = useState() + + const [itemToRemove, setItemToRemove]: [UserTrait | undefined, Dispatch>] + = useState() + + useEffect(() => { + setSoftwareTypesData(props.softwareTrait?.traits.data) + }, [props.softwareTrait]) + + function toggleRemoveConfirmation(): void { + setRemoveConfirmationOpen(!removeConfirmationOpen) + setItemToRemove(undefined) + } + + function handleEditBtnClick(trait: UserTrait): void { + setItemToUpdate(trait) + setIsEditMode(true) + setSelectedSoftwareType(trait.softwareType) + setSelectedSoftwareName(trim(trait.name)) + setFormErrors({}) + } + + function handleTrashBtnClick(trait: UserTrait): void { + setRemoveConfirmationOpen(true) + setItemToRemove(trait) + } + + function handleSoftwareTypeChange(event: React.ChangeEvent): void { + setSelectedSoftwareType(event.target.value) + } + + function handleSoftwareNameChange(event: React.ChangeEvent): void { + setSelectedSoftwareName(event.target.value) + } + + function resetForm(): void { + setSelectedSoftwareType(undefined) + setSelectedSoftwareName(undefined) + formElRef.current.reset() + } + + function handleFormAction(): void { + // validate the form + const sN: string = trim(selectedSoftwareName) + const updatedFormErrors: { [key: string]: string } = {} + const softwareTypeUpdate: UserTrait = { + name: sN, + softwareType: selectedSoftwareType, + } + + if (softwareTypesData?.find( + (trait: UserTrait) => trait.name === sN && trait.softwareType === selectedSoftwareType, + )) { + resetForm() + return + } + + if (!selectedSoftwareType) { + updatedFormErrors.softwareType = 'Software type is required' + } + + if (!sN) { + updatedFormErrors.softwareName = 'Software name is required' + } + + if (isEmpty(updatedFormErrors)) { + // call the API to update the trait based on action type + if (isEditMode) { + const updatedSoftwareTypesData: UserTrait[] = reject(softwareTypesData, (trait: UserTrait) => ( + trait.name === itemToUpdate?.name && trait.softwareType === itemToUpdate?.softwareType + )) || [] + + updateMemberTraitsAsync( + props.profile.handle, + [{ + categoryName: 'Software', + traitId: 'software', + traits: { + data: [ + ...updatedSoftwareTypesData || [], + softwareTypeUpdate, + ], + }, + }], + ) + .then(() => { + toast.success('Software updated successfully') + setSoftwareTypesData([ + ...updatedSoftwareTypesData || [], + softwareTypeUpdate, + ]) + }) + .catch(() => { + toast.error('Error updating software') + }) + .finally(() => { + resetForm() + setIsEditMode(false) + }) + } else { + updateMemberTraitsAsync( + props.profile.handle, + [{ + categoryName: 'Software', + traitId: 'software', + traits: { + data: [ + ...softwareTypesData || [], + softwareTypeUpdate, + ], + }, + }], + ) + .then(() => { + toast.success('Software added successfully') + setSoftwareTypesData([ + ...softwareTypesData || [], + softwareTypeUpdate, + ]) + }) + .catch(() => { + toast.error('Error adding new software') + }) + .finally(() => { + resetForm() + }) + } + } + + setFormErrors(updatedFormErrors) + } + + function handleCancelEditMode(): void { + setIsEditMode(false) + resetForm() + setFormErrors({}) + } + + function onRemoveItemConfirm(): void { + const updatedSoftwareTypesData: UserTrait[] = reject(softwareTypesData, (trait: UserTrait) => ( + trait.name === itemToRemove?.name && trait.softwareType === itemToRemove?.softwareType + )) || [] + + updateMemberTraitsAsync( + props.profile.handle, + [{ + categoryName: 'Software', + traitId: 'software', + traits: { + data: updatedSoftwareTypesData, + }, + }], + ) + .then(() => { + toast.success('Software deleted successfully') + setSoftwareTypesData(updatedSoftwareTypesData) + }) + .catch(() => { + toast.error('Error deleting software') + }) + .finally(() => { + toggleRemoveConfirmation() + }) + } + + return ( + Software} + containerClass={styles.container} + contentClass={styles.content} + > + { + softwareTypesData?.map((trait: UserTrait) => ( + + + + )} + title={trait.name} + infoText={trait.softwareType} + actionElement={( +
+
+ )} + /> + )) + } + + +
+ Are you sure you want to delete + {' '} + {itemToRemove?.name} + ? + {' '} + This action cannot be undone. +
+
+ +
+

Add a new software

+
+ + +
+ {!isEditMode && } +
+
+
+
+ ) +} + +export default Software diff --git a/src/apps/accounts/src/settings/tabs/tools/software/index.ts b/src/apps/accounts/src/settings/tabs/tools/software/index.ts new file mode 100644 index 000000000..fe6ce76e3 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/software/index.ts @@ -0,0 +1 @@ +export { default as Software } from './Software' diff --git a/src/apps/accounts/src/settings/tabs/tools/software/software-types.config.ts b/src/apps/accounts/src/settings/tabs/tools/software/software-types.config.ts new file mode 100644 index 000000000..38111917d --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/software/software-types.config.ts @@ -0,0 +1,23 @@ +export const softwareTypes: Array<{ + label: string, + value: string, +}> = [{ + label: 'Developer Tools', + value: 'Developer Tools', +}, +{ + label: 'Browser', + value: 'Browser', +}, +{ + label: 'Productivity', + value: 'Productivity', +}, +{ + label: 'Graphics & Design', + value: 'Graphics & Design', +}, +{ + label: 'Utilities', + value: 'Utilities', +}] diff --git a/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.module.scss b/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.module.scss new file mode 100644 index 000000000..1de4cfa45 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.module.scss @@ -0,0 +1,60 @@ +@import '@libs/ui/styles/includes'; + +.container { + margin: $sp-8 0; + + .content { + display: flex; + flex-direction: column; + margin-bottom: 0; + + .imageWrap { + padding: $sp-4; + background-color: $black-5; + border-radius: 4px; + margin-right: $sp-2; + align-self: flex-start; + width: 64px; + height: 64px; + } + + .actionElements { + display: flex; + align-items: center; + + :global(button) { + padding: $sp-2; + + @include ltelg { + padding: $sp-1; + } + } + + svg { + width: 20px; + height: 20px; + } + } + + .formWrap { + display: grid; + grid-template-columns: 1fr 1fr; + margin: $sp-13 0 $sp-4; + + .formCTAs { + display: flex; + align-items: center; + + svg { + width: 14px; + height: 14px; + margin-right: $sp-1; + } + + .ctaBtnCancel { + margin-left: $sp-8; + } + } + } + } +} \ No newline at end of file diff --git a/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.tsx b/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.tsx new file mode 100644 index 000000000..f8568b16f --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/subscriptions/Subscriptions.tsx @@ -0,0 +1,291 @@ +import { Dispatch, FC, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react' +import { bind, isEmpty, reject, trim } from 'lodash' +import { toast } from 'react-toastify' + +import { updateMemberTraitsAsync, UserProfile, UserTrait } from '~/libs/core' +import { Button, Collapsible, ConfirmModal, IconOutline, InputText } from '~/libs/ui' +import { SettingSection, SubscriptionsIcon } from '~/apps/accounts/src/lib' + +import styles from './Subscriptions.module.scss' + +interface SubscriptionsProps { + subscriptionsTrait: UserTrait | undefined + profile: UserProfile +} + +const Subscriptions: FC = (props: SubscriptionsProps) => { + const formElRef: MutableRefObject = useRef() + + const [subscriptionsTypesData, setSubscriptionsTypesData]: [ + UserTrait[] | undefined, + Dispatch> + ] = useState() + + const [selectedSubsctiptionName, setSelectedSubscriptionName]: [ + string | undefined, + Dispatch> + ] + = useState() + + const [formErrors, setFormErrors]: [ + { [key: string]: string }, + Dispatch> + ] + = useState<{ [key: string]: string }>({}) + + const [isEditMode, setIsEditMode]: [boolean, Dispatch>] = useState(false) + + const [removeConfirmationOpen, setRemoveConfirmationOpen]: [boolean, Dispatch>] + = useState(false) + + const [itemToUpdate, setItemToUpdate]: [UserTrait | undefined, Dispatch>] + = useState() + + const [itemToRemove, setItemToRemove]: [UserTrait | undefined, Dispatch>] + = useState() + + useEffect(() => { + setSubscriptionsTypesData(props.subscriptionsTrait?.traits.data) + }, [props.subscriptionsTrait]) + + function toggleRemoveConfirmation(): void { + setRemoveConfirmationOpen(!removeConfirmationOpen) + setItemToRemove(undefined) + } + + function handleEditBtnClick(trait: UserTrait): void { + setItemToUpdate(trait) + setIsEditMode(true) + setSelectedSubscriptionName(trim(trait.name)) + setFormErrors({}) + } + + function handleTrashBtnClick(trait: UserTrait): void { + setRemoveConfirmationOpen(true) + setItemToRemove(trait) + } + + function handleSubscriptionsNameChange(event: React.ChangeEvent): void { + setSelectedSubscriptionName(event.target.value) + } + + function resetForm(): void { + setSelectedSubscriptionName(undefined) + formElRef.current.reset() + } + + function handleFormAction(): void { + // validate the form + const sN: string = trim(selectedSubsctiptionName) + const updatedFormErrors: { [key: string]: string } = {} + const softwareTypeUpdate: UserTrait = { + name: sN, + } + + if (subscriptionsTypesData?.find( + (trait: UserTrait) => trait.name === sN, + )) { + resetForm() + return + } + + if (!sN) { + updatedFormErrors.subscriptionName = 'Subscription name is required' + } + + if (isEmpty(updatedFormErrors)) { + // call the API to update the trait based on action type + if (isEditMode) { + const updatedSubscriptionsTypesData: UserTrait[] = reject( + subscriptionsTypesData, + (trait: UserTrait) => ( + trait.name === itemToUpdate?.name + ), + ) || [] + + updateMemberTraitsAsync( + props.profile.handle, + [{ + categoryName: 'Subscription', + traitId: 'subscription', + traits: { + data: [ + ...updatedSubscriptionsTypesData || [], + softwareTypeUpdate, + ], + }, + }], + ) + .then(() => { + toast.success('Subscription updated successfully') + setSubscriptionsTypesData([ + ...updatedSubscriptionsTypesData || [], + softwareTypeUpdate, + ]) + }) + .catch(() => { + toast.error('Error updating subscription') + }) + .finally(() => { + resetForm() + setIsEditMode(false) + }) + } else { + updateMemberTraitsAsync( + props.profile.handle, + [{ + categoryName: 'Subscription', + traitId: 'subscription', + traits: { + data: [ + ...subscriptionsTypesData || [], + softwareTypeUpdate, + ], + }, + }], + ) + .then(() => { + toast.success('Subscription added successfully') + setSubscriptionsTypesData([ + ...subscriptionsTypesData || [], + softwareTypeUpdate, + ]) + }) + .catch(() => { + toast.error('Error adding new subscription') + }) + .finally(() => { + resetForm() + }) + } + } + + setFormErrors(updatedFormErrors) + } + + function handleCancelEditMode(): void { + setIsEditMode(false) + resetForm() + setFormErrors({}) + } + + function onRemoveItemConfirm(): void { + const updatedSubscriptionsTypesData: UserTrait[] = reject(subscriptionsTypesData, (trait: UserTrait) => ( + trait.name === itemToRemove?.name + )) || [] + + updateMemberTraitsAsync( + props.profile.handle, + [{ + categoryName: 'Subscription', + traitId: 'subscription', + traits: { + data: updatedSubscriptionsTypesData, + }, + }], + ) + .then(() => { + toast.success('Subscription deleted successfully') + setSubscriptionsTypesData(updatedSubscriptionsTypesData) + }) + .catch(() => { + toast.error('Error deleting subscription') + }) + .finally(() => { + toggleRemoveConfirmation() + }) + } + + return ( + Subscriptions} + containerClass={styles.container} + contentClass={styles.content} + > + { + subscriptionsTypesData?.map((trait: UserTrait) => ( + + + + )} + title={trait.name} + actionElement={( +
+
+ )} + /> + )) + } + + +
+ Are you sure you want to delete + {' '} + {itemToRemove?.name} + ? + {' '} + This action cannot be undone. +
+
+ +
+

Add a new subscription

+
+ +
+ {!isEditMode && } +
+
+
+
+ ) +} + +export default Subscriptions diff --git a/src/apps/accounts/src/settings/tabs/tools/subscriptions/index.ts b/src/apps/accounts/src/settings/tabs/tools/subscriptions/index.ts new file mode 100644 index 000000000..fa78794f6 --- /dev/null +++ b/src/apps/accounts/src/settings/tabs/tools/subscriptions/index.ts @@ -0,0 +1 @@ +export { default as Subscriptions } from './Subscriptions' From cc7b2ab0be8f6e30ff5a81a12cd2bc06412aa2ba Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 7 Jun 2023 15:08:28 +0300 Subject: [PATCH 2/2] Fixes form reset on password change --- .../src/settings/tabs/account/user-and-pass/UserAndPassword.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apps/accounts/src/settings/tabs/account/user-and-pass/UserAndPassword.tsx b/src/apps/accounts/src/settings/tabs/account/user-and-pass/UserAndPassword.tsx index d41821722..75ed68182 100644 --- a/src/apps/accounts/src/settings/tabs/account/user-and-pass/UserAndPassword.tsx +++ b/src/apps/accounts/src/settings/tabs/account/user-and-pass/UserAndPassword.tsx @@ -123,6 +123,7 @@ const UserAndPassword: FC = (props: UserAndPasswordProps) action='submit' formDef={UserAndPassFromConfig} formValues={formValues} + resetFormAfterSave requestGenerator={requestGenerator} save={onSave} shouldDisableButton={shouldDisableChangePasswordButton}