From 8194b6dc796a9118a3a6f0e228f8c2495597d2cc Mon Sep 17 00:00:00 2001 From: ikprk Date: Tue, 25 Jun 2024 08:58:23 +0200 Subject: [PATCH 1/8] Billing step --- .../ChangeNowModal/steps/FormStep.tsx | 21 +- .../GuardarianModal/GuardarianModal.tsx | 68 +++++ .../GuardarianModal/GuardarianModal.types.ts | 4 + .../src/components/GuardarianModal/index.ts | 1 + .../steps/GuardarianModalBillingInfoStep.tsx | 244 ++++++++++++++++++ packages/atlas/src/config/env.ts | 2 + .../GuardarianService/GuardarianService.ts | 170 ++++++++++++ .../GuardarianService.types.ts | 75 ++++++ .../src/utils/GuardarianService/index.ts | 1 + 9 files changed, 577 insertions(+), 9 deletions(-) create mode 100644 packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx create mode 100644 packages/atlas/src/components/GuardarianModal/GuardarianModal.types.ts create mode 100644 packages/atlas/src/components/GuardarianModal/index.ts create mode 100644 packages/atlas/src/components/GuardarianModal/steps/GuardarianModalBillingInfoStep.tsx create mode 100644 packages/atlas/src/utils/GuardarianService/GuardarianService.ts create mode 100644 packages/atlas/src/utils/GuardarianService/GuardarianService.types.ts create mode 100644 packages/atlas/src/utils/GuardarianService/index.ts diff --git a/packages/atlas/src/components/ChangeNowModal/steps/FormStep.tsx b/packages/atlas/src/components/ChangeNowModal/steps/FormStep.tsx index 98908d9326..1f0ecd7910 100644 --- a/packages/atlas/src/components/ChangeNowModal/steps/FormStep.tsx +++ b/packages/atlas/src/components/ChangeNowModal/steps/FormStep.tsx @@ -19,7 +19,6 @@ import { useSubscribeAccountBalance } from '@/providers/joystream' import { useSnackbar } from '@/providers/snackbars' import { square } from '@/styles' import { - Currency, JOYSTREAM_CHANGENOW_LEGACY_TICKER, JOYSTREAM_CHANGENOW_TICKER, changeNowService, @@ -339,24 +338,28 @@ export const FormStep = ({ setPrimaryButtonProps, onSubmit, type, initialValues ) } -type CurrencyInputProps = { - currencies: ComboBoxProps<{ value: string } & Currency>['items'] - initialCurrency?: ComboBoxProps<{ value: string } & Currency>['initialSelectedItem'] +type CurrencyInputProps = { + currencies: ComboBoxProps<{ value: string } & T>['items'] + initialCurrency?: ComboBoxProps<{ value: string } & T>['initialSelectedItem'] + currency?: string lockedCurrency?: string onCurrencySelect: (value: string) => void isLoading?: boolean } & TokenInputProps -const CurrencyInput = ({ +export const CurrencyInput = ({ currencies, onCurrencySelect, lockedCurrency, isLoading, + currency, initialCurrency, ...tokenProps -}: CurrencyInputProps) => { +}: CurrencyInputProps) => { const [sel, setSel] = useState(initialCurrency?.value) - const selected = currencies?.find((opt) => opt.value.toLowerCase() === (lockedCurrency ?? sel)?.toLowerCase()) + const selected = currencies?.find( + (opt) => opt.value.toLowerCase() === (lockedCurrency ?? currency ?? sel)?.toLowerCase() + ) return ( } nodeEnd={isLoading ? :
} /> @@ -368,8 +371,8 @@ const CurrencyInput = ({ disabled={!!lockedCurrency} placeholder="BTC" onSelectedItemChange={(e) => { - setSel(e?.legacyTicker ?? '') - onCurrencySelect(e?.legacyTicker ?? '') + setSel(e?.value ?? '') + onCurrencySelect(e?.value ?? '') }} items={currencies} /> diff --git a/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx b/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx new file mode 100644 index 0000000000..a710c86d44 --- /dev/null +++ b/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx @@ -0,0 +1,68 @@ +import { useRef, useState } from 'react' + +import { GuardarianModalSteps } from './GuardarianModal.types' +import { GuardarianBillingInfo, GuardarianModalBillingInfoStep } from './steps/GuardarianModalBillingInfoStep' +import { GuardarianForm, GuardarianModalFormStep } from './steps/GuardarianModalFormStep' + +import { Button } from '../_buttons/Button' +import { DialogModal } from '../_overlays/DialogModal' + +type LoadingSetter = (value: boolean) => void +export type SetActionButtonHandler = (setLoading?: LoadingSetter) => void | Promise + +type GuardarianData = GuardarianBillingInfo & GuardarianForm + +export const GuardarianModal = () => { + const [step, setStep] = useState(GuardarianModalSteps.INFO) + const [primaryAction, setPrimaryAction] = useState(undefined) + const formRef = useRef({ + from: { + currency: undefined, + amount: undefined, + }, + to: { + currency: undefined, + amount: undefined, + }, + }) + + return ( + primaryAction?.(), + }} + additionalActionsNode={ + [GuardarianModalSteps.INFO].includes(step) ? : null + } + > + {step === GuardarianModalSteps.INFO ? ( + { + formRef.current = { + ...formRef.current, + ...data, + } + setStep(GuardarianModalSteps.FORM) + }} + setActionButtonHandler={(fn) => setPrimaryAction(() => fn)} + /> + ) : null} + {step === GuardarianModalSteps.FORM ? ( + { + formRef.current = { + ...formRef.current, + ...data, + } + // setStep(GuardarianModalSteps.FORM) + }} + setActionButtonHandler={(fn) => setPrimaryAction(() => fn)} + /> + ) : null} + + ) +} diff --git a/packages/atlas/src/components/GuardarianModal/GuardarianModal.types.ts b/packages/atlas/src/components/GuardarianModal/GuardarianModal.types.ts new file mode 100644 index 0000000000..ea1307d4ca --- /dev/null +++ b/packages/atlas/src/components/GuardarianModal/GuardarianModal.types.ts @@ -0,0 +1,4 @@ +export enum GuardarianModalSteps { + INFO, + FORM, +} diff --git a/packages/atlas/src/components/GuardarianModal/index.ts b/packages/atlas/src/components/GuardarianModal/index.ts new file mode 100644 index 0000000000..cb9397d049 --- /dev/null +++ b/packages/atlas/src/components/GuardarianModal/index.ts @@ -0,0 +1 @@ +export * from './GuardarianModal' diff --git a/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalBillingInfoStep.tsx b/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalBillingInfoStep.tsx new file mode 100644 index 0000000000..8397236c0a --- /dev/null +++ b/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalBillingInfoStep.tsx @@ -0,0 +1,244 @@ +import { useMemo } from 'react' +import { Controller, useForm } from 'react-hook-form' +import { useQuery } from 'react-query' + +import { FlexBox } from '@/components/FlexBox' +import { Text } from '@/components/Text' +import { ComboBox, ComboBoxProps } from '@/components/_inputs/ComboBox' +import { Datepicker } from '@/components/_inputs/Datepicker' +import { FormField } from '@/components/_inputs/FormField' +import { TextInput } from '@/components/_inputs/Input/Input.styles' +import { useMountEffect } from '@/hooks/useMountEffect' +import { guardarianService } from '@/utils/GuardarianService' +import { GuardarianCountry } from '@/utils/GuardarianService/GuardarianService.types' + +export type GuardarianBillingInfo = { + email?: string + firstName?: string + lastName?: string + dob?: Date + country?: string + city?: string + street?: string + apartment?: string + postIndex?: string +} + +type GuardarianModalBillingInfoProps = { + onSubmit: (form: GuardarianBillingInfo) => void + setActionButtonHandler: (fn: () => void | Promise) => void +} + +export const GuardarianModalBillingInfoStep = ({ + onSubmit, + setActionButtonHandler, +}: GuardarianModalBillingInfoProps) => { + const { + control, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + country: 'feasf', + city: 'feasf', + apartment: 'feasf', + lastName: 'feasf', + firstName: 'feasf', + email: 'feasf@fesfs.com', + street: 'feasf', + postIndex: 'feasf', + dob: new Date('11.03.2000'), + }, + }) + const { data: countries } = useQuery({ + queryKey: 'guardarianCountries', + queryFn: () => guardarianService.getSupportedCountries(), + }) + + const countriesOptions: ComboBoxProps<{ value: string } & GuardarianCountry>['items'] = + useMemo( + () => + countries?.map((country) => ({ + ...country, + name: country.country_name, + label: country.country_name, + value: country.code_iso_alpha_3, + })), + [countries] + ) ?? [] + + useMountEffect(() => { + setActionButtonHandler(handleSubmit(onSubmit)) + }) + + return ( + + + Billing information + + + Please fill out the form to perform a transaction. We don't require KYC to transactions under 700 USD. + + { + if (/^\S+@\S+\.\S+$/.test(value ?? '')) { + return true + } + + return 'Enter valid email address.' + }, + }} + render={({ field: { onChange, value } }) => ( + + + + )} + /> + + + ( + + + + )} + /> + ( + + + + )} + /> + + + + { + if (value instanceof Date) { + if (value.getTime() > Date.now()) { + return 'You cannot be born in the future, can ya?' + } + + const nowMinusAdult = new Date() + nowMinusAdult.setFullYear(nowMinusAdult.getFullYear() - 18) + if (value.getTime() > nowMinusAdult.getTime()) { + return 'Hmmm, you seem too young to take part...' + } + + return true + } + + return 'Invalid date' + }, + }} + render={({ field: { onChange, value } }) => ( + + + + )} + /> + { + const selected = countriesOptions?.find((row) => row.code_iso_alpha_3 === value) + return ( + + } + value={selected?.country_name ?? value ?? ''} + selectedItem={selected} + placeholder="Poland" + onSelectedItemChange={(e) => { + onChange(e?.code_iso_alpha_3) + }} + items={countriesOptions} + /> + + ) + }} + /> + + + + ( + + + + )} + /> + ( + + + + )} + /> + + + + ( + + + + )} + /> + + ( + + + + )} + /> + + + ) +} diff --git a/packages/atlas/src/config/env.ts b/packages/atlas/src/config/env.ts index a6ea858da4..ff7cfae904 100644 --- a/packages/atlas/src/config/env.ts +++ b/packages/atlas/src/config/env.ts @@ -47,4 +47,6 @@ export const JOY_PRICE_SERVICE_URL = readEnv('PRICE_SERVICE_URL', false, true) export const USER_LOCATION_SERVICE_URL = readEnv('GEOLOCATION_SERVICE_URL', true, true) export const HCAPTCHA_SITE_KEY = readEnv('HCAPTCHA_SITE_KEY', false, true) export const CHANGENOW_PUBLIC_API_KEY = readEnv('CHANGENOW_PUBLIC_API_KEY', false, true) +export const GUARDARIAN_PUBLIC_API_KEY = readEnv('GUARDARIAN_PUBLIC_API_KEY', false, true) + export const FORCE_MAINTENANCE = readEnv('FORCE_MAINTENANCE', false, true) diff --git a/packages/atlas/src/utils/GuardarianService/GuardarianService.ts b/packages/atlas/src/utils/GuardarianService/GuardarianService.ts new file mode 100644 index 0000000000..4a609df2e6 --- /dev/null +++ b/packages/atlas/src/utils/GuardarianService/GuardarianService.ts @@ -0,0 +1,170 @@ +import { axiosInstance } from '@/api/axios' +import { GUARDARIAN_PUBLIC_API_KEY } from '@/config/env' + +import { GuardarianCountry, GuardarianCurrencies, GuardarianEstimation } from './GuardarianService.types' + +type TransactionType = 'sell' | 'buy' + +// export const JOYSTREAM_CHANGENOW_TICKER = 'joystream' +// export const JOYSTREAM_CHANGENOW_LEGACY_TICKER = 'joy' +// const JOYSTREAM_CHANGENOW_NETWORK = 'joy' +const GUARDARIAN_URL = 'https://api-payments.guardarian.com/v1' + +class GuardarianService { + private _apiKey + private _currencies: GuardarianCurrencies | null = null + private _countries: GuardarianCountry[] = [] + + constructor(apiKey: string) { + this._apiKey = apiKey + } + + async getSupportedCountries() { + if (this._countries.length) { + return this._countries + } + + const response = await axiosInstance.get(`${GUARDARIAN_URL}/countries`) + + if (response.data) { + this._countries = response.data + } + + return response.data + } + + async getAvailableCurrencies() { + if (this._currencies) { + return this._currencies + } + + const response = await axiosInstance.get(`${GUARDARIAN_URL}/currencies?available=true`) + + if (response.data) { + this._currencies = response.data + } + + return response.data + } + + sanitizeApiErrorMessage(message: string) { + return message.replace('amountTo', 'Amount').replace('amountFrom', 'Amount') + } + + // getExchangeRange(currency: Currency, type: TransactionType) { + // const isSellingJoy = type === 'sell' + // const fromCurrency = isSellingJoy ? JOYSTREAM_CHANGENOW_TICKER : currency.ticker + // const toCurrency = isSellingJoy ? currency.ticker : JOYSTREAM_CHANGENOW_TICKER + // const fromNetwork = isSellingJoy ? JOYSTREAM_CHANGENOW_NETWORK : currency.network + // const toNetwork = isSellingJoy ? currency.network : JOYSTREAM_CHANGENOW_NETWORK + // + // return axiosInstance.get( + // `https://api.changenow.io/v2/exchange/range?fromCurrency=${fromCurrency}&toCurrency=${toCurrency}&fromNetwork=${fromNetwork}&toNetwork=${toNetwork}&flow=fixed-rate`, + // { + // headers: { + // 'x-changenow-api-key': this._apiKey, + // }, + // } + // ) + // } + + // validateCurrencyAddress(currency: string, address: string) { + // return axiosInstance.get<{ result: boolean; message: string | null }>( + // `https://api.changenow.io/v2/validate/address?currency=${currency}&address=${address}`, + // { + // headers: { + // 'x-changenow-api-key': this._apiKey, + // }, + // } + // ) + // } + + getEstimatedExchangeAmount( + amount: number, + from: { + currency: string + network?: string + }, + to: { + currency: string + network?: string + }, + direction: 'direct' | 'reverse' = 'direct' + ) { + const toCurrency = to.currency + const fromCurrency = from.currency + const fromNetwork = from.network + const toNetwork = to.network + return axiosInstance.get( + `${GUARDARIAN_URL}/estimate?from_currency=${fromCurrency}&to_currency=${toCurrency}&${ + direction === 'direct' ? 'from_amount' : 'to_amount' + }=${amount}&type=${direction}${toNetwork ? `&to_network=${toNetwork}` : ''}${ + fromNetwork ? `&from_network=${fromNetwork}` : '' + }`, + { + headers: { + 'x-api-key': this._apiKey, + }, + } + ) + } + + // createExchangeTransaction({ + // refundAddress, + // addressToBePaid, + // currency, + // amount, + // type, + // contactEmail, + // rateId, + // userId, + // }: { + // amount: number + // currency: Currency + // addressToBePaid: string + // refundAddress?: string + // type: TransactionType + // contactEmail?: string + // rateId: string + // userId?: string + // }) { + // const isSellingJoy = type === 'sell' + // const fromCurrency = isSellingJoy ? JOYSTREAM_CHANGENOW_TICKER : currency.ticker + // const toCurrency = isSellingJoy ? currency.ticker : JOYSTREAM_CHANGENOW_TICKER + // const fromNetwork = isSellingJoy ? JOYSTREAM_CHANGENOW_NETWORK : currency.network + // const toNetwork = isSellingJoy ? currency.network : JOYSTREAM_CHANGENOW_NETWORK + // + // return axiosInstance.post( + // 'https://api.changenow.io/v2/exchange', + // { + // fromCurrency, + // fromNetwork, + // toCurrency, + // toNetwork, + // contactEmail, + // fromAmount: String(amount), + // address: addressToBePaid, + // refundAddress, + // flow: 'fixed-rate', + // type: 'direct', + // rateId, + // userId, + // }, + // { + // headers: { + // 'x-changenow-api-key': this._apiKey, + // }, + // } + // ) + // } + + // getTransactionStatus(id: string) { + // return axiosInstance.get(`https://api.changenow.io/v2/exchange/by-id?id=${id}`, { + // headers: { + // 'x-changenow-api-key': this._apiKey, + // }, + // }) + // } +} + +export const guardarianService = new GuardarianService(GUARDARIAN_PUBLIC_API_KEY) diff --git a/packages/atlas/src/utils/GuardarianService/GuardarianService.types.ts b/packages/atlas/src/utils/GuardarianService/GuardarianService.types.ts new file mode 100644 index 0000000000..a978167d92 --- /dev/null +++ b/packages/atlas/src/utils/GuardarianService/GuardarianService.types.ts @@ -0,0 +1,75 @@ +export type GuardarianCryptoCurrency = { + id: number + currency_type: string + ticker: string + name: string + enabled: boolean + enabled_subscription: boolean + has_external_id: boolean + is_featured: boolean + is_stable: boolean + is_available: boolean + logo_url: string + payment_methods: GuardarianPaymentMethod[] + block_explorer_url_mask: string + default_exchange_value: number + networks: GuardarianNetwork[] +} + +export type GuardarianFiatCurrency = { + id: string + currency_type: string + ticker: string + name: string + enabled: boolean + enabled_subscription: boolean + has_external_id: boolean + is_featured: boolean + is_stable: boolean + is_available: boolean + logo_url: string + payment_methods: GuardarianPaymentMethod[] + block_explorer_url_mask: string + default_exchange_value: number + networks: GuardarianNetwork[] +} + +export type GuardarianPaymentMethod = { + type: string + payment_category: string + deposit_enabled: boolean + withdrawal_enabled: boolean +} + +export type GuardarianNetwork = { + name: string + network: string + block_explorer_url_mask: string + token_contract: string + enabled_subscription: boolean + payment_methods: GuardarianPaymentMethod + network_fee: number +} + +export type GuardarianCountry = { + id: number + country_name: string + code_iso_alpha_2: string + code_iso_alpha_3: string + code_iso_numeric: string + supported: boolean +} + +export type GuardarianCurrencies = { + crypto_currencies: GuardarianCryptoCurrency[] + fiat_currencies: GuardarianCryptoCurrency[] +} + +export type GuardarianEstimation = { + to_currency: string + from_currency: string + to_network: string | null + from_network: string | null + value: string + estimated_exchange_rate: string +} diff --git a/packages/atlas/src/utils/GuardarianService/index.ts b/packages/atlas/src/utils/GuardarianService/index.ts new file mode 100644 index 0000000000..68374f20ee --- /dev/null +++ b/packages/atlas/src/utils/GuardarianService/index.ts @@ -0,0 +1 @@ +export * from './GuardarianService' From 6a8fc11829bf38fc4eef414937d6aa3a89f6caf3 Mon Sep 17 00:00:00 2001 From: ikprk Date: Tue, 25 Jun 2024 08:58:39 +0200 Subject: [PATCH 2/8] Form step --- .../steps/GuardarianModalFormStep.tsx | 316 ++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 packages/atlas/src/components/GuardarianModal/steps/GuardarianModalFormStep.tsx diff --git a/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalFormStep.tsx b/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalFormStep.tsx new file mode 100644 index 0000000000..1c46c5ff9a --- /dev/null +++ b/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalFormStep.tsx @@ -0,0 +1,316 @@ +import styled from '@emotion/styled' +import { isAxiosError } from 'axios' +import { DebouncedFunc, debounce } from 'lodash-es' +import { useMemo, useRef, useState } from 'react' +import { Controller, useForm } from 'react-hook-form' +import { useQuery } from 'react-query' + +import { SvgActionChangePrice } from '@/assets/icons' +import { CurrencyInput } from '@/components/ChangeNowModal/steps/FormStep' +import { FlexBox } from '@/components/FlexBox' +import { Text } from '@/components/Text' +import { Button } from '@/components/_buttons/Button' +import { ComboBoxProps } from '@/components/_inputs/ComboBox' +import { FormField } from '@/components/_inputs/FormField' +import { Select } from '@/components/_inputs/Select' +import { useMountEffect } from '@/hooks/useMountEffect' +import { useSnackbar } from '@/providers/snackbars' +import { square } from '@/styles' +import { guardarianService } from '@/utils/GuardarianService' +import { GuardarianCurrencies } from '@/utils/GuardarianService/GuardarianService.types' +import { SentryLogger } from '@/utils/logs' + +export type GuardarianForm = { + from: { + currency?: string // currency?: GuardarianFiatCurrency | GuardarianCryptoCurrency + amount?: number + network?: string + } + to: { + currency?: string // currency?: GuardarianFiatCurrency | GuardarianCryptoCurrency + amount?: number + network?: string + } + isBuyingCrypto?: boolean + serverError?: string +} + +type GuardarianModalFormProps = { + onSubmit: (form: GuardarianForm) => void + setActionButtonHandler: (fn: () => void | Promise) => void +} + +export const GuardarianModalFormStep = ({ onSubmit, setActionButtonHandler }: GuardarianModalFormProps) => { + const { displaySnackbar } = useSnackbar() + const { + control, + handleSubmit, + formState: { errors }, + watch, + setValue, + clearErrors, + setError, + getValues, + } = useForm({ + defaultValues: { + from: { + amount: undefined, + currency: undefined, + network: undefined, + }, + to: { + amount: undefined, + currency: undefined, + network: undefined, + }, + isBuyingCrypto: true, + }, + }) + const isBuyingCrypto = watch('isBuyingCrypto') + const debouncedExchangeEstimation = useRef Promise + > | null>(null) + const [isLoadingRate, setIsLoadingRate] = useState<'to' | 'from' | null>(null) + const { data: currencies } = useQuery({ + queryKey: 'guardarianCurrencies', + queryFn: () => guardarianService.getAvailableCurrencies(), + onSuccess: () => { + debouncedExchangeEstimation.current = debounce(async (amount: number, direction: 'from' | 'to') => { + const isDirectionFrom = direction === 'from' + setIsLoadingRate(isDirectionFrom ? 'to' : 'from') + const { to, from } = getValues() + if (!to.currency || !from.currency || !from.amount || !to.amount) { + setIsLoadingRate(null) + return + } + + try { + const { data } = await guardarianService.getEstimatedExchangeAmount( + amount, + from as { currency: string }, + to as { currency: string }, + isDirectionFrom ? 'direct' : 'reverse' + ) + + const { value: estimation } = data + + setValue( + // Not sure why, buy using `xx.amount` wasn't updating the component, hence the spread + isDirectionFrom ? 'to' : 'from', + { + ...(isDirectionFrom ? to : from), + amount: +estimation, + }, + { shouldTouch: true } + ) + } catch (e) { + if (isAxiosError(e) && e.response?.data.message && e.response.status === 400) { + setError('serverError', { + message: e.response.data.message, + type: direction, + }) + return + } + SentryLogger.error('Failed to get exchange estimation: ', 'GuardarianModal:FormStep', e) + + displaySnackbar({ + title: 'Failed to get rate estimation', + iconType: 'error', + }) + } finally { + setIsLoadingRate(null) + } + }, 500) + }, + }) + + const fiatOptions: ComboBoxProps<{ value: string } & GuardarianCurrencies['fiat_currencies'][number]>['items'] = + useMemo( + () => + currencies?.fiat_currencies.map((fiat) => ({ + ...fiat, + label: fiat.name, + value: fiat.ticker, + })), + [currencies?.fiat_currencies] + ) ?? [] + + const cryptoOptions: ComboBoxProps<{ value: string } & GuardarianCurrencies['crypto_currencies'][number]>['items'] = + useMemo( + () => + currencies?.crypto_currencies.map((crypto) => ({ + ...crypto, + label: crypto.name, + value: crypto.ticker, + })), + [currencies?.crypto_currencies] + ) ?? [] + + const cryptoCurrnecy = watch(isBuyingCrypto ? 'to.currency' : 'from.currency') + const cryptoNetworks = cryptoOptions + .find((c) => c.ticker === cryptoCurrnecy) + ?.networks.map((n) => ({ ...n, label: n.network, value: n.network })) + + useMountEffect(() => { + setActionButtonHandler(handleSubmit(onSubmit)) + }) + + return ( + + + Transaction details + + + Please fill out the form to perform a transaction. We don't require KYC to transactions under 700 USD. + + + { + return ( + <> + + { + onChange({ ...value, amount }) + clearErrors() + if (amount) { + debouncedExchangeEstimation.current?.(amount, 'from') + } + }} + onCurrencySelect={(currency) => { + onChange({ ...value, currency, network: undefined }) + clearErrors() + if (value.amount) { + debouncedExchangeEstimation.current?.(value.amount, 'from') + } + }} + /> + + {(cryptoNetworks?.length ?? 0) > 1 && !isBuyingCrypto ? ( + { + clearErrors() + onChange({ ...value, network }) + const from = watch('from') + if (value.amount) { + debouncedExchangeEstimation.current?.(value.amount, 'to') + } + + if (from.amount) { + debouncedExchangeEstimation.current?.(from.amount, 'from') + } + }} + items={cryptoNetworks!} + /> + ) : null} + + ) + }} + /> + + {errors.serverError?.message ? ( + + {errors.serverError.message} + + ) : null} + + ) +} + +const SwapButton = styled(Button)` + position: absolute; + + svg { + ${square(24)} + } +` From 62cea6a1505602529f02655b8b592d011f9b9900 Mon Sep 17 00:00:00 2001 From: ikprk Date: Wed, 26 Jun 2024 19:28:19 +0200 Subject: [PATCH 3/8] Add post form steps --- .../GuardarianModal/GuardarianModal.tsx | 51 ++++++- .../GuardarianModal/GuardarianModal.types.ts | 4 + .../steps/GuardarianProgressModal.tsx | 54 ++++++++ .../GuardarianService/GuardarianService.ts | 130 ++++++++++-------- .../GuardarianService.types.ts | 80 +++++++++++ 5 files changed, 261 insertions(+), 58 deletions(-) create mode 100644 packages/atlas/src/components/GuardarianModal/steps/GuardarianProgressModal.tsx diff --git a/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx b/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx index a710c86d44..5815634c2f 100644 --- a/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx +++ b/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx @@ -1,8 +1,13 @@ +import styled from '@emotion/styled' import { useRef, useState } from 'react' +import ReactDOM from 'react-dom' + +import { guardarianService } from '@/utils/GuardarianService' import { GuardarianModalSteps } from './GuardarianModal.types' import { GuardarianBillingInfo, GuardarianModalBillingInfoStep } from './steps/GuardarianModalBillingInfoStep' import { GuardarianForm, GuardarianModalFormStep } from './steps/GuardarianModalFormStep' +import { GuardarianProgressModal } from './steps/GuardarianProgressModal' import { Button } from '../_buttons/Button' import { DialogModal } from '../_overlays/DialogModal' @@ -14,6 +19,8 @@ type GuardarianData = GuardarianBillingInfo & GuardarianForm export const GuardarianModal = () => { const [step, setStep] = useState(GuardarianModalSteps.INFO) + const [checkoutUrl, setCheckoutUrl] = useState('') + const [transactionId, setTransactionId] = useState('') const [primaryAction, setPrimaryAction] = useState(undefined) const formRef = useRef({ from: { @@ -53,16 +60,56 @@ export const GuardarianModal = () => { ) : null} {step === GuardarianModalSteps.FORM ? ( { + onSubmit={async (data) => { formRef.current = { ...formRef.current, ...data, } - // setStep(GuardarianModalSteps.FORM) + const { from, to, ...billingInfo } = formRef.current + + const response = await guardarianService.createTransaction({ + amount: from.amount ?? 0, + from: from as { currency: string }, + to: to as { currency: string }, + billingInfo: { + ...billingInfo, + }, + }) + + setCheckoutUrl(response.data.redirect_url) + + setStep(GuardarianModalSteps.PROGRESS) }} setActionButtonHandler={(fn) => setPrimaryAction(() => fn)} /> ) : null} + {step === GuardarianModalSteps.FAILED ?
failed
: null} + {step === GuardarianModalSteps.SUCCESS ?
success
: null} + {step === GuardarianModalSteps.TIMEOUT ?
transaction timeouted
: null} + {step === GuardarianModalSteps.PROGRESS && transactionId ? ( + + ) : null} + {checkoutUrl + ? ReactDOM.createPortal( + + + , + document.body + ) + : null} ) } + +const FrameBox = styled.div` + position: absolute; + inset: 0; + display: grid; + place-items: center; +` + +export const StyledFrame = styled.iframe` + width: 90vw; + height: 90vh; + z-index: 9999; +` diff --git a/packages/atlas/src/components/GuardarianModal/GuardarianModal.types.ts b/packages/atlas/src/components/GuardarianModal/GuardarianModal.types.ts index ea1307d4ca..090b0e0abd 100644 --- a/packages/atlas/src/components/GuardarianModal/GuardarianModal.types.ts +++ b/packages/atlas/src/components/GuardarianModal/GuardarianModal.types.ts @@ -1,4 +1,8 @@ export enum GuardarianModalSteps { INFO, FORM, + PROGRESS, + TIMEOUT, + FAILED, + SUCCESS, } diff --git a/packages/atlas/src/components/GuardarianModal/steps/GuardarianProgressModal.tsx b/packages/atlas/src/components/GuardarianModal/steps/GuardarianProgressModal.tsx new file mode 100644 index 0000000000..c517037fce --- /dev/null +++ b/packages/atlas/src/components/GuardarianModal/steps/GuardarianProgressModal.tsx @@ -0,0 +1,54 @@ +import { useRef } from 'react' +import { useQuery } from 'react-query' + +import { FlexBox } from '@/components/FlexBox' +import { Text } from '@/components/Text' +import { TextButton } from '@/components/_buttons/Button' +import { Loader } from '@/components/_loaders/Loader' +import { guardarianService } from '@/utils/GuardarianService' + +import { GuardarianModalSteps } from '../GuardarianModal.types' + +type GuardarianProgressModalTypes = { + transactionId: string + goToStep: (step: GuardarianModalSteps) => void +} + +export const GuardarianProgressModal = ({ transactionId, goToStep }: GuardarianProgressModalTypes) => { + const mountTimestamp = useRef(Date.now()) + + const { data } = useQuery( + ['getTransactionStatus', transactionId], + () => guardarianService.getTransactionStatus(transactionId).then((res) => res.data), + { + refetchInterval: 30_000, + onSuccess: (data) => { + if (['failed', 'cancelled'].includes(data.status)) { + goToStep(GuardarianModalSteps.FAILED) + } + + if (data.status === 'finished') { + goToStep(GuardarianModalSteps.SUCCESS) + } + + // if transaction doesn't fail or succeed after 26 min + // the issue might be on API side, we should not waste more time waiting + if ( + data.status === 'expired' || + (data.status.startsWith('waiting') && Date.now() - mountTimestamp.current > 26 * 60 * 1000) + ) { + goToStep(GuardarianModalSteps.TIMEOUT) + } + }, + } + ) + return ( + + + + Please continue in the popup. If you don't see one click on the link below + + Finalize your transaction... + + ) +} diff --git a/packages/atlas/src/utils/GuardarianService/GuardarianService.ts b/packages/atlas/src/utils/GuardarianService/GuardarianService.ts index 4a609df2e6..0eea33b370 100644 --- a/packages/atlas/src/utils/GuardarianService/GuardarianService.ts +++ b/packages/atlas/src/utils/GuardarianService/GuardarianService.ts @@ -1,7 +1,12 @@ import { axiosInstance } from '@/api/axios' import { GUARDARIAN_PUBLIC_API_KEY } from '@/config/env' -import { GuardarianCountry, GuardarianCurrencies, GuardarianEstimation } from './GuardarianService.types' +import { + GuardarianCountry, + GuardarianCreateTransactionResponse, + GuardarianCurrencies, + GuardarianEstimation, +} from './GuardarianService.types' type TransactionType = 'sell' | 'buy' @@ -109,62 +114,75 @@ class GuardarianService { ) } - // createExchangeTransaction({ - // refundAddress, - // addressToBePaid, - // currency, - // amount, - // type, - // contactEmail, - // rateId, - // userId, - // }: { - // amount: number - // currency: Currency - // addressToBePaid: string - // refundAddress?: string - // type: TransactionType - // contactEmail?: string - // rateId: string - // userId?: string - // }) { - // const isSellingJoy = type === 'sell' - // const fromCurrency = isSellingJoy ? JOYSTREAM_CHANGENOW_TICKER : currency.ticker - // const toCurrency = isSellingJoy ? currency.ticker : JOYSTREAM_CHANGENOW_TICKER - // const fromNetwork = isSellingJoy ? JOYSTREAM_CHANGENOW_NETWORK : currency.network - // const toNetwork = isSellingJoy ? currency.network : JOYSTREAM_CHANGENOW_NETWORK - // - // return axiosInstance.post( - // 'https://api.changenow.io/v2/exchange', - // { - // fromCurrency, - // fromNetwork, - // toCurrency, - // toNetwork, - // contactEmail, - // fromAmount: String(amount), - // address: addressToBePaid, - // refundAddress, - // flow: 'fixed-rate', - // type: 'direct', - // rateId, - // userId, - // }, - // { - // headers: { - // 'x-changenow-api-key': this._apiKey, - // }, - // } - // ) - // } + createTransaction({ + from, + to, + amount, + extraId, + billingInfo, + }: { + amount: number + from: { + currency: string + network?: string + } + to: { + currency: string + network?: string + } + extraId?: string + billingInfo: { + email: string + country: string + region: string + city: string + street: string + apartment: string + postIndex: string + firstName: string + lastName: string + dob: string + } + }) { + console.log('inside', from, to) + return axiosInstance.post( + `${GUARDARIAN_URL}/transaction`, + { + from_amount: amount, + from_currency: from.currency, + to_currency: to.currency, + from_network: from.network, + to_network: to.network, + payout_info: { + extra_id: extraId, + }, + billing_info: { + country_alpha_2: billingInfo.country, + region: billingInfo.region, + city: billingInfo.city, + street_address: billingInfo.street, + apt_number: billingInfo.apartment, + post_index: billingInfo.postIndex, + first_name: billingInfo.firstName, + last_name: billingInfo.lastName, + date_of_birthday: billingInfo.dob, + }, + }, + { + headers: { + 'x-api-key': this._apiKey, + }, + } + ) + } - // getTransactionStatus(id: string) { - // return axiosInstance.get(`https://api.changenow.io/v2/exchange/by-id?id=${id}`, { - // headers: { - // 'x-changenow-api-key': this._apiKey, - // }, - // }) - // } + getTransactionStatus(id: string) { + return axiosInstance.get(`${GUARDARIAN_URL}/transaction/${id}`, { + headers: { + 'x-api-key': this._apiKey, + }, + }) + } } export const guardarianService = new GuardarianService(GUARDARIAN_PUBLIC_API_KEY) diff --git a/packages/atlas/src/utils/GuardarianService/GuardarianService.types.ts b/packages/atlas/src/utils/GuardarianService/GuardarianService.types.ts index a978167d92..76930c1bed 100644 --- a/packages/atlas/src/utils/GuardarianService/GuardarianService.types.ts +++ b/packages/atlas/src/utils/GuardarianService/GuardarianService.types.ts @@ -73,3 +73,83 @@ export type GuardarianEstimation = { value: string estimated_exchange_rate: string } + +export type GuardarianCreateTransactionResponse = { + id: number + status: string + email: string + errors: Error[] + status_details: string | null + from_currency: string + initial_from_currency: string + from_network: string + from_currency_with_network: string + from_amount: number + deposit_type: string + payout_type: string + expected_from_amount: number + initial_expected_from_amount: number + to_currency: string + to_network: string + to_currency_with_network: string + to_amount: number + output_hash: string + expected_to_amount: number + location: string + created_at: string + updated_at: string + partner_id: number + external_partner_link_id: string + from_amount_in_eur: number + customer_payout_address_changeable: boolean + estimate_breakdown: EstimateBreakdown + payout: Payout + deposit_payment_category: string + payout_payment_category: string + redirect_url: string + preauth_token: string + skip_choose_payout_address: boolean + skip_choose_payment_category: boolean +} + +export type Error = { + code: string + reason: string +} + +export type EstimateBreakdown = { + toAmount: number + fromAmount: number + serviceFees: ServiceFee[] + convertedAmount: ConvertedAmount + estimatedExchangeRate: number + networkFee: NetworkFee + partnerFee: PartnerFee +} + +export type ServiceFee = { + name: string + amount: number + currency: string +} + +export type ConvertedAmount = { + amount: number + currency: string +} + +export type NetworkFee = { + amount: number + currency: string +} + +export type PartnerFee = { + amount: number + currency: string + percentage: string +} + +export type Payout = { + address: string + extra_id: string +} From c0b26598182a373b23f4123c6aee97c9427fbee9 Mon Sep 17 00:00:00 2001 From: ikprk Date: Wed, 26 Jun 2024 19:44:14 +0200 Subject: [PATCH 4/8] Minor improvements --- .../GuardarianModal/GuardarianModal.tsx | 30 +++++++++++++++---- .../steps/GuardarianModalBillingInfoStep.tsx | 20 +++++++------ .../steps/GuardarianModalFormStep.tsx | 2 +- .../steps/GuardarianProgressModal.tsx | 14 +++++---- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx b/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx index 5815634c2f..6a17cdab2b 100644 --- a/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx +++ b/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx @@ -2,6 +2,7 @@ import styled from '@emotion/styled' import { useRef, useState } from 'react' import ReactDOM from 'react-dom' +import { SvgActionClose } from '@/assets/icons' import { guardarianService } from '@/utils/GuardarianService' import { GuardarianModalSteps } from './GuardarianModal.types' @@ -20,6 +21,7 @@ type GuardarianData = GuardarianBillingInfo & GuardarianForm export const GuardarianModal = () => { const [step, setStep] = useState(GuardarianModalSteps.INFO) const [checkoutUrl, setCheckoutUrl] = useState('') + const [forceCloseFrame, setForceCloseFrame] = useState(false) const [transactionId, setTransactionId] = useState('') const [primaryAction, setPrimaryAction] = useState(undefined) const formRef = useRef({ @@ -38,10 +40,17 @@ export const GuardarianModal = () => { size={step === GuardarianModalSteps.FORM ? 'small' : 'medium'} title="Guardarian" show - primaryButton={{ - text: 'Continue', - onClick: () => primaryAction?.(), - }} + primaryButton={ + [GuardarianModalSteps.FORM, GuardarianModalSteps.INFO].includes(step) + ? { + text: 'Continue', + onClick: () => primaryAction?.(), + } + : { + text: 'Close', + onClick: undefined, + } + } additionalActionsNode={ [GuardarianModalSteps.INFO].includes(step) ? : null } @@ -77,6 +86,7 @@ export const GuardarianModal = () => { }) setCheckoutUrl(response.data.redirect_url) + setTransactionId(String(response.data.id)) setStep(GuardarianModalSteps.PROGRESS) }} @@ -87,11 +97,12 @@ export const GuardarianModal = () => { {step === GuardarianModalSteps.SUCCESS ?
success
: null} {step === GuardarianModalSteps.TIMEOUT ?
transaction timeouted
: null} {step === GuardarianModalSteps.PROGRESS && transactionId ? ( - + ) : null} - {checkoutUrl + {checkoutUrl && !forceCloseFrame ? ReactDOM.createPortal( + setForceCloseFrame(true)} variant="warning" icon={} /> , document.body @@ -108,6 +119,13 @@ const FrameBox = styled.div` place-items: center; ` +export const FrameCloseButton = styled(Button)` + z-index: 111111111111111111111; + position: absolute; + top: 10px; + right: 10px; +` + export const StyledFrame = styled.iframe` width: 90vw; height: 90vh; diff --git a/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalBillingInfoStep.tsx b/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalBillingInfoStep.tsx index 8397236c0a..c99248e7dc 100644 --- a/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalBillingInfoStep.tsx +++ b/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalBillingInfoStep.tsx @@ -16,6 +16,7 @@ export type GuardarianBillingInfo = { email?: string firstName?: string lastName?: string + region?: string dob?: Date country?: string city?: string @@ -39,15 +40,16 @@ export const GuardarianModalBillingInfoStep = ({ formState: { errors }, } = useForm({ defaultValues: { - country: 'feasf', - city: 'feasf', - apartment: 'feasf', - lastName: 'feasf', - firstName: 'feasf', - email: 'feasf@fesfs.com', - street: 'feasf', - postIndex: 'feasf', - dob: new Date('11.03.2000'), + country: '', + city: '', + apartment: '', + lastName: '', + firstName: '', + region: '', + email: '', + street: '', + postIndex: '', + dob: undefined, }, }) const { data: countries } = useQuery({ diff --git a/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalFormStep.tsx b/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalFormStep.tsx index 1c46c5ff9a..58571168fd 100644 --- a/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalFormStep.tsx +++ b/packages/atlas/src/components/GuardarianModal/steps/GuardarianModalFormStep.tsx @@ -79,7 +79,7 @@ export const GuardarianModalFormStep = ({ onSubmit, setActionButtonHandler }: Gu const isDirectionFrom = direction === 'from' setIsLoadingRate(isDirectionFrom ? 'to' : 'from') const { to, from } = getValues() - if (!to.currency || !from.currency || !from.amount || !to.amount) { + if (!to.currency || !from.currency || !from.amount) { setIsLoadingRate(null) return } diff --git a/packages/atlas/src/components/GuardarianModal/steps/GuardarianProgressModal.tsx b/packages/atlas/src/components/GuardarianModal/steps/GuardarianProgressModal.tsx index c517037fce..0e278af87b 100644 --- a/packages/atlas/src/components/GuardarianModal/steps/GuardarianProgressModal.tsx +++ b/packages/atlas/src/components/GuardarianModal/steps/GuardarianProgressModal.tsx @@ -11,13 +11,14 @@ import { GuardarianModalSteps } from '../GuardarianModal.types' type GuardarianProgressModalTypes = { transactionId: string + redirectUrl: string goToStep: (step: GuardarianModalSteps) => void } -export const GuardarianProgressModal = ({ transactionId, goToStep }: GuardarianProgressModalTypes) => { +export const GuardarianProgressModal = ({ transactionId, goToStep, redirectUrl }: GuardarianProgressModalTypes) => { const mountTimestamp = useRef(Date.now()) - const { data } = useQuery( + useQuery( ['getTransactionStatus', transactionId], () => guardarianService.getTransactionStatus(transactionId).then((res) => res.data), { @@ -42,13 +43,14 @@ export const GuardarianProgressModal = ({ transactionId, goToStep }: GuardarianP }, } ) + return ( - - + + - Please continue in the popup. If you don't see one click on the link below + Please continue in the popup. If you don't see one click on the link below. - Finalize your transaction... + Finalize your transaction... ) } From eed3bf06ee287db460fae6745261ef9575479704 Mon Sep 17 00:00:00 2001 From: ikprk Date: Wed, 26 Jun 2024 20:06:39 +0200 Subject: [PATCH 5/8] Add guadarian modal button to sidebar --- packages/atlas/src/.env | 1 + .../components/GuardarianModal/GuardarianModal.tsx | 11 +++++------ .../providers/transactions/transactions.manager.tsx | 5 ++++- .../src/providers/transactions/transactions.store.ts | 8 ++++++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/atlas/src/.env b/packages/atlas/src/.env index 4ed1ec82cd..42707c2b09 100644 --- a/packages/atlas/src/.env +++ b/packages/atlas/src/.env @@ -17,6 +17,7 @@ VITE_ASSET_LOGS_URL= VITE_GEOLOCATION_SERVICE_URL=https://geolocation.joystream.org VITE_HCAPTCHA_SITE_KEY=41cae189-7676-4f6b-aa56-635be26d3ceb VITE_CHANGENOW_PUBLIC_API_KEY=0d8a58104f82b860a70e9460bccf62ae1e0fca4a93fd7e0c27c90448187b988f +VITE_GUARDARIAN_PUBLIC_API_KEY=8f6319f6-001e-40c1-b772-ad18dd7066c0 VITE_WALLET_CONNECT_PROJECT_ID=33b2609463e399daee8c51726546c8dd # YPP configuration diff --git a/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx b/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx index 6a17cdab2b..b31d915dd1 100644 --- a/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx +++ b/packages/atlas/src/components/GuardarianModal/GuardarianModal.tsx @@ -18,7 +18,7 @@ export type SetActionButtonHandler = (setLoading?: LoadingSetter) => void | Prom type GuardarianData = GuardarianBillingInfo & GuardarianForm -export const GuardarianModal = () => { +export const GuardarianModal = ({ onClose }: { onClose: () => void }) => { const [step, setStep] = useState(GuardarianModalSteps.INFO) const [checkoutUrl, setCheckoutUrl] = useState('') const [forceCloseFrame, setForceCloseFrame] = useState(false) @@ -46,13 +46,12 @@ export const GuardarianModal = () => { text: 'Continue', onClick: () => primaryAction?.(), } - : { - text: 'Close', - onClick: undefined, - } + : undefined } additionalActionsNode={ - [GuardarianModalSteps.INFO].includes(step) ? : null + } > {step === GuardarianModalSteps.INFO ? ( diff --git a/packages/atlas/src/providers/transactions/transactions.manager.tsx b/packages/atlas/src/providers/transactions/transactions.manager.tsx index 8ee3c91069..ff1992b064 100644 --- a/packages/atlas/src/providers/transactions/transactions.manager.tsx +++ b/packages/atlas/src/providers/transactions/transactions.manager.tsx @@ -2,6 +2,7 @@ import { FC, useEffect, useRef, useState } from 'react' import { useQueryNodeStateSubscription } from '@/api/hooks/queryNode' import { ChangeNowModal } from '@/components/ChangeNowModal/ChangeNowModal' +import { GuardarianModal } from '@/components/GuardarianModal' import { TransactionModal } from '@/components/_overlays/TransactionModal' import { ExtrinsicStatus } from '@/joystream-lib/types' import { useSnackbar } from '@/providers/snackbars' @@ -15,7 +16,8 @@ export const TransactionsManager: FC = () => { const { transactions, changeNowModal, - actions: { removeOldBlockActions, removeTransaction, setChangeNowModal }, + guardarianModal, + actions: { removeOldBlockActions, removeTransaction, setChangeNowModal, setGuardarianModal }, } = useTransactionManagerStore((state) => state) const userWalletName = useWalletStore((state) => state.wallet?.title) @@ -103,6 +105,7 @@ export const TransactionsManager: FC = () => { /> )} {changeNowModal ? setChangeNowModal(null)} /> : null} + {guardarianModal ? setGuardarianModal(false)} /> : null} ) } diff --git a/packages/atlas/src/providers/transactions/transactions.store.ts b/packages/atlas/src/providers/transactions/transactions.store.ts index 271175206f..019879e2fb 100644 --- a/packages/atlas/src/providers/transactions/transactions.store.ts +++ b/packages/atlas/src/providers/transactions/transactions.store.ts @@ -12,6 +12,7 @@ type TransactionManagerStoreState = { blockActions: ProcessedBlockAction[] transactions: Record changeNowModal: TransactionType | null + guardarianModal: boolean } type TransactionManagerStoreActions = { @@ -21,6 +22,7 @@ type TransactionManagerStoreActions = { updateTransaction: (id: string, transaction: Transaction) => void removeTransaction: (id: string) => void setChangeNowModal: (type: TransactionType | null) => void + setGuardarianModal: (show: boolean) => void } export const useTransactionManagerStore = createStore({ @@ -28,8 +30,14 @@ export const useTransactionManagerStore = createStore ({ + setGuardarianModal: (show) => + set((state) => { + state.guardarianModal = show + }), setChangeNowModal: (type) => set((state) => { state.changeNowModal = type From 5848c0e4d836024fdb36c4129df46dfad3e540a7 Mon Sep 17 00:00:00 2001 From: ikprk Date: Wed, 26 Jun 2024 20:07:06 +0200 Subject: [PATCH 6/8] Guardarian button on sidebar - to be reverted --- .../_navigation/SidenavViewer/SidenavViewer.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/atlas/src/components/_navigation/SidenavViewer/SidenavViewer.tsx b/packages/atlas/src/components/_navigation/SidenavViewer/SidenavViewer.tsx index 622a12a407..95d68eb7ec 100644 --- a/packages/atlas/src/components/_navigation/SidenavViewer/SidenavViewer.tsx +++ b/packages/atlas/src/components/_navigation/SidenavViewer/SidenavViewer.tsx @@ -12,11 +12,13 @@ import { SvgSidebarToken, } from '@/assets/icons' import { AppLogo } from '@/components/AppLogo' +import { FlexBox } from '@/components/FlexBox' import { Button } from '@/components/_buttons/Button' import { atlasConfig } from '@/config' import { absoluteRoutes } from '@/config/routes' import { getCorrectLoginModal } from '@/providers/auth/auth.helpers' import { useAuthStore } from '@/providers/auth/auth.store' +import { useTransactionManagerStore } from '@/providers/transactions/transactions.store' import { useUser } from '@/providers/user/user.hooks' import { square } from '@/styles' @@ -76,6 +78,7 @@ export const SidenavViewer: FC = () => { } = useAuthStore() const hasAtLeastOneChannel = !!activeMembership?.channels.length && activeMembership?.channels.length >= 1 + const setGuardarianModal = useTransactionManagerStore((state) => state.actions.setGuardarianModal) const { isLoggedIn } = useUser() const closeAndSignIn = () => { @@ -83,9 +86,14 @@ export const SidenavViewer: FC = () => { setAuthModalOpenName(getCorrectLoginModal()) } const buttonsContent = !isLoggedIn ? ( - + + + + ) : hasAtLeastOneChannel ? (