From 91ea68dc534b31a0a654a07d8c181d9d39733ca1 Mon Sep 17 00:00:00 2001 From: aleka Date: Fri, 1 Dec 2023 15:27:19 -0500 Subject: [PATCH] Add chaos labs launch incentives panel (#182) * add chaos lab incentives panel * add seen state * external link dialog * better disconnected state --- package.json | 2 +- pnpm-lock.yaml | 8 +- public/dots-background.svg | 1 + public/logos/chaos-labs.svg | 1 + public/rewards-stars.svg | 1 + src/components/Icon.tsx | 3 + src/constants/dialogs.ts | 1 + src/constants/localStorage.ts | 1 + src/hooks/useNotificationTypes.tsx | 5 +- src/icons/index.ts | 1 + src/icons/leaderboard.svg | 3 + src/layout/DialogManager.tsx | 2 + src/layout/Header/HeaderDesktop.tsx | 21 +- src/pages/rewards/DYDXBalancePanel.tsx | 8 +- src/pages/rewards/LaunchIncentivesPanel.tsx | 296 ++++++++++++++++++++ src/pages/rewards/MigratePanel.tsx | 20 +- src/pages/rewards/RewardsPage.tsx | 36 ++- src/state/configs.ts | 11 +- src/state/configsSelectors.ts | 3 + src/styles/colors.css | 1 + src/views/dialogs/ExternalLinkDialog.tsx | 46 +++ 21 files changed, 449 insertions(+), 22 deletions(-) create mode 100644 public/dots-background.svg create mode 100644 public/logos/chaos-labs.svg create mode 100644 public/rewards-stars.svg create mode 100644 src/icons/leaderboard.svg create mode 100644 src/pages/rewards/LaunchIncentivesPanel.tsx create mode 100644 src/views/dialogs/ExternalLinkDialog.tsx diff --git a/package.json b/package.json index e702766..ab40d6e 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@cosmjs/tendermint-rpc": "^0.31.0", "@dydxprotocol/v4-abacus": "^1.0.30", "@dydxprotocol/v4-client-js": "^1.0.0", - "@dydxprotocol/v4-localization": "^1.0.8", + "@dydxprotocol/v4-localization": "^1.0.17", "@ethersproject/providers": "^5.7.2", "@js-joda/core": "^5.5.3", "@radix-ui/react-checkbox": "^1.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a855c47..cfe48b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,8 +29,8 @@ dependencies: specifier: ^1.0.0 version: 1.0.0 '@dydxprotocol/v4-localization': - specifier: ^1.0.8 - version: 1.0.8 + specifier: ^1.0.17 + version: 1.0.17 '@ethersproject/providers': specifier: ^5.7.2 version: 5.7.2 @@ -1016,8 +1016,8 @@ packages: - utf-8-validate dev: false - /@dydxprotocol/v4-localization@1.0.8: - resolution: {integrity: sha512-h5CSkWHPct1BWmiwJBeX9vOauXSwlx8RK9ryOsaCipJWS4AnBni07p3/jj3L/HgmU1BZXwfcr2hBouXVxxZS8A==} + /@dydxprotocol/v4-localization@1.0.17: + resolution: {integrity: sha512-fybNpi1ono1IdwFQJTszsHbHmGti2BdhTo1nz9KOmKh3tmXdhWObKb64Ve6/6Fjk6h/9+DeZ5ZG4UbYyYMyhCA==} dev: false /@dydxprotocol/v4-proto@0.4.1: diff --git a/public/dots-background.svg b/public/dots-background.svg new file mode 100644 index 0000000..2c1c8e2 --- /dev/null +++ b/public/dots-background.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/logos/chaos-labs.svg b/public/logos/chaos-labs.svg new file mode 100644 index 0000000..383e3f9 --- /dev/null +++ b/public/logos/chaos-labs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/rewards-stars.svg b/public/rewards-stars.svg new file mode 100644 index 0000000..1416b77 --- /dev/null +++ b/public/rewards-stars.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index 4d09fe8..d5f5d64 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -38,6 +38,7 @@ import { HelpCircleIcon, HideIcon, HistoryIcon, + LeaderboardIcon, LinkOutIcon, LockIcon, LogoShortIcon, @@ -111,6 +112,7 @@ export enum IconName { HelpCircle = 'HelpCircle', Hide = 'Hide', History = 'History', + Leaderboard = 'Leaderboard', LinkOut = 'LinkOut', Lock = 'Lock', LogoShort = 'LogoShort', @@ -185,6 +187,7 @@ const icons = { [IconName.HelpCircle]: HelpCircleIcon, [IconName.Hide]: HideIcon, [IconName.History]: HistoryIcon, + [IconName.Leaderboard]: LeaderboardIcon, [IconName.LinkOut]: LinkOutIcon, [IconName.Lock]: LockIcon, [IconName.LogoShort]: LogoShortIcon, diff --git a/src/constants/dialogs.ts b/src/constants/dialogs.ts index 1982409..7ce2904 100644 --- a/src/constants/dialogs.ts +++ b/src/constants/dialogs.ts @@ -3,6 +3,7 @@ export enum DialogTypes { Deposit = 'Deposit', DisconnectWallet = 'DisconnectWallet', ExchangeOffline = 'ExchangeOffline', + ExternalLink = 'ExternalLink', FillDetails = 'FillDetails', Help = 'Help', ExternalNavKeplr = 'ExternalNavKeplr', diff --git a/src/constants/localStorage.ts b/src/constants/localStorage.ts index 411d917..863f24e 100644 --- a/src/constants/localStorage.ts +++ b/src/constants/localStorage.ts @@ -22,6 +22,7 @@ export enum LocalStorageKey { SelectedTheme = 'dydx.SelectedTheme', SelectedTradeLayout = 'dydx.SelectedTradeLayout', TradingViewChartConfig = 'dydx.TradingViewChartConfig', + HasSeenLaunchIncentives = 'dydx.HasSeenLaunchIncentives', } export const LOCAL_STORAGE_VERSIONS = { diff --git a/src/hooks/useNotificationTypes.tsx b/src/hooks/useNotificationTypes.tsx index 9c89321..619a072 100644 --- a/src/hooks/useNotificationTypes.tsx +++ b/src/hooks/useNotificationTypes.tsx @@ -220,7 +220,7 @@ export const notificationTypes: NotificationTypeConfig[] = [ body: stringGetter({ key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.BODY', params: { - BLOGPOST: ( + DOS_BLOGPOST: ( <$Link href="https://www.dydxopsdao.com/blog/deep-dive-full-trading" target="_blank" @@ -229,8 +229,7 @@ export const notificationTypes: NotificationTypeConfig[] = [ {stringGetter({ key: STRING_KEYS.HERE })} ), - // todo: update localization to flip the two - DOS_BLOGPOST: ( + TRADING_BLOGPOST: ( <$Link href="https://dydx.exchange/blog/v4-full-trading" target="_blank" diff --git a/src/icons/index.ts b/src/icons/index.ts index e6cf1c5..a32913e 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -32,6 +32,7 @@ export { default as GiftboxIcon } from './giftbox.svg'; export { default as HelpCircleIcon } from './help-circle.svg'; export { default as HideIcon } from './hide.svg'; export { default as HistoryIcon } from './history.svg'; +export { default as LeaderboardIcon } from './leaderboard.svg'; export { default as LinkOutIcon } from './link-out.svg'; export { default as LockIcon } from './lock.svg'; export { default as LogoShortIcon } from './logo-short'; diff --git a/src/icons/leaderboard.svg b/src/icons/leaderboard.svg new file mode 100644 index 0000000..937ee50 --- /dev/null +++ b/src/icons/leaderboard.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/layout/DialogManager.tsx b/src/layout/DialogManager.tsx index e85e6b1..49faff7 100644 --- a/src/layout/DialogManager.tsx +++ b/src/layout/DialogManager.tsx @@ -11,6 +11,7 @@ import { DepositDialog } from '@/views/dialogs/DepositDialog'; import { DisconnectDialog } from '@/views/dialogs/DisconnectDialog'; import { ExchangeOfflineDialog } from '@/views/dialogs/ExchangeOfflineDialog'; import { HelpDialog } from '@/views/dialogs/HelpDialog'; +import { ExternalLinkDialog } from '@/views/dialogs/ExternalLinkDialog'; import { ExternalNavKeplrDialog } from '@/views/dialogs/ExternalNavKeplrDialog'; import { MnemonicExportDialog } from '@/views/dialogs/MnemonicExportDialog'; import { MobileSignInDialog } from '@/views/dialogs/MobileSignInDialog'; @@ -53,6 +54,7 @@ export const DialogManager = () => { [DialogTypes.FillDetails]: , [DialogTypes.Help]: , [DialogTypes.ExternalNavKeplr]: , + [DialogTypes.ExternalLink]: , [DialogTypes.MnemonicExport]: , [DialogTypes.MobileSignIn]: , [DialogTypes.Onboarding]: , diff --git a/src/layout/Header/HeaderDesktop.tsx b/src/layout/Header/HeaderDesktop.tsx index bc493ec..97e5fd7 100644 --- a/src/layout/Header/HeaderDesktop.tsx +++ b/src/layout/Header/HeaderDesktop.tsx @@ -1,6 +1,6 @@ import styled, { type AnyStyledComponent } from 'styled-components'; import { Link } from 'react-router-dom'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { ButtonShape } from '@/constants/buttons'; import { DialogTypes } from '@/constants/dialogs'; @@ -8,6 +8,10 @@ import { STRING_KEYS } from '@/constants/localization'; import { AppRoute } from '@/constants/routes'; import { LogoShortIcon, BellStrokeIcon } from '@/icons'; +import { headerMixins } from '@/styles/headerMixins'; +import { layoutMixins } from '@/styles/layoutMixins'; +import breakpoints from '@/styles/breakpoints'; + import { useTokenConfigs, useStringGetter, useURLConfigs } from '@/hooks'; import { Icon, IconName } from '@/components/Icon'; @@ -21,10 +25,7 @@ import { NotificationsMenu } from '@/views/menus/NotificationsMenu'; import { LanguageSelector } from '@/views/menus/LanguageSelector'; import { openDialog } from '@/state/dialogs'; - -import { headerMixins } from '@/styles/headerMixins'; -import { layoutMixins } from '@/styles/layoutMixins'; -import breakpoints from '@/styles/breakpoints'; +import { getHasSeenLaunchIncentives } from '@/state/configsSelectors'; export const HeaderDesktop = () => { const stringGetter = useStringGetter(); @@ -32,6 +33,8 @@ export const HeaderDesktop = () => { const dispatch = useDispatch(); const { chainTokenLabel } = useTokenConfigs(); + const hasSeenLaunchIncentives = useSelector(getHasSeenLaunchIncentives); + const navItems = [ { group: 'navigation', @@ -55,6 +58,7 @@ export const HeaderDesktop = () => { value: chainTokenLabel, label: chainTokenLabel, href: `/${chainTokenLabel}`, + slotAfter: !hasSeenLaunchIncentives && , }, { value: 'MORE', @@ -238,3 +242,10 @@ Styled.IconButton = styled(IconButton)<{ size?: string }>` --button-icon-size: 1rem; --button-padding: 0 0.5em; `; + +Styled.UnreadIndicator = styled.div` + width: 0.4375rem; + height: 0.4375rem; + border-radius: 50%; + background-color: var(--color-accent); +`; diff --git a/src/pages/rewards/DYDXBalancePanel.tsx b/src/pages/rewards/DYDXBalancePanel.tsx index ac4803d..f5c6c60 100644 --- a/src/pages/rewards/DYDXBalancePanel.tsx +++ b/src/pages/rewards/DYDXBalancePanel.tsx @@ -2,6 +2,7 @@ import type { ElementType } from 'react'; import styled, { AnyStyledComponent } from 'styled-components'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; +import breakpoints from '@/styles/breakpoints'; import { layoutMixins } from '@/styles/layoutMixins'; import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks'; @@ -116,12 +117,17 @@ const Styled: Record = {}; Styled.Panel = styled(Panel)` --panel-paddingX: 1.5rem; + + @media ${breakpoints.tablet} { + --panel-paddingY: 1.5rem; + --panel-content-paddingY: 1rem; + } `; Styled.Header = styled.div` ${layoutMixins.spacedRow} gap: 1rem; - padding: 1rem 1.5rem 0; + padding: var(--panel-paddingY) var(--panel-paddingX) 0; `; Styled.Title = styled.h3` diff --git a/src/pages/rewards/LaunchIncentivesPanel.tsx b/src/pages/rewards/LaunchIncentivesPanel.tsx new file mode 100644 index 0000000..5ffc37e --- /dev/null +++ b/src/pages/rewards/LaunchIncentivesPanel.tsx @@ -0,0 +1,296 @@ +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { useQuery } from 'react-query'; +import styled, { AnyStyledComponent } from 'styled-components'; + +import { STRING_KEYS } from '@/constants/localization'; +import { ButtonAction } from '@/constants/buttons'; +import { DialogTypes } from '@/constants/dialogs'; + +import breakpoints from '@/styles/breakpoints'; +import { useAccounts, useBreakpoints, useStringGetter } from '@/hooks'; + +import { layoutMixins } from '@/styles/layoutMixins'; + +import { Panel } from '@/components/Panel'; +import { Button } from '@/components/Button'; + +import { Output, OutputType } from '@/components/Output'; +import { Icon, IconName } from '@/components/Icon'; +import { Tag, TagSize } from '@/components/Tag'; + +import { markLaunchIncentivesSeen } from '@/state/configs'; +import { openDialog } from '@/state/dialogs'; + +import { log } from '@/lib/telemetry'; + +export const LaunchIncentivesPanel = () => { + const { isNotTablet } = useBreakpoints(); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(markLaunchIncentivesSeen()); + }, []); + + return isNotTablet ? ( + } slotRight={}> + + + ) : ( + + + + + + + + ); +}; + +const LaunchIncentivesTitle = () => { + const stringGetter = useStringGetter(); + return ( + + {stringGetter({ + key: STRING_KEYS.LAUNCH_INCENTIVES_TITLE, + params: { + FOR_V4: {stringGetter({ key: STRING_KEYS.FOR_V4 })}, + }, + })} + {stringGetter({ key: STRING_KEYS.NEW })} + + ); +}; + +const EstimatedRewards = () => { + const stringGetter = useStringGetter(); + const { dydxAddress } = useAccounts(); + + const { data, isLoading } = useQuery({ + enabled: !!dydxAddress, + queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}`, + queryFn: async () => { + if (!dydxAddress) return undefined; + const resp = await fetch(`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}`); + return (await resp.json())?.incentivePoints; + }, + onError: (error: Error) => log('LaunchIncentives/fetchPoints', error), + }); + + return ( + + +
+ {stringGetter({ key: STRING_KEYS.ESTIMATED_REWARDS })} + + {stringGetter({ + key: STRING_KEYS.LAUNCH_INCENTIVES_SEASON_NUM, + params: { + SEASON_NUMBER: 1, + }, + })} + +
+ + + + {data !== undefined && stringGetter({ key: STRING_KEYS.POINTS })} + +
+ + +
+ ); +}; + +const LaunchIncentivesContent = () => { + const stringGetter = useStringGetter(); + const dispatch = useDispatch(); + + return ( + + + {stringGetter({ key: STRING_KEYS.LAUNCH_INCENTIVES_DESCRIPTION })}{' '} + + + + { + dispatch( + openDialog({ + type: DialogTypes.ExternalLink, + dialogProps: { link: 'https://dydx.exchange/blog/v4-full-trading' }, + }) + ); + }} + slotRight={} + > + {stringGetter({ key: STRING_KEYS.ABOUT })} + + { + dispatch( + openDialog({ + type: DialogTypes.ExternalLink, + dialogProps: { link: 'https://community.chaoslabs.xyz/dydx-v4/risk/leaderboard' }, + }) + ); + }} + slotRight={} + slotLeft={} + > + {stringGetter({ key: STRING_KEYS.LEADERBOARD })} + + + + ); +}; + +const Styled: Record = {}; + +Styled.Panel = styled(Panel)` + --panel-paddingY: 1rem; + --panel-paddingX: 1.5rem; + + background-color: var(--color-layer-4); + width: 100%; + + @media ${breakpoints.tablet} { + --panel-paddingY: 1.5rem; + } +`; + +Styled.ForV4 = styled.span` + color: var(--color-text-0); +`; + +Styled.Title = styled.h3` + ${layoutMixins.inlineRow} + font: var(--font-medium-book); + color: var(--color-text-2); + + @media ${breakpoints.notTablet} { + padding: var(--panel-paddingY) var(--panel-paddingX) 0; + } +`; + +Styled.Description = styled.div` + color: var(--color-text-0); + --link-color: var(--color-text-1); + + a { + display: inline; + text-decoration: underline; + text-underline-offset: 0.25rem; + + ::before { + content: ' '; + } + } +`; + +Styled.ButtonRow = styled.div` + ${layoutMixins.inlineRow} + gap: 0.75rem; + margin-top: 0.5rem; + + a:last-child { + --button-width: 100%; + } +`; + +Styled.Button = styled(Button)` + --button-padding: 0 1rem; +`; + +Styled.LinkOutIcon = styled(Icon)` + color: var(--color-text-1); +`; + +Styled.AboutButton = styled(Styled.Button)` + --button-textColor: var(--color-text-2); + --button-backgroundColor: var(--color-layer-6); + --button-border: solid var(--border-width) var(--color-layer-7); +`; + +Styled.Column = styled.div` + ${layoutMixins.flexColumn} + gap: 0.5rem; +`; + +Styled.EstimatedRewardsCard = styled.div` + ${layoutMixins.spacedRow} + padding: 1rem 1.25rem; + min-width: 21.25rem; + height: calc(100% - calc(1.5rem * 2)); + margin: 1.5rem; + + background-color: var(--color-layer-5); + background-image: url('/dots-background.svg'); + background-size: cover; + + border-radius: 0.75rem; + border: solid var(--border-width) var(--color-layer-6); + color: var(--color-text-1); + + @media ${breakpoints.tablet} { + margin: 0 0 0.5rem; + } +`; + +Styled.EstimatedRewardsCardContent = styled.div` + ${layoutMixins.flexColumn} + gap: 1rem; + height: 100%; + justify-content: space-between; + + div { + ${layoutMixins.flexColumn} + gap: 0.15rem; + font: var(--font-medium-book); + + :first-child { + color: var(--color-text-2); + } + } +`; + +Styled.BackgroundDots = styled.img` + position: absolute; +`; + +Styled.Season = styled.span` + font: var(--font-small-book); + color: var(--color-text-1); +`; + +Styled.Points = styled.span` + ${layoutMixins.inlineRow} + gap: 0.25rem; + font: var(--font-large-book); + color: var(--color-text-0); + + output { + color: var(--color-text-2); + } +`; + +Styled.Image = styled.img` + position: relative; + float: right; + + width: 5.25rem; + height: auto; +`; + +Styled.ChaosLabsLogo = styled.img` + height: 1.25rem; + align-self: start; +`; + +Styled.NewTag = styled(Tag)` + color: var(--color-accent); + background-color: var(--color-accent-faded); +`; diff --git a/src/pages/rewards/MigratePanel.tsx b/src/pages/rewards/MigratePanel.tsx index fd822bc..f623082 100644 --- a/src/pages/rewards/MigratePanel.tsx +++ b/src/pages/rewards/MigratePanel.tsx @@ -7,6 +7,8 @@ import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons'; import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks'; +import { breakpoints } from '@/styles'; + import { layoutMixins } from '@/styles/layoutMixins'; import { Details } from '@/components/Details'; @@ -134,13 +136,22 @@ const Styled: Record = {}; Styled.MigratePanel = styled(Panel)` --panel-paddingX: 1.5rem; width: 100%; + + background-image: url('/dots-background.svg'); + background-position: right; + background-repeat: no-repeat; + + @media ${breakpoints.tablet} { + --panel-paddingY: 1.5rem; + --panel-content-paddingY: 1rem; + } `; Styled.Title = styled.h3` font: var(--font-medium-book); color: var(--color-text-2); - padding: 1rem 1.5rem 0; + padding: var(--panel-paddingY) var(--panel-paddingX) 0; `; Styled.MigrateAction = styled.div` @@ -164,6 +175,13 @@ Styled.Token = styled(Output)` Styled.Description = styled.div` color: var(--color-text-0); --link-color: var(--color-text-1); + + a { + display: inline; + ::before { + content: ' '; + } + } `; Styled.Column = styled.div` diff --git a/src/pages/rewards/RewardsPage.tsx b/src/pages/rewards/RewardsPage.tsx index 3bdba80..745d746 100644 --- a/src/pages/rewards/RewardsPage.tsx +++ b/src/pages/rewards/RewardsPage.tsx @@ -19,6 +19,7 @@ import { openDialog } from '@/state/dialogs'; import { DYDXBalancePanel } from './DYDXBalancePanel'; import { MigratePanel } from './MigratePanel'; +import { LaunchIncentivesPanel } from './LaunchIncentivesPanel'; export const RewardsPage = () => { const dispatch = useDispatch(); @@ -39,9 +40,20 @@ export const RewardsPage = () => { return ( {import.meta.env.VITE_V3_TOKEN_ADDRESS && } - - {isTablet && } + {isTablet ? ( + <> + + + + ) : ( + + + + + )} + + {stringGetter({ key: STRING_KEYS.GOVERNANCE })}} slotRight={panelArrow} @@ -67,8 +79,6 @@ export const RewardsPage = () => { - - {isNotTablet && } ); @@ -99,6 +109,11 @@ Styled.Page = styled.div` Styled.Panel = styled(Panel)` --panel-paddingX: 1.5rem; + + @media ${breakpoints.tablet} { + --panel-paddingY: 1.5rem; + --panel-content-paddingY: 1rem; + } `; Styled.Title = styled.h3` @@ -110,20 +125,29 @@ Styled.Title = styled.h3` Styled.Description = styled.div` color: var(--color-text-0); --link-color: var(--color-text-1); + + a { + display: inline; + ::before { + content: ' '; + } + } `; Styled.PanelRow = styled.div` ${layoutMixins.gridEqualColumns} gap: 1.5rem; - align-items: flex-start; - @media ${breakpoints.tablet} { grid-auto-flow: row; grid-template-columns: 1fr; } `; +Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)` + grid-template-columns: 2fr 1fr; +`; + Styled.IconButton = styled(IconButton)` color: var(--color-text-0); --color-border: var(--color-layer-6); diff --git a/src/state/configs.ts b/src/state/configs.ts index c90209d..9da0355 100644 --- a/src/state/configs.ts +++ b/src/state/configs.ts @@ -17,6 +17,7 @@ export interface ConfigsState { feeTiers?: kollections.List; feeDiscounts?: FeeDiscount[]; network?: NetworkConfigs; + hasSeenLaunchIncentives: boolean; } const DOCUMENT_THEME_MAP = { @@ -43,6 +44,10 @@ const initialState: ConfigsState = { feeDiscounts: undefined, feeTiers: undefined, network: undefined, + hasSeenLaunchIncentives: getLocalStorage({ + key: LocalStorageKey.HasSeenLaunchIncentives, + defaultValue: false, + }), }; changeTheme(initialState.appTheme); @@ -60,7 +65,11 @@ export const configsSlice = createSlice({ ...state, ...action.payload, }), + markLaunchIncentivesSeen: (state: ConfigsState) => { + setLocalStorage({ key: LocalStorageKey.HasSeenLaunchIncentives, value: true }); + state.hasSeenLaunchIncentives = true; + }, }, }); -export const { setAppTheme, setConfigs } = configsSlice.actions; +export const { setAppTheme, setConfigs, markLaunchIncentivesSeen } = configsSlice.actions; diff --git a/src/state/configsSelectors.ts b/src/state/configsSelectors.ts index ec4aef2..d83bd84 100644 --- a/src/state/configsSelectors.ts +++ b/src/state/configsSelectors.ts @@ -5,3 +5,6 @@ export const getAppTheme = (state: RootState) => state.configs.appTheme; export const getFeeTiers = (state: RootState) => state.configs.feeTiers?.toArray(); export const getFeeDiscounts = (state: RootState) => state.configs.feeDiscounts; + +export const getHasSeenLaunchIncentives = (state: RootState) => + state.configs.hasSeenLaunchIncentives; diff --git a/src/styles/colors.css b/src/styles/colors.css index dc2bab7..d3a6812 100644 --- a/src/styles/colors.css +++ b/src/styles/colors.css @@ -116,6 +116,7 @@ --color-gradient-positive: hsla(158, 49%, 48%, 0.16); --color-gradient-negative: hsla(0, 100%, 66%, 0.16); + --color-accent-faded: hsla(var(--theme-classic-hue-purple), 100%, 70%, 0.16); --color-risk-low: var(--theme-classic-color-green); --color-risk-medium: var(--theme-classic-color-yellow); diff --git a/src/views/dialogs/ExternalLinkDialog.tsx b/src/views/dialogs/ExternalLinkDialog.tsx new file mode 100644 index 0000000..ae2bdb8 --- /dev/null +++ b/src/views/dialogs/ExternalLinkDialog.tsx @@ -0,0 +1,46 @@ +import styled, { type AnyStyledComponent } from 'styled-components'; + +import { ButtonAction, ButtonType } from '@/constants/buttons'; +import { STRING_KEYS } from '@/constants/localization'; +import { useStringGetter } from '@/hooks'; + +import { Button } from '@/components/Button'; +import { Dialog } from '@/components/Dialog'; + +import { layoutMixins } from '@/styles/layoutMixins'; + +type ElementProps = { + link: string; + linkDescription?: string; + setIsOpen: (open: boolean) => void; +}; + +export const ExternalLinkDialog = ({ setIsOpen, link, linkDescription }: ElementProps) => { + const stringGetter = useStringGetter(); + return ( + + +

{stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE_DISCLAIMER })}.

+ +
+
+ ); +}; + +const Styled: Record = {}; + +Styled.Content = styled.div` + ${layoutMixins.flexColumn} + gap: 1rem; + + font: var(--font-base-book); +`;