diff --git a/.env.sample b/.env.sample index e1d7193f48..fb2eb9a17c 100644 --- a/.env.sample +++ b/.env.sample @@ -11,6 +11,9 @@ VITE_APP_PUBLIC_URL=https://dev.push.org/ # WALLET CONNECT PROJECT ID VITE_APP_WALLETCONNECT_PROJECT_ID=walletconnect_project_id +# Discord Client ID +VITE_APP_DISCORD_CLIENT_ID=client_id + # LOCALHOST CREDS # VITE_APP_IPFS_INFURA_API_KEY="your_secret_infura_api_key_for_localhost" # VITE_APP_IPFS_INFURA_API_SECRET="your_secret_infura_api_secret_for_localhost" diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index bda9cb9e67..543355fd48 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -31,7 +31,7 @@ jobs: if: github.event.action != 'closed' # You might want to skip the build if the PR has been closed env: VITE_APP_WALLETCONNECT_PROJECT_ID: ${{ secrets.VITE_APP_WALLETCONNECT_PROJECT_ID }} - + VITE_APP_DISCORD_CLIENT_ID: ${{ secrets.VITE_APP_DISCORD_CLIENT_ID }} run: | yarn install yarn build:pr:preview diff --git a/package.json b/package.json index f0a920b2bd..e860d36f2a 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "assert": "2.0.0", "babel-plugin-styled-components": "1.10.7", "blockies": "0.0.2", + "blockies-react-svg": "^0.0.13", "browserify-zlib": "^0.2.0", "browserslist": "4.14.6", "buffer": "6.0.3", @@ -75,6 +76,8 @@ "eth-crypto": "1.6.0", "eth-sig-util": "^3.0.1", "ethers": "^5.7.2", + "firebase": "^10.12.2", + "formik": "^2.4.6", "https-browserify": "1.0.0", "image-size": "0.9.3", "immer": "^10.0.2", @@ -97,6 +100,7 @@ "react-ga": "2.7.0", "react-icons": "4.12.0", "react-image-file-resizer": "^0.4.7", + "react-infinite-scroller": "^1.2.6", "react-joyride": "^2.4.0", "react-multi-select-component": "^4.2.3", "react-player": "^2.16.0", @@ -115,7 +119,8 @@ "styled-components": "^5.3.8", "url": "0.11.0", "web3": "^1.8.2", - "xss": "^1.0.14" + "xss": "^1.0.14", + "yup": "^1.4.0" }, "devDependencies": { "@3id/connect": "0.4.1", @@ -125,6 +130,7 @@ "@types/openpgp": "^4.4.18", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", + "@types/react-infinite-scroller": "^1", "@types/styled-components": "5.1.26", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", diff --git a/src/App.tsx b/src/App.tsx index 1b41d66738..b62cdf3a34 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -52,6 +52,7 @@ import SpaceContextProvider from 'contexts/SpaceContext'; import { SpaceWidgetSection } from 'sections/space/SpaceWidgetSection'; import { blocksColors } from 'blocks'; import { textVariants } from 'blocks/text/Text.constants'; +import APP_PATHS from 'config/AppPaths'; dotenv.config(); @@ -327,7 +328,8 @@ export default function App() { // const { spaceUI } = useSpaceComponents(); const location = useLocation(); - const isSnapPage = location?.pathname.includes('/snap'); + const isHeaderHidden = location?.pathname.includes(APP_PATHS.PointsVault); + const isSidebarHidden = location?.pathname.includes(APP_PATHS.PointsVault) || location?.pathname.includes('/snap'); return ( @@ -379,15 +381,17 @@ export default function App() { }} /> - -
- + {!isHeaderHidden && ( + +
+ + )} - {!isSnapPage && ( + {!isSidebarHidden && ( = T | { [key in Breakpoint]?: T }; -export type RadiusType = `r${number}`; +export type RadiusType = `r${number}` | `r${number} r${number}` | `r${number} r${number} r${number} r${number}`; export type SpaceType = `s${number}` | `s${number} s${number}` | `s${number} s${number} s${number} s${number}`; @@ -59,7 +59,7 @@ export type ThemeMode = 'light' | 'dark'; export type ThemeModeColors = Record; -export type BorderValue = `${number}px ${string} ${BlocksColors}`; +export type BorderValue = `${number}px ${string} ${BlocksColors}` | 'none'; export type ThemeModeBorder = Record; diff --git a/src/blocks/Blocks.utils.ts b/src/blocks/Blocks.utils.ts index 33d97c2fb4..38f736d48a 100644 --- a/src/blocks/Blocks.utils.ts +++ b/src/blocks/Blocks.utils.ts @@ -11,7 +11,8 @@ import { ResponsiveCSSPropertyData, ThemeMode, ThemeModeBorder, - BorderValue + BorderValue, + RadiusType } from './Blocks.types'; /** @@ -166,3 +167,17 @@ export const getBlocksBorder = (mode: ThemeMode, border?: BorderValue | ThemeMod borderValues[2] = `var(--${borderValues[2]})`; return borderValues.join(' '); }; + +/** + * @param radius + * @returns + */ +export const getBlocksBorderRadius = (radius?: RadiusType) => { + // If border-radius is not given return undefined, to avoid any breakages + if (!radius) return radius; + + return radius.replace(/\b(\w+)\b/g, 'var(--$1)'); + + // If passed a design system border-radius then use radius as a variable + return `var(--${radius})`; +}; diff --git a/src/blocks/box/Box.constants.ts b/src/blocks/box/Box.constants.ts index 462320befb..a487e867af 100644 --- a/src/blocks/box/Box.constants.ts +++ b/src/blocks/box/Box.constants.ts @@ -10,6 +10,7 @@ export const boxRestrictedCSSPropKeys: (keyof BoxCSSProps | keyof ModeProp)[] = 'position', 'boxShadow', 'alignItems', + 'alignSelf', 'display', 'flexDirection', 'gap', diff --git a/src/blocks/box/Box.tsx b/src/blocks/box/Box.tsx index d5d3792589..80d5ae4f7a 100644 --- a/src/blocks/box/Box.tsx +++ b/src/blocks/box/Box.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components'; import { useBlocksTheme } from '../Blocks.hooks'; import { TransformedHTMLAttributes, ModeProp } from '../Blocks.types'; -import { getBlocksColor, getBlocksBorder } from '../Blocks.utils'; +import { getBlocksColor, getBlocksBorder, getBlocksBorderRadius } from '../Blocks.utils'; import { BoxCSSProps, BoxComponentProps } from './Box.types'; import { getBoxResponsiveCSS } from './Box.utils'; import { boxRestrictedCSSPropKeys } from './Box.constants'; @@ -13,7 +13,7 @@ export type BoxProps = BoxCSSProps & BoxComponentProps & TransformedHTMLAttribut const StyledBox = styled.div.withConfig({ shouldForwardProp: (prop, defaultValidatorFn) => !boxRestrictedCSSPropKeys.includes(prop as keyof BoxCSSProps) && defaultValidatorFn(prop), -}) ` +})` /* Responsive props */ ${(props) => getBoxResponsiveCSS(props)} @@ -21,7 +21,7 @@ const StyledBox = styled.div.withConfig({ color: ${(props) => getBlocksColor(props.mode, props.color)}; background-color: ${(props) => getBlocksColor(props.mode, props.backgroundColor)}; box-shadow: ${(props) => props.boxShadow}; - border-radius: ${(props) => props.borderRadius}; + border-radius: ${(props) => getBlocksBorderRadius(props.borderRadius)}; cursor: ${(props) => props.cursor}; overflow: ${(props) => props.overflow}; border: ${(props) => getBlocksBorder(props.mode, props.border)}; diff --git a/src/blocks/box/Box.types.ts b/src/blocks/box/Box.types.ts index 764f9a79f6..c44d586422 100644 --- a/src/blocks/box/Box.types.ts +++ b/src/blocks/box/Box.types.ts @@ -3,6 +3,7 @@ import { CSSProperties, ReactNode } from 'react'; import { BlocksColors, BorderValue, + RadiusType, ResponsiveProp, SpaceType, ThemeModeBorder, @@ -14,6 +15,8 @@ import { FlattenSimpleInterpolation } from 'styled-components'; export type BoxResponsiveProps = { /* Sets align-items css property */ alignItems?: ResponsiveProp; + /* Sets align-self css property */ + alignSelf?: ResponsiveProp; /* Sets flex-direction css property */ flexDirection?: ResponsiveProp; /* Sets gap between the elements */ @@ -44,7 +47,7 @@ export type BoxNonResponsiveProps = { /* Sets border css property */ border?: BorderValue | ThemeModeBorder; /* Sets border-radius css property */ - borderRadius?: string; + borderRadius?: RadiusType; /* Sets background-color css property */ backgroundColor?: BlocksColors | ThemeModeColors; /* Sets color css property */ @@ -74,6 +77,7 @@ export type BoxComponentProps = { export type BoxResponsiveCSSProperties = | 'align-items' + | 'align-self' | 'display' | 'flex-direction' | 'gap' diff --git a/src/blocks/box/Box.utils.ts b/src/blocks/box/Box.utils.ts index fb32bce2e0..3d93690b57 100644 --- a/src/blocks/box/Box.utils.ts +++ b/src/blocks/box/Box.utils.ts @@ -3,6 +3,7 @@ import { BoxResponsiveCSSPropertiesData, BoxResponsiveProps } from './Box.types' const getBoxResponsiveCSSProperties = (props: BoxResponsiveProps): BoxResponsiveCSSPropertiesData[] => [ { propName: 'align-items', prop: props.alignItems }, + { propName: 'align-self', prop: props.alignSelf }, { propName: 'display', prop: props.display }, { propName: 'flex-direction', prop: props.flexDirection }, { propName: 'gap', prop: props.gap }, @@ -14,7 +15,7 @@ const getBoxResponsiveCSSProperties = (props: BoxResponsiveProps): BoxResponsive { propName: 'max-width', prop: props.maxWidth }, { propName: 'min-width', prop: props.minWidth }, { propName: 'padding', prop: props.padding }, - { propName: 'width', prop: props.width } + { propName: 'width', prop: props.width }, ]; export const getBoxResponsiveCSS = (props: BoxResponsiveProps) => { diff --git a/src/blocks/button/Button.utils.ts b/src/blocks/button/Button.utils.ts index 7a9e28e4ed..2f39d638e0 100644 --- a/src/blocks/button/Button.utils.ts +++ b/src/blocks/button/Button.utils.ts @@ -186,6 +186,7 @@ export const getButtonSizeStyles = ({ gap: var(--s1); height: 32px; padding: var(--s3) var(--s4); + min-width: 100px; `} /* Button text size css */ @@ -224,6 +225,7 @@ export const getButtonSizeStyles = ({ gap: var(--s1); height: 40px; padding: var(--s3) var(--s6); + min-width: 100px; `} /* Button text size css */ @@ -263,6 +265,7 @@ export const getButtonSizeStyles = ({ gap: var(--s1); height: 48px; padding: var(--s4) var(--s6); + min-width: 100px; `} /* Button text size css */ @@ -301,6 +304,7 @@ export const getButtonSizeStyles = ({ gap: var(--s1); height: 52px; padding: var(--s4) var(--s8); + min-width: 100px; `} /* Button text size css */ diff --git a/src/blocks/hoverableSVG/HoverableSVG.tsx b/src/blocks/hoverableSVG/HoverableSVG.tsx index 14776fe687..97018530d3 100644 --- a/src/blocks/hoverableSVG/HoverableSVG.tsx +++ b/src/blocks/hoverableSVG/HoverableSVG.tsx @@ -1,8 +1,15 @@ import { FC } from 'react'; import styled from 'styled-components'; import { useBlocksTheme } from '../Blocks.hooks'; -import { BlocksColors, ThemeModeColors, SpaceType, ModeProp, TransformedHTMLAttributes } from '../Blocks.types'; -import { getBlocksColor } from '../Blocks.utils'; +import { + BlocksColors, + ThemeModeColors, + SpaceType, + ModeProp, + TransformedHTMLAttributes, + RadiusType, +} from '../Blocks.types'; +import { getBlocksColor, getBlocksBorderRadius } from '../Blocks.utils'; export type HoverableSVGProps = { /* Icon component */ icon: React.ReactNode; @@ -20,17 +27,19 @@ export type HoverableSVGProps = { padding?: SpaceType; /* Sets the margin for SVG button container */ margin?: SpaceType; + /* Sets the margin for SVG button container */ + borderRadius?: RadiusType; } & TransformedHTMLAttributes; - const StyledButton = styled.button.withConfig({ shouldForwardProp: (prop, defaultValidatorFn) => !['mode'].includes(prop) && defaultValidatorFn(prop), -}) & ModeProp>` +}) & ModeProp>` display: inline-flex; align-items: center; justify-content: center; padding: var(--${(props) => props.padding || 's0'}); margin: var(--${(props) => props.margin || 's0'}); + border-radius: ${(props) => getBlocksBorderRadius(props.borderRadius)}; background-color: ${({ defaultBackground, mode }) => getBlocksColor(mode, defaultBackground) || 'transparent'}; color: ${({ mode, defaultColor }) => getBlocksColor(mode, defaultColor) || 'inherit'}; border: none; @@ -54,6 +63,7 @@ const HoverableSVG: FC = ({ hoverBackground, padding, margin, + borderRadius, ...props }) => { const { mode } = useBlocksTheme(); @@ -66,6 +76,7 @@ const HoverableSVG: FC = ({ hoverBackground={hoverBackground} padding={padding} margin={margin} + borderRadius={borderRadius} mode={mode} {...props} > @@ -73,4 +84,4 @@ const HoverableSVG: FC = ({ ); }; -export { HoverableSVG }; \ No newline at end of file +export { HoverableSVG }; diff --git a/src/blocks/icons/components/Star.tsx b/src/blocks/icons/components/Star.tsx new file mode 100644 index 0000000000..1d70e002c0 --- /dev/null +++ b/src/blocks/icons/components/Star.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; +import { IconWrapper } from '../IconWrapper'; +import { IconProps } from '../Icons.types'; + +const Star: FC = (allProps) => { + const { svgProps: props, ...restProps } = allProps; + return ( + + + + } + {...restProps} + /> + ); +}; + +export default Star; diff --git a/src/blocks/icons/index.ts b/src/blocks/icons/index.ts index 02d17bfc87..fe2feec934 100644 --- a/src/blocks/icons/index.ts +++ b/src/blocks/icons/index.ts @@ -117,6 +117,8 @@ export { default as SendNotificationFilled } from './components/SendNotification export { default as Smiley } from './components/Smiley'; +export { default as Star } from './components/Star'; + export { default as Settings } from './components/Settings'; export { default as Swap } from './components/Swap'; diff --git a/src/blocks/illustrations/components/Discord.tsx b/src/blocks/illustrations/components/Discord.tsx new file mode 100644 index 0000000000..9ebba0e628 --- /dev/null +++ b/src/blocks/illustrations/components/Discord.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import { IllustrationWrapper } from '../IllustrationWrapper'; +import { IllustrationProps } from '../Illustrations.types'; + +const Discord: FC = (allProps) => { + const { svgProps: props, ...restProps } = allProps; + return ( + + + + + } + {...restProps} + /> + ); +}; + +export default Discord; diff --git a/src/blocks/illustrations/components/Points.tsx b/src/blocks/illustrations/components/Points.tsx new file mode 100644 index 0000000000..af87661d6f --- /dev/null +++ b/src/blocks/illustrations/components/Points.tsx @@ -0,0 +1,125 @@ +import { FC } from 'react'; +import { IllustrationWrapper } from '../IllustrationWrapper'; +import { IllustrationProps } from '../Illustrations.types'; + +const Points: FC = (allProps) => { + const { svgProps: props, ...restProps } = allProps; + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + {...restProps} + /> + ); +}; + +export default Points; diff --git a/src/blocks/illustrations/components/Referral.tsx b/src/blocks/illustrations/components/Referral.tsx new file mode 100644 index 0000000000..76d86c83e4 --- /dev/null +++ b/src/blocks/illustrations/components/Referral.tsx @@ -0,0 +1,1134 @@ +import { FC } from 'react'; +import { IllustrationWrapper } from '../IllustrationWrapper'; +import { IllustrationProps } from '../Illustrations.types'; + +const Referral: FC = (allProps) => { + const { svgProps: props, ...restProps } = allProps; + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + {...restProps} + /> + ); +}; + +export default Referral; diff --git a/src/blocks/illustrations/components/RewardsActivity.tsx b/src/blocks/illustrations/components/RewardsActivity.tsx new file mode 100644 index 0000000000..af1df98af0 --- /dev/null +++ b/src/blocks/illustrations/components/RewardsActivity.tsx @@ -0,0 +1,39 @@ +import { FC } from 'react'; +import { IllustrationWrapper } from '../IllustrationWrapper'; +import { IllustrationProps } from '../Illustrations.types'; + +const RewardsActivity: FC = (allProps) => { + const { svgProps: props, ...restProps } = allProps; + return ( + + + + + + + } + {...restProps} + /> + ); +}; + +export default RewardsActivity; diff --git a/src/blocks/illustrations/components/RewardsBell.tsx b/src/blocks/illustrations/components/RewardsBell.tsx new file mode 100644 index 0000000000..51521c38a6 --- /dev/null +++ b/src/blocks/illustrations/components/RewardsBell.tsx @@ -0,0 +1,58 @@ +import { FC } from 'react'; +import { IllustrationWrapper } from '../IllustrationWrapper'; +import { IllustrationProps } from '../Illustrations.types'; + +const RewardsBell: FC = (allProps) => { + const { svgProps: props, ...restProps } = allProps; + return ( + + + + + + + + + + + + + + + } + {...restProps} + /> + ); +}; + +export default RewardsBell; diff --git a/src/blocks/illustrations/components/Twitter.tsx b/src/blocks/illustrations/components/Twitter.tsx new file mode 100644 index 0000000000..b95c2245b8 --- /dev/null +++ b/src/blocks/illustrations/components/Twitter.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import { IllustrationWrapper } from '../IllustrationWrapper'; +import { IllustrationProps } from '../Illustrations.types'; + +const Twitter: FC = (allProps) => { + const { svgProps: props, ...restProps } = allProps; + return ( + + + + + } + {...restProps} + /> + ); +}; + +export default Twitter; diff --git a/src/blocks/illustrations/index.ts b/src/blocks/illustrations/index.ts index cb322c2adc..6e0636a6e1 100644 --- a/src/blocks/illustrations/index.ts +++ b/src/blocks/illustrations/index.ts @@ -7,11 +7,28 @@ export { default as ChatDark } from './components/ChatDark'; export { default as Communication } from './components/Communication'; export { default as CommunicationDark } from './components/CommunicationDark'; -export { default as Notification } from './components/Notification'; -export { default as NotificationDark } from './components/NotificationDark'; +export { default as Discord } from './components/Discord'; + +export { default as Ethereum } from './components/Ethereum'; export { default as PushAlpha } from './components/PushAlpha'; + export { default as PushBot } from './components/PushBot'; + export { default as PushDev } from './components/PushDev'; + +export { default as Points } from './components/Points'; + export { default as Metamask } from './components/Metamask'; -export { default as Ethereum } from './components/Ethereum'; + +export { default as Notification } from './components/Notification'; +export { default as NotificationDark } from './components/NotificationDark'; + +export { default as RewardsBell } from './components/RewardsBell'; +export { default as Referral } from './components/Referral'; + +export { default as Twitter } from './components/Twitter'; + +export { default as RewardsActivity } from './components/RewardsActivity'; + +export { default as PushLogo } from './components/PushLogo'; diff --git a/src/blocks/index.ts b/src/blocks/index.ts index 0c4ffec8cc..e8bcb17ab5 100644 --- a/src/blocks/index.ts +++ b/src/blocks/index.ts @@ -3,12 +3,14 @@ export { Button, type ButtonProps } from './button'; export { Dropdown, type DropdownProps } from './dropdown'; export { HoverableSVG, type HoverableSVGProps } from './hoverableSVG'; export { Link, type LinkProps } from './link'; +export { Lozenge, type LozengeProps } from './lozenge'; export { Menu, type MenuProps, MenuItem, type MenuItemComponentProps } from './menu'; export { Separator, type SeparatorProps } from './separator'; export { Skeleton, type SkeletonProps } from './skeleton'; export { Tabs, type TabsProps, type TabItem } from './tabs'; export { Text, type TextProps } from './text'; export { Tooltip, type TooltipProps } from './tooltip'; +export { TextInput } from './textInput'; export * from './Blocks.colors'; export * from './Blocks.constants'; diff --git a/src/blocks/link/Link.tsx b/src/blocks/link/Link.tsx index 4e69e5f42e..f6be9c0693 100644 --- a/src/blocks/link/Link.tsx +++ b/src/blocks/link/Link.tsx @@ -10,6 +10,7 @@ import { Text, TextProps } from '../text'; export type LinkProps = RouterLinkProps & { css?: FlattenSimpleInterpolation; textProps?: TextProps; + isText?: boolean; } & TransformedHTMLAttributes; const StyledLink = styled(RouterLink).withConfig({ @@ -20,22 +21,23 @@ const StyledLink = styled(RouterLink).withConfig({ text-decoration: none; &:hover > * { - color: ${({ mode }) => getBlocksColor(mode, { light: 'pink-600', dark: 'pink-400' })}; + color: ${({ mode, isText }) => (isText ? getBlocksColor(mode, { light: 'pink-600', dark: 'pink-400' }) : '')}; } /* Extra CSS props */ ${(props) => props.css || ''} `; -const Link: FC = ({ textProps, ...props }) => { +const Link: FC = ({ textProps, isText = true, ...props }) => { const { mode } = useBlocksTheme(); return ( - {props?.children} + {isText ? {props?.children} : props.children} ); }; diff --git a/src/blocks/lozenge/Lozenge.constants.tsx b/src/blocks/lozenge/Lozenge.constants.tsx new file mode 100644 index 0000000000..3befe70c33 --- /dev/null +++ b/src/blocks/lozenge/Lozenge.constants.tsx @@ -0,0 +1,107 @@ +import { FlattenSimpleInterpolation, css } from 'styled-components'; + +//Types +import { LozengeSize, LozengeVariant } from './Lozenge.types'; +import { ThemeMode } from '../Blocks.types'; + +export const getLozengeVariantStyles = ({ + mode, + variant, +}: { + mode?: ThemeMode; + variant: LozengeVariant; +}): FlattenSimpleInterpolation => { + if (mode === 'dark') { + if (variant === 'primary') { + return css` + /* Lozenge tag container variant css */ + background-color: var(--pink-300); + color: var(--pink-800); + .icon { + color: var(--pink-400); + } + `; + } + return css``; + } + if (variant === 'primary') { + return css` + /* Lozenge tag container variant css */ + background-color: var(--pink-200); + color: var(--pink-600); + .icon { + color: var(--pink-400); + } + `; + } + return css``; +}; + +export const getLozengeSizeStyles = ({ + iconOnly, + size, +}: { + iconOnly?: boolean; + size: LozengeSize; +}): FlattenSimpleInterpolation => { + if (size === 'small') { + return css` + /* Lozenge tag container size css */ + + ${iconOnly + ? ` + border-radius: var(--r1); + gap: var(--s0); + padding: var(--s1); + ` + : ` + border-radius: var(--r1); + gap: var(--s1); + padding: var(--s1) var(--s2); + `} + + /* Lozenge text size css */ + leading-trim: both; + text-edge: cap; + font-size: 10px; + font-style: normal; + font-weight: 700; + line-height: 14px; + + .icon > span { + height: 8px; + width: 8px; + } + `; + } + + return css` + /* Lozenge tag container size css + note: - add medium small and large sizes */ + + ${iconOnly + ? ` + border-radius: 15.6px; + gap: var(--s0); + padding: 15.6px; + ` + : ` + border-radius: var(--r3); + gap: var(--s1); + padding: var(--s4); + `} + + /* Lozenge text size css */ + leading-trim: both; + text-edge: cap; + font-size: 18px; + font-style: normal; + font-weight: 500; + line-height: 16px; + + .icon > span { + height: 24px; + width: 24px; + } + `; +}; diff --git a/src/blocks/lozenge/Lozenge.tsx b/src/blocks/lozenge/Lozenge.tsx new file mode 100644 index 0000000000..8aa9708a89 --- /dev/null +++ b/src/blocks/lozenge/Lozenge.tsx @@ -0,0 +1,79 @@ +import { ReactNode, forwardRef } from 'react'; + +import styled, { FlattenSimpleInterpolation } from 'styled-components'; + +import { useBlocksTheme } from '../Blocks.hooks'; + +import { getLozengeSizeStyles, getLozengeVariantStyles } from './Lozenge.constants'; + +import { ModeProp, TransformedHTMLAttributes } from '../Blocks.types'; +import { LozengeSize, LozengeVariant } from './Lozenge.types'; + +export type LozengeProps = { + /* Child react nodes rendered by Box */ + children?: ReactNode; + /* Additional prop from styled components to apply custom css to Lozenge */ + css?: FlattenSimpleInterpolation; + /* Render an icon before lozenge contents */ + icon?: ReactNode; + /* Sets the size of the lozenge */ + size?: LozengeSize; + /* Sets the variant of the lozenge */ + variant?: LozengeVariant; +} & TransformedHTMLAttributes; + +type StyledLozengeProps = LozengeProps & ModeProp & { iconOnly: boolean }; + +const StyledLozenge = styled.div.withConfig({ + shouldForwardProp: (prop, defaultValidatorFn) => !['mode'].includes(prop) && defaultValidatorFn(prop), +})` + /* Common Lozenge CSS */ + + align-items: center; + display: flex; + font-family: var(--font-family); + justify-content: center; + white-space: nowrap; + + /* Common icon css added through CSS class */ + .icon { + display: flex; + align-items: center; + justify-content: center; + } + + /* Lozenge variant CSS styles */ + ${({ variant, mode }) => getLozengeVariantStyles({ variant: variant || 'primary', mode })} + + /* Lozenge and font size CSS styles */ + ${({ iconOnly, size }) => getLozengeSizeStyles({ iconOnly, size: size || 'small' })} + + /* Custom CSS applied via styled component css prop */ + ${(props) => props.css || ''} +`; + +const Lozenge = forwardRef( + ({ variant = 'primary', size = 'small', icon, children, ...props }, ref) => { + const { mode } = useBlocksTheme(); + const iconOnly = !children; + + return ( + + {icon && {icon}} + {children} + + ); + } +); + +Lozenge.displayName = 'Lozenge'; + +export { Lozenge }; diff --git a/src/blocks/lozenge/Lozenge.types.tsx b/src/blocks/lozenge/Lozenge.types.tsx new file mode 100644 index 0000000000..83ef5aad17 --- /dev/null +++ b/src/blocks/lozenge/Lozenge.types.tsx @@ -0,0 +1,5 @@ +export type LozengeVariant = 'primary'; + +export type LozengeSize = 'small'; + +export type LozengeVariantStyles = Record; diff --git a/src/blocks/lozenge/index.tsx b/src/blocks/lozenge/index.tsx new file mode 100644 index 0000000000..6d3411bff8 --- /dev/null +++ b/src/blocks/lozenge/index.tsx @@ -0,0 +1,3 @@ +export * from './Lozenge'; +export * from './Lozenge.constants'; +export * from './Lozenge.types'; diff --git a/src/blocks/text/Text.tsx b/src/blocks/text/Text.tsx index e5bd01c8f2..4454255f63 100644 --- a/src/blocks/text/Text.tsx +++ b/src/blocks/text/Text.tsx @@ -35,7 +35,8 @@ export type TextProps = { TransformedHTMLAttributes; const StyledText = styled.p.withConfig({ - shouldForwardProp: (prop, defaultValidatorFn) => !['mode', 'color'].includes(prop) && defaultValidatorFn(prop), + shouldForwardProp: (prop, defaultValidatorFn) => + !['mode', 'color', 'display'].includes(prop) && defaultValidatorFn(prop), })` /* Variant CSS */ ${({ variant }) => getVariantStyles(variant)} diff --git a/src/blocks/textInput/TextInput.constants.tsx b/src/blocks/textInput/TextInput.constants.tsx new file mode 100644 index 0000000000..37948a5744 --- /dev/null +++ b/src/blocks/textInput/TextInput.constants.tsx @@ -0,0 +1,26 @@ +import { TextInputStyles } from './TextInput.types'; + +//optimisation needed for this part +export const backgroundColor: TextInputStyles = { + error: { light: 'red-100', dark: 'red-100' }, + disabled: { light: 'gray-200', dark: 'gray-800' }, + default: { light: 'white', dark: 'gray-800' }, +}; + +export const borderColor: TextInputStyles = { + error: { light: 'red-400', dark: 'red-400' }, + disabled: { light: 'gray-300', dark: 'gray-900' }, + default: { light: 'gray-200', dark: 'gray-800' }, +}; + +export const textColor: TextInputStyles = { + error: { light: 'red-700', dark: 'red-700' }, + disabled: { light: 'gray-400', dark: 'gray-700' }, + default: { light: 'gray-1000', dark: 'gray-100' }, +}; + +export const placeholderColor: TextInputStyles = { + error: { light: 'red-600', dark: 'red-600' }, + disabled: { light: 'gray-400', dark: 'gray-700' }, + default: { light: 'gray-400', dark: 'gray-500' }, +}; diff --git a/src/blocks/textInput/TextInput.tsx b/src/blocks/textInput/TextInput.tsx new file mode 100644 index 0000000000..4492d87536 --- /dev/null +++ b/src/blocks/textInput/TextInput.tsx @@ -0,0 +1,190 @@ +import { ReactNode, forwardRef } from 'react'; + +import styled, { FlattenSimpleInterpolation } from 'styled-components'; + +import { ModeProp } from '../Blocks.types'; + +import { getTextInputState, getTextInputStateStyles } from './TextInput.utils'; +import { useBlocksTheme } from 'blocks/Blocks.hooks'; +import { CrossFilled } from 'blocks/icons'; +import { Text } from 'blocks/text'; + +export type TextInputProps = { + /* Additional prop from styled components to apply custom css to input field */ + css?: FlattenSimpleInterpolation; + /* Render an icon before input field contents */ + icon?: ReactNode; + /* Handles the change in input value */ + onChange?: (e: React.ChangeEvent) => void; + /* Input value */ + value?: string; + /* Input type value */ + type?: 'text' | 'password'; + /* Handles the clearing the entire input value */ + onClear?: () => void; + /* Label for the input field */ + label?: string; + /* TotalLength of input value */ + totalCount?: number; + /* Placeholder for input field */ + placeholder?: string; + /* Sets the input field to be compulsory */ + required?: boolean; + /* Sets the input field to error state */ + error?: boolean; + /* Sets the input field to success state */ + success?: boolean; + /* Sets button as disabled */ + disabled?: boolean; + /* Description shown below the input field */ + description?: string; + /* Error message shown below the input field */ + errorMessage?: string; +}; + +const StyledTextInput = styled.div` + /* Common Input field CSS */ + display: flex; + flex-direction: column; + width: inherit; + gap: var(--s2); + .label-count { + display: flex; + justify-content: space-between; + align-items: center; + } + + .input-container { + cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')}; + display: flex; + align-items: center; + font-family: var(--font-family); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + white-space: nowrap; + padding: var(--s0) var(--s3); + border-radius: var(--r3); + + /* Common icon css added through CSS class */ + .icon { + display: flex; + align-items: center; + justify-content: center; + margin-right: 2px; + span { + width: 18px; + height: 13.5px; + } + } + + & input { + flex: 1; + border: none; + background-color: transparent; + padding: var(--s3) var(--s0); + &:focus { + outline: none; + } + &:hover { + outline: none; + } + :disabled { + background-color: transparent; + } + } + } + /* TextInput type CSS styles */ + ${({ mode, error, disabled }) => + getTextInputStateStyles({ + mode, + state: getTextInputState({ error: !!error, disabled: !!disabled }), + })} + + /* Custom CSS applied via styled component css prop */ + ${(props) => props.css || ''} +`; + +const TextInput = forwardRef( + ( + { + disabled, + error, + success, + required, + label, + totalCount, + placeholder, + icon, + type = 'text', + onChange, + value, + onClear, + description, + errorMessage, + ...props + }, + ref + ) => { + const { mode } = useBlocksTheme(); + return ( + + {(label || totalCount) && ( +
+ {label && {label}} + {totalCount && {totalCount}} +
+ )} +
+ {icon && {icon}} + + {/* width is not getting applied */} + {onClear && ( + onClear?.()} + /> + )} +
+ {description && ( + + {description} + + )} + {errorMessage && ( + + {errorMessage} + + )} +
+ ); + } +); + +TextInput.displayName = 'TextInput'; + +export { TextInput }; diff --git a/src/blocks/textInput/TextInput.types.tsx b/src/blocks/textInput/TextInput.types.tsx new file mode 100644 index 0000000000..e21c67c238 --- /dev/null +++ b/src/blocks/textInput/TextInput.types.tsx @@ -0,0 +1,4 @@ +import { ThemeModeColors } from 'blocks/Blocks.types'; + +export type TextInputStates = 'error' | 'disabled' | 'default'; +export type TextInputStyles = Record; diff --git a/src/blocks/textInput/TextInput.utils.ts b/src/blocks/textInput/TextInput.utils.ts new file mode 100644 index 0000000000..aa2a64b823 --- /dev/null +++ b/src/blocks/textInput/TextInput.utils.ts @@ -0,0 +1,58 @@ +import { ThemeMode } from 'blocks/Blocks.types'; +import { getBlocksColor } from 'blocks/Blocks.utils'; + +import { backgroundColor, borderColor, placeholderColor, textColor } from './TextInput.constants'; +import { TextInputStates } from './TextInput.types'; + +export const getTextInputState = ({ error, disabled }: { error: boolean; disabled: boolean }): TextInputStates => { + if (error) { + return 'error'; + } else if (disabled) { + return 'disabled'; + } + return 'default'; +}; + +export const getTextInputStateStyles = ({ mode, state }: { mode: ThemeMode; state: TextInputStates }) => { + /*check all dark mode , label ,count and icon and placeholder colors + add success state + */ + return ` + + .input-container { + background-color: ${getBlocksColor(mode, backgroundColor[state])}; + + border: 1.5px solid ${getBlocksColor(mode, borderColor[state])}; + + + &:hover { + border: 1.5px solid ${getBlocksColor(mode, { + light: 'gray-300', + dark: 'gray-700', + })}; + }; + + &:focus-within { + border: 1.5px solid ${getBlocksColor( + mode, + state === 'error' ? borderColor.error : { light: 'pink-300', dark: 'pink-300' } + )}; + }; + + .icon,.clear { + color: ${getBlocksColor(mode, { + light: 'gray-300', + dark: 'gray-100', + })}; + }; + + & input{ + color: ${getBlocksColor(mode, textColor[state])}; + ::placeholder { + color: ${getBlocksColor(mode, placeholderColor[state])}; + + }; + } + +}`; +}; diff --git a/src/blocks/textInput/index.ts b/src/blocks/textInput/index.ts new file mode 100644 index 0000000000..a7fcf6f39c --- /dev/null +++ b/src/blocks/textInput/index.ts @@ -0,0 +1 @@ +export * from './TextInput'; diff --git a/src/common/components/ContentLayout.tsx b/src/common/components/ContentLayout.tsx new file mode 100644 index 0000000000..30a78d8853 --- /dev/null +++ b/src/common/components/ContentLayout.tsx @@ -0,0 +1,34 @@ +// React and other libraries +import { FC, ReactNode } from 'react'; + +// third party libraries +import { css } from 'styled-components'; + +//components +import { Box } from 'blocks'; + +type ContentLayoutProps = { + children: ReactNode; +}; + +const ContentLayout: FC = ({ children }) => { + return ( + + {children} + + ); +}; +export { ContentLayout }; diff --git a/src/common/components/index.ts b/src/common/components/index.ts new file mode 100644 index 0000000000..fd0249d4e7 --- /dev/null +++ b/src/common/components/index.ts @@ -0,0 +1,3 @@ +export * from './ContentLayout'; +export * from './SubscribeChannelDropdown'; +export * from './UnsubscribeChannelDropdown'; diff --git a/src/common/index.ts b/src/common/index.ts index 4cc90d02bd..9330290d59 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -1 +1,2 @@ export * from './hooks'; +export * from './components'; diff --git a/src/components/chat/unlockProfile/UnlockProfile.tsx b/src/components/chat/unlockProfile/UnlockProfile.tsx index f0fe2bf145..3c463121af 100644 --- a/src/components/chat/unlockProfile/UnlockProfile.tsx +++ b/src/components/chat/unlockProfile/UnlockProfile.tsx @@ -42,9 +42,9 @@ const UnlockProfile = ({ InnerComponentProps, onClose }: UnlockProfileModalProps const { type } = InnerComponentProps; const theme = useTheme(); - const { handleConnectWallet, connectWallet } = useContext(AppContext); + const { handleConnectWallet } = useContext(AppContext); - const { account, wallet } = useAccount(); + const { account, wallet, connect } = useAccount(); // Ensures if profile is stored then true is returned else false const [rememberMe, setRememberMe] = useState(false); @@ -193,7 +193,7 @@ const UnlockProfile = ({ InnerComponentProps, onClose }: UnlockProfileModalProps activeStatus={activeStatus.status} status={PROFILESTATE.CONNECT_WALLET} disabled={activeStatus.status !== PROFILESTATE.CONNECT_WALLET && true} - onClick={() => connectWallet()} + onClick={() => connect()} > Connect Wallet diff --git a/src/components/chat/unlockProfile/UnlockProfileWrapper.tsx b/src/components/chat/unlockProfile/UnlockProfileWrapper.tsx index 6c08ab8ec6..aa2e4b87a2 100644 --- a/src/components/chat/unlockProfile/UnlockProfileWrapper.tsx +++ b/src/components/chat/unlockProfile/UnlockProfileWrapper.tsx @@ -1,5 +1,5 @@ import { ItemVV2 } from 'components/reusables/SharedStylingV2'; -import useModalBlur from 'hooks/useModalBlur'; +import useModalBlur, { MODAL_POSITION } from 'hooks/useModalBlur'; import { useEffect } from 'react'; import styled from 'styled-components'; import UnlockProfile from './UnlockProfile'; @@ -11,13 +11,14 @@ export enum UNLOCK_PROFILE_TYPE { interface IntroContainerProps { type?: UNLOCK_PROFILE_TYPE; + showConnectModal?: boolean } const DEFAULT_PROPS = { type: UNLOCK_PROFILE_TYPE.MODAL, }; -const UnlockProfileWrapper = ({ type = DEFAULT_PROPS.type }: IntroContainerProps) => { +const UnlockProfileWrapper = ({ type = DEFAULT_PROPS.type, showConnectModal }: IntroContainerProps) => { const { isModalOpen: isProfileModalOpen, showModal: showProfileModal, @@ -25,7 +26,7 @@ const UnlockProfileWrapper = ({ type = DEFAULT_PROPS.type }: IntroContainerProps } = useModalBlur(); useEffect(() => { - if (type === UNLOCK_PROFILE_TYPE.MODAL) { + if (type === UNLOCK_PROFILE_TYPE.MODAL && showConnectModal) { showProfileModal(); } }, [type]); @@ -39,6 +40,7 @@ const UnlockProfileWrapper = ({ type = DEFAULT_PROPS.type }: IntroContainerProps }} modalRadius="24px" modalBorder={false} + modalPosition={MODAL_POSITION.ON_PARENT} /> ); } else { diff --git a/src/config/AppPaths.ts b/src/config/AppPaths.ts index 98f74704f6..4588a9d8b9 100644 --- a/src/config/AppPaths.ts +++ b/src/config/AppPaths.ts @@ -7,6 +7,10 @@ enum APP_PATHS { Spaces = '/spaces', Channels = '/channels', Dashboard = '/dashboard', + Rewards = '/points', + RewardsActivities = '/points/activity', + RewardsLeaderboard = '/points/leaderboard', + PointsVault = '/points/vault', Send = '/send', Receive = '/receive', Govern = '/govern', @@ -22,7 +26,7 @@ enum APP_PATHS { Support = '/support', UserSettings = '/user/settings', ChannelSettings = '/channel/settings', - ClaimGalxe = 'claim/galxe' + ClaimGalxe = 'claim/galxe', } export default APP_PATHS; diff --git a/src/config/Globals.js b/src/config/Globals.js index 4b3971cec8..3fd2745e45 100644 --- a/src/config/Globals.js +++ b/src/config/Globals.js @@ -101,6 +101,7 @@ export default { LEFT_BAR_WIDTH: 250, COLLAPSABLE_LEFT_BAR_WIDTH: 90, COLLAPSABLE_RIGHT_BAR_WIDTH: 90, + NO_LEFT_BAR_WIDTH: 0, NAVBAR_SECTIONS: { PRIMARY: 1, diff --git a/src/config/config-alpha.js b/src/config/config-alpha.js index d55b7bfb4e..097a76bea9 100644 --- a/src/config/config-alpha.js +++ b/src/config/config-alpha.js @@ -17,6 +17,7 @@ export const config = { ipfsInfuraAPIKey: import.meta.env.VITE_APP_IPFS_INFURA_API_KEY || '22rfiNb1J645FdehoqbKMpLbF6V', ipfsInfuraAPISecret: import.meta.env.VITE_APP_IPFS_INFURA_API_SECRET || 'a757597f020425c3ae532e6be84de552', + discord_client_id: import.meta.env.VITE_APP_DISCORD_CLIENT_ID, /** * Allowed Networks @@ -51,16 +52,16 @@ export const config = { * Analaytics + Firebase */ googleAnalyticsId: 'UA-165415629-1', - vapidKey: 'BFRmmAEEXOhk31FIsooph5CxlXKh6N0_NocUWHzvtpoUEvqQTwLXu6XtwkrH7ckyr2CvVz1ll-8q4oo6-ZqFJPY', + vapidKey: 'BOMOB--KihZkwM8SQ_OrPEsuu8UcSYiRB9AvMjsWil3WJDmxBEcDex8g4d5rFGgA8U-7esfRM5pvR98jaE1nX0M', firebaseConfig: { - apiKey: 'AIzaSyClOk4qP0ttFW-BPnXy7WT920xfdXSbFu8', - authDomain: 'epns-internal.firebaseapp.com', - databaseURL: 'https://epns-internal.firebaseio.com', - projectId: 'epns-internal', - storageBucket: 'epns-internal.appspot.com', - messagingSenderId: '755180533582', - appId: '1:755180533582:web:752ff8db31905506b7d01f', - measurementId: 'G-ZJH2T7R9S1', + apiKey: 'AIzaSyBrzkFPyNmVDFzGY7dKz2HocUO4m-ni-Fc', + authDomain: 'epns-ethereum-push-service.firebaseapp.com', + databaseURL: 'https://epns-ethereum-push-service.firebaseio.com', + projectId: 'epns-ethereum-push-service', + storageBucket: 'epns-ethereum-push-service.appspot.com', + messagingSenderId: '915758146133', + appId: '1:915758146133:web:2de388356233f5c22f2adc', + measurementId: 'G-X1L5P2E4EP', }, /** diff --git a/src/config/config-dev.js b/src/config/config-dev.js index 2446a52d61..77760246db 100644 --- a/src/config/config-dev.js +++ b/src/config/config-dev.js @@ -17,6 +17,7 @@ export const config = { ipfsInfuraAPIKey: import.meta.env.VITE_APP_IPFS_INFURA_API_KEY || '2DVyu4GEkiFksOrihKk8NMEWWwY', ipfsInfuraAPISecret: import.meta.env.VITE_APP_IPFS_INFURA_API_SECRET || '8e39eefc3d70b851b47f90611d40cfa5', + discord_client_id: import.meta.env.VITE_APP_DISCORD_CLIENT_ID, /** * Allowed Networks @@ -53,16 +54,15 @@ export const config = { * Analaytics + Firebase */ googleAnalyticsId: 'UA-165415629-5', - vapidKey: 'BFRmmAEEXOhk31FIsooph5CxlXKh6N0_NocUWHzvtpoUEvqQTwLXu6XtwkrH7ckyr2CvVz1ll-8q4oo6-ZqFJPY', + vapidKey: 'BJYsH1MYRqzfuzduyHLNaUfZCYdAahcJXsdWzdTqleWox0vOLaycyVPdy_J9XWzSIKvRu0xkwxo75mhDiVJhNnw', firebaseConfig: { - apiKey: 'AIzaSyClOk4qP0ttFW-BPnXy7WT920xfdXSbFu8', - authDomain: 'epns-internal.firebaseapp.com', - databaseURL: 'https://epns-internal.firebaseio.com', - projectId: 'epns-internal', - storageBucket: 'epns-internal.appspot.com', - messagingSenderId: '755180533582', - appId: '1:755180533582:web:752ff8db31905506b7d01f', - measurementId: 'G-ZJH2T7R9S1', + apiKey: 'AIzaSyB4aXx2pJ9T5sw0Q1bba3jI1EAGp0Z5kBI', + authDomain: 'push-dev-a6a63.firebaseapp.com', + projectId: 'push-dev-a6a63', + storageBucket: 'push-dev-a6a63.appspot.com', + messagingSenderId: '974364469170', + appId: '1:974364469170:web:47fd6304c6cf36b5bfe6ab', + measurementId: 'G-5YR8N35DY4', }, /** diff --git a/src/config/config-general.js b/src/config/config-general.js index 0c7630b8f0..eaa9277804 100644 --- a/src/config/config-general.js +++ b/src/config/config-general.js @@ -4,7 +4,7 @@ export const config = { * API Calls Related */ // infuraAPIKey: 'e214e9781e104829bc93941508a45b58' - infuraAPIKey: '1afc24a3e4c443a0990d6e5efc2ecde5' + infuraAPIKey: '1afc24a3e4c443a0990d6e5efc2ecde5', } @@ -49,7 +49,7 @@ export const abis = { rockstarV2: rockstarV2Abi, NFTRewardsV2: NFTRewardsV2, stakingV2: stakingV2Abi, - uniV2LpToken:uniV2LpTokenAbi, - pushCoreV2:PushCoreV2, + uniV2LpToken: uniV2LpTokenAbi, + pushCoreV2: PushCoreV2, pushReveal: PushRevealAbi }; diff --git a/src/config/config-localhost.js b/src/config/config-localhost.js index f796104bbf..31c920ef4d 100644 --- a/src/config/config-localhost.js +++ b/src/config/config-localhost.js @@ -46,16 +46,15 @@ export const config = { * Analaytics + Firebase */ googleAnalyticsId: 'UA-165415629-5', - vapidKey: 'BFRmmAEEXOhk31FIsooph5CxlXKh6N0_NocUWHzvtpoUEvqQTwLXu6XtwkrH7ckyr2CvVz1ll-8q4oo6-ZqFJPY', + vapidKey: 'BJYsH1MYRqzfuzduyHLNaUfZCYdAahcJXsdWzdTqleWox0vOLaycyVPdy_J9XWzSIKvRu0xkwxo75mhDiVJhNnw', firebaseConfig: { - apiKey: 'AIzaSyClOk4qP0ttFW-BPnXy7WT920xfdXSbFu8', - authDomain: 'epns-internal.firebaseapp.com', - databaseURL: 'https://epns-internal.firebaseio.com', - projectId: 'epns-internal', - storageBucket: 'epns-internal.appspot.com', - messagingSenderId: '755180533582', - appId: '1:755180533582:web:752ff8db31905506b7d01f', - measurementId: 'G-ZJH2T7R9S1', + apiKey: 'AIzaSyB4aXx2pJ9T5sw0Q1bba3jI1EAGp0Z5kBI', + authDomain: 'push-dev-a6a63.firebaseapp.com', + projectId: 'push-dev-a6a63', + storageBucket: 'push-dev-a6a63.appspot.com', + messagingSenderId: '974364469170', + appId: '1:974364469170:web:47fd6304c6cf36b5bfe6ab', + measurementId: 'G-5YR8N35DY4', }, /** diff --git a/src/config/config-prod.js b/src/config/config-prod.js index aea4beaab2..2cf4b64029 100644 --- a/src/config/config-prod.js +++ b/src/config/config-prod.js @@ -17,6 +17,7 @@ export const config = { ipfsInfuraAPIKey: import.meta.env.VITE_APP_IPFS_INFURA_API_KEY || '2DVyu4GEkiFksOrihKk8NMEWWwY', ipfsInfuraAPISecret: import.meta.env.VITE_APP_IPFS_INFURA_API_SECRET || '8e39eefc3d70b851b47f90611d40cfa5', + discord_client_id: import.meta.env.VITE_APP_DISCORD_CLIENT_ID, /** * Allowed Networks @@ -52,16 +53,16 @@ export const config = { * Analaytics + Firebase */ googleAnalyticsId: 'UA-165415629-1', - vapidKey: 'BFRmmAEEXOhk31FIsooph5CxlXKh6N0_NocUWHzvtpoUEvqQTwLXu6XtwkrH7ckyr2CvVz1ll-8q4oo6-ZqFJPY', + vapidKey: 'BOMOB--KihZkwM8SQ_OrPEsuu8UcSYiRB9AvMjsWil3WJDmxBEcDex8g4d5rFGgA8U-7esfRM5pvR98jaE1nX0M', firebaseConfig: { - apiKey: 'AIzaSyClOk4qP0ttFW-BPnXy7WT920xfdXSbFu8', - authDomain: 'epns-internal.firebaseapp.com', - databaseURL: 'https://epns-internal.firebaseio.com', - projectId: 'epns-internal', - storageBucket: 'epns-internal.appspot.com', - messagingSenderId: '755180533582', - appId: '1:755180533582:web:752ff8db31905506b7d01f', - measurementId: 'G-ZJH2T7R9S1', + apiKey: 'AIzaSyBrzkFPyNmVDFzGY7dKz2HocUO4m-ni-Fc', + authDomain: 'epns-ethereum-push-service.firebaseapp.com', + databaseURL: 'https://epns-ethereum-push-service.firebaseio.com', + projectId: 'epns-ethereum-push-service', + storageBucket: 'epns-ethereum-push-service.appspot.com', + messagingSenderId: '915758146133', + appId: '1:915758146133:web:2de388356233f5c22f2adc', + measurementId: 'G-X1L5P2E4EP', }, /** diff --git a/src/config/config-staging.js b/src/config/config-staging.js index 54af695579..1be1b680ef 100644 --- a/src/config/config-staging.js +++ b/src/config/config-staging.js @@ -17,6 +17,7 @@ export const config = { ipfsInfuraAPIKey: import.meta.env.VITE_APP_IPFS_INFURA_API_KEY || '2DVyu4GEkiFksOrihKk8NMEWWwY', ipfsInfuraAPISecret: import.meta.env.VITE_APP_IPFS_INFURA_API_SECRET || '8e39eefc3d70b851b47f90611d40cfa5', + discord_client_id: import.meta.env.VITE_APP_DISCORD_CLIENT_ID, /** * Allowed Networks @@ -54,7 +55,7 @@ export const config = { * Analaytics + Firebase */ googleAnalyticsId: 'UA-165415629-5', - vapidKey: 'BFRmmAEEXOhk31FIsooph5CxlXKh6N0_NocUWHzvtpoUEvqQTwLXu6XtwkrH7ckyr2CvVz1ll-8q4oo6-ZqFJPY', + vapidKey: 'BO-oYHtENkaP1nRQMmXAmjbkyWz_4sms1Z5OzE8B7h5gmuXiePvLmbXRiJNA233WtzzEo83yWZAVX1blsJQkNFg', firebaseConfig: { apiKey: 'AIzaSyClOk4qP0ttFW-BPnXy7WT920xfdXSbFu8', authDomain: 'epns-internal.firebaseapp.com', diff --git a/src/contexts/AppContext.tsx b/src/contexts/AppContext.tsx index afb8e9d63a..e38743eb4c 100644 --- a/src/contexts/AppContext.tsx +++ b/src/contexts/AppContext.tsx @@ -267,9 +267,6 @@ const AppContextProvider = ({ children }) => { }); } - // connect stream as well - await setupStream(userInstance); - console.debug('src::contexts::AppContext::initializePushSDK::User Intance Initialized', userInstance); if (userInstance) { setBlockedLoading({ @@ -281,6 +278,9 @@ const AppContextProvider = ({ children }) => { }); } dispatch(setUserPushSDKInstance(userInstance)); + + // connect stream as well + await setupStream(userInstance); return userInstance; } catch (error) { // Handle initialization error diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 2d5f5e8452..922c359ef8 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,5 +1,6 @@ -export * from "./useDeviceWidthCheck" -export * from "./useInactiveListener" -export * from './useSDKSocket' -export * from "./useAsyncOperation"; -export * from "./useAccount"; +export * from './useDeviceWidthCheck'; +export * from './useInactiveListener'; +export * from './useSDKSocket'; +export * from './useAsyncOperation'; +export * from './useAccount'; +export * from './useCopy'; diff --git a/src/hooks/useAccount.tsx b/src/hooks/useAccount.tsx index 371aee0e5f..9343252dd7 100644 --- a/src/hooks/useAccount.tsx +++ b/src/hooks/useAccount.tsx @@ -47,6 +47,7 @@ export const useAccount = () => { return { wallet: wallet ? wallet : readOnlyWallet, walletAddress: wallet ? wallet.accounts[0]?.address : readOnlyWallet, + isWalletConnected: !!wallet?.accounts?.length, connecting, connect, disconnect, diff --git a/src/hooks/useCopy.ts b/src/hooks/useCopy.ts new file mode 100644 index 0000000000..f1c60849ba --- /dev/null +++ b/src/hooks/useCopy.ts @@ -0,0 +1,25 @@ +import { useState, useRef } from 'react'; + +export const useCopy = () => { + const [isCopied, setIsCopied] = useState(false); + const textRef = useRef(null); + + const copyToClipboard = () => { + if (textRef.current) { + const textToCopy = textRef.current.innerText; + navigator.clipboard + .writeText(textToCopy) + .then(() => { + setIsCopied(true); + setTimeout(() => { + setIsCopied(false); + }, 2000); + }) + .catch((err) => { + console.error('Failed to copy text: ', err); + }); + } + }; + + return { textRef, isCopied, copyToClipboard }; +}; diff --git a/src/modules/dashboard/Dashboard.tsx b/src/modules/dashboard/Dashboard.tsx index 6f148d72c8..a62a0b9298 100644 --- a/src/modules/dashboard/Dashboard.tsx +++ b/src/modules/dashboard/Dashboard.tsx @@ -19,6 +19,8 @@ const Dashboard: FC = () => { display="flex" margin={{ initial: 's4 s6 s4 s6', ml: 's4' }} gap={{ ml: 's6' }} + height="100%" + width="-webkit-fill-available" > = ({ { padding="s1" height="fit-content" justifyContent="space-between" - borderRadius="var(--r4)" + borderRadius="r4" width={{ lp: 'auto', initial: 'fit-content' }} > {dahboardChannelTabs?.map((channelTab, index) => { @@ -62,7 +62,7 @@ const ChannelTabsSection = () => { width={{ ll: '100%' }} alignItems="center" padding="s2 s3" - borderRadius="var(--r4)" + borderRadius="r4" backgroundColor={ selectedChannelTab === channelTab.value ? { dark: 'gray-800', light: 'white' } : 'transparent' } @@ -84,7 +84,7 @@ const ChannelTabsSection = () => { display="flex" flexDirection="column" overflow="hidden auto" - borderRadius="var(--r6)" + borderRadius="r6" minHeight="285px" maxHeight="285px" border={{ light: '1px solid gray-200', dark: '1px solid gray-800' }} diff --git a/src/modules/dashboard/components/ChannelVariantsSection.tsx b/src/modules/dashboard/components/ChannelVariantsSection.tsx index 333d6dfc4c..cf07800a6a 100644 --- a/src/modules/dashboard/components/ChannelVariantsSection.tsx +++ b/src/modules/dashboard/components/ChannelVariantsSection.tsx @@ -4,7 +4,7 @@ import { FC } from 'react'; // Components import { Box, Text } from 'blocks'; import { ChannelTabsSection } from './ChannelTabsSection'; -import { SnapSection } from './SnapSection'; +import { RewardsSection } from './RewardsSection'; import { RecommendedChatsList } from './RecommendedChatsList'; export type ChannelVariantsSectionProps = {}; @@ -13,7 +13,7 @@ const ChannelVariantsSection: FC = () => { return ( = () => { Recommended Chats - + ); diff --git a/src/modules/dashboard/components/FeaturedChannels.tsx b/src/modules/dashboard/components/FeaturedChannels.tsx index 22fd4f0206..8e12c05634 100644 --- a/src/modules/dashboard/components/FeaturedChannels.tsx +++ b/src/modules/dashboard/components/FeaturedChannels.tsx @@ -40,7 +40,7 @@ const FeaturedChannels: FC = () => { return ( = () => { )} diff --git a/src/modules/dashboard/components/FeaturedChannelsList.tsx b/src/modules/dashboard/components/FeaturedChannelsList.tsx index a621889f9d..10ac248ba3 100644 --- a/src/modules/dashboard/components/FeaturedChannelsList.tsx +++ b/src/modules/dashboard/components/FeaturedChannelsList.tsx @@ -12,10 +12,9 @@ import { css } from 'styled-components'; export type FeaturedChannelsListProps = { listRef: any; featuredChannelsList: FeaturedChannelDetailsProps[]; - projectedItemWidth: number; }; -const FeaturedChannelsList: FC = ({ listRef, featuredChannelsList, projectedItemWidth }) => { +const FeaturedChannelsList: FC = ({ listRef, featuredChannelsList }) => { return ( = ({ listRef, featured gap="s6" padding="s4 s0" overflow="scroll" - width={{ initial: 'calc(100vw - 346px)' }} css={css` overflow-y: scroll; `} > {featuredChannelsList?.map((channel) => { - return ( - - ); + return ; })} ); diff --git a/src/modules/dashboard/components/FeaturedChannelsListItem.tsx b/src/modules/dashboard/components/FeaturedChannelsListItem.tsx index 0e337d9c4a..a0511e2106 100644 --- a/src/modules/dashboard/components/FeaturedChannelsListItem.tsx +++ b/src/modules/dashboard/components/FeaturedChannelsListItem.tsx @@ -29,11 +29,10 @@ import { ImageV3 } from '../Dashboard.styled'; type FeaturedChannelsListItemProps = { channelAddress: string; - width: string; }; const FeaturedChannelsListItem: FC = (props) => { - const { channelAddress, width } = props; + const { channelAddress } = props; const { wallet } = useAccount(); const isWalletConnected = !!wallet?.accounts?.length; @@ -62,12 +61,13 @@ const FeaturedChannelsListItem: FC = (props) => { flexDirection="column" border={{ light: '1px solid gray-200', dark: '1px solid gray-800' }} padding="s6" - borderRadius="24px" + borderRadius="r6" gap="s3" - width={{ initial: width }} + width={{ initial: '27.35%', tb: 'fit-content', ml: '75%' }} css={css` flex-shrink: 0; `} + minHeight={{ initial: 'auto', tb: '180px' }} > = `} > {channelListArray.map((channel: FeaturedChannelDetailsProps) => ( - + ))} ); diff --git a/src/modules/dashboard/components/RecommendedChatListItem.tsx b/src/modules/dashboard/components/RecommendedChatListItem.tsx index e932765287..6cc47bb491 100644 --- a/src/modules/dashboard/components/RecommendedChatListItem.tsx +++ b/src/modules/dashboard/components/RecommendedChatListItem.tsx @@ -42,7 +42,10 @@ const RecommendedChatListItem: FC = ({ chat }) => - + ); }; -export { SnapSection }; +export { RewardsSection }; diff --git a/src/modules/dashboard/index.ts b/src/modules/dashboard/index.ts index a7cc81c0cc..161f5478b9 100644 --- a/src/modules/dashboard/index.ts +++ b/src/modules/dashboard/index.ts @@ -1,5 +1 @@ -import { Dashboard } from './Dashboard'; - -export default Dashboard; -export type { DashboardProps } from './Dashboard'; - +export { Dashboard, type DashboardProps } from './Dashboard'; diff --git a/src/modules/pointsVault/PointsVault.constants.ts b/src/modules/pointsVault/PointsVault.constants.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/modules/pointsVault/PointsVault.tsx b/src/modules/pointsVault/PointsVault.tsx new file mode 100644 index 0000000000..a36d93312a --- /dev/null +++ b/src/modules/pointsVault/PointsVault.tsx @@ -0,0 +1,21 @@ +import { Box } from 'blocks'; +import { FC, useState } from 'react'; +import { PointsVaultLogin } from './components/PointsVaultLogin'; +import { PointsVaultListContainer } from './components/PointsVaultListContainer'; +import { PointsVaultView } from './PointsVault.types'; + +const PointsVault: FC = () => { + const [activeView, setActiveView] = useState('login'); + + return ( + + {activeView === 'login' && } + {activeView === 'list' && } + + ); +}; + +export { PointsVault }; diff --git a/src/modules/pointsVault/PointsVault.types.ts b/src/modules/pointsVault/PointsVault.types.ts new file mode 100644 index 0000000000..bad818bbe0 --- /dev/null +++ b/src/modules/pointsVault/PointsVault.types.ts @@ -0,0 +1,2 @@ +export type PointsVaultView = 'list' | 'login'; +export type VaultLoginformValues = { username: string; password: string }; diff --git a/src/modules/pointsVault/components/PointsVaultApprovedList.tsx b/src/modules/pointsVault/components/PointsVaultApprovedList.tsx new file mode 100644 index 0000000000..43a4fac72b --- /dev/null +++ b/src/modules/pointsVault/components/PointsVaultApprovedList.tsx @@ -0,0 +1,101 @@ +import { Box } from 'blocks'; +import { useQueryClient } from '@tanstack/react-query'; +import InfiniteScroll from 'react-infinite-scroller'; +import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; +import { PointsVaultListColumns } from './PointsVaultListColumns'; +import { PointsVaultListItem } from './PointsVaultListItem'; +import { + PointsVaultActivitiesResponse, + pointsVaultRejectedUsers, + useGetPointsVaultApprovedUsers, + usePointsVaultToken, +} from 'queries'; +import { LeaderBoardNullState } from 'modules/rewards/components/LeaderboardNullState'; + +type PointsVaultApprovedListProps = { + query: { + wallet?: string; + twitter?: string; + }; +}; + +const PointsVaultApprovedList = ({ query }: PointsVaultApprovedListProps) => { + const token = usePointsVaultToken(); + + const queryClient = useQueryClient(); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, refetch } = + useGetPointsVaultApprovedUsers({ + status: 'COMPLETED', + token, + pageSize: 20, + twitter: query.twitter, + wallet: query.wallet, + }); + + const hasMoreData = !isFetchingNextPage && hasNextPage; + + const pointsVaultList = isLoading + ? Array(5).fill(0) + : data?.pages.flatMap((page: PointsVaultActivitiesResponse) => page.activities) || []; + + if (!pointsVaultList.length) { + return ( + + ); + } + + const handleRefetch = () => { + refetch(); + queryClient.invalidateQueries({ queryKey: [pointsVaultRejectedUsers] }); + }; + + return ( + + + + fetchNextPage()} + hasMore={hasMoreData} + loader={ + + + + } + useWindow={false} + threshold={150} + > + {pointsVaultList.map((item, index) => ( + + ))} + + + + ); +}; + +export { PointsVaultApprovedList }; diff --git a/src/modules/pointsVault/components/PointsVaultListActionButtons.tsx b/src/modules/pointsVault/components/PointsVaultListActionButtons.tsx new file mode 100644 index 0000000000..2a47c6e55f --- /dev/null +++ b/src/modules/pointsVault/components/PointsVaultListActionButtons.tsx @@ -0,0 +1,78 @@ +import { Box, Button } from 'blocks'; +import { + PointsVaultActivity, + PointsVaultStatus, + useApprovePointsVaultUser, + usePointsVaultToken, + useRejectPointsVaultUser, +} from 'queries'; + +export type PointsVaultListActionButtonsProps = { + item: PointsVaultActivity; + status: PointsVaultStatus; + refetch: (actions?: PointsVaultStatus) => void; +}; + +const PointsVaultListActionButtons = ({ status, item, refetch }: PointsVaultListActionButtonsProps) => { + const token = usePointsVaultToken(); + + const { mutate: rejectPointsVaultUser, isPending: isPendingRejection } = useRejectPointsVaultUser(); + + const { mutate: approvePointsVaultUser, isPending: isPendingApproval } = useApprovePointsVaultUser(); + + const handleReject = async () => { + rejectPointsVaultUser( + { + activityId: item.activityId, + currentStatus: status, + token, + }, + { onSuccess: () => refetch('REJECTED') } + ); + }; + + const handleApprove = async () => { + approvePointsVaultUser( + { + activityId: item.activityId, + currentStatus: status, + token, + }, + { onSuccess: () => refetch('COMPLETED') } + ); + }; + + return ( + + {status !== 'COMPLETED' && ( + + )} + {status !== 'REJECTED' && ( + + )} + + ); +}; + +export { PointsVaultListActionButtons }; diff --git a/src/modules/pointsVault/components/PointsVaultListColumns.tsx b/src/modules/pointsVault/components/PointsVaultListColumns.tsx new file mode 100644 index 0000000000..93bd5e6173 --- /dev/null +++ b/src/modules/pointsVault/components/PointsVaultListColumns.tsx @@ -0,0 +1,57 @@ +import { FC } from 'react'; +import { Box, Text } from 'blocks'; + +const PointsVaultListColumns: FC = () => { + return ( + + + + USER + + + + + + TWITTER LINK + + + + + + FOLLOWERS + + + + + + ACTION + + + + ); +}; + +export { PointsVaultListColumns }; diff --git a/src/modules/pointsVault/components/PointsVaultListContainer.tsx b/src/modules/pointsVault/components/PointsVaultListContainer.tsx new file mode 100644 index 0000000000..7a7ca979cd --- /dev/null +++ b/src/modules/pointsVault/components/PointsVaultListContainer.tsx @@ -0,0 +1,126 @@ +import { Box, Link, Search, Tabs, Text, TextInput } from 'blocks'; +import { PointsVaultApprovedList } from './PointsVaultApprovedList'; +import PushIcon from 'assets/snap/PushIcon.svg'; +import { css } from 'styled-components'; +import { PointsVaultPendingList } from './PointsVaultPendingList'; +import { PointsVaultRejectedList } from './PointsVaultRejectedList'; +import { useDebounce } from 'react-use'; +import { useCallback, useState } from 'react'; +import { ethers } from 'ethers'; + +const PointsVaultListContainer = () => { + const [query, setQuery] = useState(''); + const [debouncedQuery, setDebouncedQuery] = useState({}); + + const getFormattedQuery = useCallback((qry: string) => { + if (!qry) return {}; + const isAddress = ethers.utils.isAddress(qry); + const key = isAddress ? 'wallet' : 'twitter'; + const value = isAddress ? `eip155:${qry}` : qry; + return { [key]: value }; + }, []); + + useDebounce(() => setDebouncedQuery(getFormattedQuery(query)), 500, [query]); + + return ( + + + Points Vault + + + + + Ensure the user has at-least 50 Followers and is following + + + + @pushprotocol + + {' '} + + on Twitter + + + Push Icon + + + + } + value={query} + onChange={(e) => setQuery(e.target.value)} + /> + + + setQuery('')} + items={[ + { + key: 'PENDING', + label: 'Pending', + children: , + }, + { + key: 'COMPLETED', + label: 'Approved', + children: , + }, + { + key: 'REJECTED', + label: 'Rejected', + children: , + }, + ]} + /> + + ); +}; + +export { PointsVaultListContainer }; diff --git a/src/modules/pointsVault/components/PointsVaultListItem.tsx b/src/modules/pointsVault/components/PointsVaultListItem.tsx new file mode 100644 index 0000000000..34cd6db2d5 --- /dev/null +++ b/src/modules/pointsVault/components/PointsVaultListItem.tsx @@ -0,0 +1,103 @@ +import { Box, Link, Skeleton, Text } from 'blocks'; +import { useBlocksTheme } from 'blocks/Blocks.hooks'; +import { PointsVaultActivity, PointsVaultStatus, useGetUserTwitterDetails, usePointsVaultToken } from 'queries'; +import { css } from 'styled-components'; +import { PointsVaultListActionButtons } from './PointsVaultListActionButtons'; +import { caip10ToWallet } from 'helpers/w2w'; + +export type PointsVaultListItemProps = { + item: PointsVaultActivity; + isLoading: boolean; + refetch: (actions?: PointsVaultStatus) => void; +}; + +const PointsVaultListItem = ({ isLoading, item, refetch }: PointsVaultListItemProps) => { + const { mode } = useBlocksTheme(); + const token = usePointsVaultToken(); + const { data } = useGetUserTwitterDetails(item.data?.twitter, token); + + return ( + + + + + {caip10ToWallet(item.userWallet)} + + + + + + + + + https://x.com/{item.data?.twitter} + + + + + + + + + {data?.followersCount ?? '-'} + + + + + + + + + + + ); +}; + +export { PointsVaultListItem }; diff --git a/src/modules/pointsVault/components/PointsVaultLogin.tsx b/src/modules/pointsVault/components/PointsVaultLogin.tsx new file mode 100644 index 0000000000..fd217d7717 --- /dev/null +++ b/src/modules/pointsVault/components/PointsVaultLogin.tsx @@ -0,0 +1,143 @@ +import { FC } from 'react'; + +import { useFormik } from 'formik'; +import * as yup from 'yup'; +import { MdError } from 'react-icons/md'; + +import { usePointsVaultUserLogin } from 'queries'; +import useToast from 'hooks/useToast'; + +import { Box, Button, TextInput, PushLogo, Text } from 'blocks'; + +import { PointsVaultView, VaultLoginformValues } from '../PointsVault.types'; + +export type PointsVaultLoginProps = { + handleSetActiveView: (component: PointsVaultView) => void; +}; + +const PointsVaultLogin: FC = ({ handleSetActiveView }) => { + const { mutate: pointsVaultUserLogin, isPending } = usePointsVaultUserLogin(); + + const validationSchema = yup.object().shape({ + username: yup.string().required('Username is required'), + password: yup.string().required('Password is required'), + }); + + const formik = useFormik({ + initialValues: { + username: '', + password: '', + }, + validationSchema: validationSchema, + onSubmit: (values) => { + handleLogin({ ...values }); + }, + }); + const toast = useToast(); + + const handleLogin = ({ username, password }: { username: string; password: string }) => { + pointsVaultUserLogin( + { + username, + password, + }, + { + onSuccess: (response) => { + //response.token is the auth token + handleSetActiveView('list'); + }, + onError: (error: any) => { + if (error.name) { + toast.showMessageToast({ + toastTitle: 'Error', + toastMessage: error.response.data.error, + toastType: 'ERROR', + getToastIcon: (size) => ( + + ), + }); + } + }, + } + ); + }; + + return ( + + + + + Push + + + + + Points Vault Login + + +
+ + + + + + + + + +
+
+
+
+ ); +}; + +export { PointsVaultLogin }; diff --git a/src/modules/pointsVault/components/PointsVaultPendingList.tsx b/src/modules/pointsVault/components/PointsVaultPendingList.tsx new file mode 100644 index 0000000000..271d211292 --- /dev/null +++ b/src/modules/pointsVault/components/PointsVaultPendingList.tsx @@ -0,0 +1,109 @@ +import { Box } from 'blocks'; +import { useQueryClient } from '@tanstack/react-query'; +import InfiniteScroll from 'react-infinite-scroller'; +import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; +import { PointsVaultListColumns } from './PointsVaultListColumns'; +import { PointsVaultListItem } from './PointsVaultListItem'; +import { + PointsVaultActivitiesResponse, + PointsVaultStatus, + pointsVaultApprovedUsers, + pointsVaultRejectedUsers, + useGetPointsVaultPendingUsers, + usePointsVaultToken, +} from 'queries'; +import { LeaderBoardNullState } from 'modules/rewards/components/LeaderboardNullState'; + +type PointsVaultPendingListProps = { + query: { + wallet?: string; + twitter?: string; + }; +}; + +const PointsVaultPendingList = ({ query }: PointsVaultPendingListProps) => { + const token = usePointsVaultToken(); + + const queryClient = useQueryClient(); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, refetch } = + useGetPointsVaultPendingUsers({ + status: 'PENDING', + token, + pageSize: 20, + twitter: query.twitter, + wallet: query.wallet, + }); + + const hasMoreData = !isFetchingNextPage && hasNextPage; + + const pointsVaultList = isLoading + ? Array(5).fill(0) + : data?.pages.flatMap((page: PointsVaultActivitiesResponse) => page.activities) || []; + + if (!pointsVaultList.length) { + return ( + + ); + } + + const handleRefetch = (action?: PointsVaultStatus) => { + refetch(); + + const key = + action === 'COMPLETED' ? pointsVaultApprovedUsers : action === 'REJECTED' ? pointsVaultRejectedUsers : undefined; + + if (key) { + queryClient.invalidateQueries({ queryKey: [key] }); + } + }; + + return ( + + + + fetchNextPage()} + hasMore={hasMoreData} + loader={ + + + + } + useWindow={false} + threshold={150} + > + {pointsVaultList.map((item, index) => ( + + ))} + + + + ); +}; + +export { PointsVaultPendingList }; diff --git a/src/modules/pointsVault/components/PointsVaultRejectedList.tsx b/src/modules/pointsVault/components/PointsVaultRejectedList.tsx new file mode 100644 index 0000000000..d41a403edf --- /dev/null +++ b/src/modules/pointsVault/components/PointsVaultRejectedList.tsx @@ -0,0 +1,101 @@ +import { Box } from 'blocks'; +import { useQueryClient } from '@tanstack/react-query'; +import InfiniteScroll from 'react-infinite-scroller'; +import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; +import { PointsVaultListColumns } from './PointsVaultListColumns'; +import { PointsVaultListItem } from './PointsVaultListItem'; +import { + PointsVaultActivitiesResponse, + pointsVaultApprovedUsers, + useGetPointsVaultRejectedUsers, + usePointsVaultToken, +} from 'queries'; +import { LeaderBoardNullState } from 'modules/rewards/components/LeaderboardNullState'; + +type PointsVaultRejectedListProps = { + query: { + wallet?: string; + twitter?: string; + }; +}; + +const PointsVaultRejectedList = ({ query }: PointsVaultRejectedListProps) => { + const token = usePointsVaultToken(); + + const queryClient = useQueryClient(); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, refetch } = + useGetPointsVaultRejectedUsers({ + status: 'REJECTED', + token, + pageSize: 20, + twitter: query.twitter, + wallet: query.wallet, + }); + + const hasMoreData = !isFetchingNextPage && hasNextPage; + + const pointsVaultList = isLoading + ? Array(5).fill(0) + : data?.pages.flatMap((page: PointsVaultActivitiesResponse) => page.activities) || []; + + if (!pointsVaultList.length) { + return ( + + ); + } + + const handleRefetch = () => { + refetch(); + queryClient.invalidateQueries({ queryKey: [pointsVaultApprovedUsers] }); + }; + + return ( + + + + fetchNextPage()} + hasMore={hasMoreData} + loader={ + + + + } + useWindow={false} + threshold={150} + > + {pointsVaultList.map((item, index) => ( + + ))} + + + + ); +}; + +export { PointsVaultRejectedList }; diff --git a/src/modules/pointsVault/index.ts b/src/modules/pointsVault/index.ts new file mode 100644 index 0000000000..ce512cf4e8 --- /dev/null +++ b/src/modules/pointsVault/index.ts @@ -0,0 +1 @@ +export { PointsVault } from './PointsVault'; diff --git a/src/modules/rewards/Rewards.constants.ts b/src/modules/rewards/Rewards.constants.ts new file mode 100644 index 0000000000..023e3671a8 --- /dev/null +++ b/src/modules/rewards/Rewards.constants.ts @@ -0,0 +1,16 @@ +import { RewardsTabsList } from './Rewards.types'; + +export const rewardsTabsList: RewardsTabsList = [ + { + value: 'dashboard', + label: 'Dashboard' + }, + { + value: 'activity', + label: 'Reward Activities' + }, + { + value: 'leaderboard', + label: 'Leaderboard' + } +]; diff --git a/src/modules/rewards/Rewards.tsx b/src/modules/rewards/Rewards.tsx new file mode 100644 index 0000000000..4183a77f0f --- /dev/null +++ b/src/modules/rewards/Rewards.tsx @@ -0,0 +1,109 @@ +// React and other libraries +import { FC, useEffect } from 'react'; + +// third party libraries +import { css } from 'styled-components'; +import { useSelector } from 'react-redux'; + +//Hooks +import { useAccount } from 'hooks'; +import { useRewardsTabs } from './hooks/useRewardsTabs'; +import { useGenerateUserId } from './hooks/useGenerateUserId'; +import { useDiscordSession } from './hooks/useDiscordSession'; + +//Types +import { UserStoreType } from 'types'; +import { UNLOCK_PROFILE_TYPE } from 'components/chat/unlockProfile/UnlockProfile'; + +//helpers +import { walletToCAIP10 } from 'helpers/w2w'; + +//Components +import { Box, Text } from 'blocks'; +import { ReferralSection } from './components/ReferralSection'; +import { RewardsTabsContainer } from './components/RewardsTabsContainer'; +import UnlockProfileWrapper from 'components/chat/unlockProfile/UnlockProfileWrapper'; + +export type RewardsProps = {}; + +const Rewards: FC = () => { + const { userPushSDKInstance } = useSelector((state: UserStoreType) => state.user); + + const { account } = useAccount(); + + const caip10WalletAddress = walletToCAIP10({ account }); + + // Used to set the discord session after discord redirects back to the Dapp. + useDiscordSession(); + + const { activeTab, handleSetActiveTab } = useRewardsTabs(); + + const { showConnectModal, setConnectModalVisibility } = useGenerateUserId(caip10WalletAddress); + + useEffect(() => { + if (activeTab !== 'activity') { + setConnectModalVisibility(false); + } + + if (activeTab === 'activity' && userPushSDKInstance && userPushSDKInstance.readmode()) { + setConnectModalVisibility(true); + } + }, [activeTab, account, userPushSDKInstance]); + + const heading = activeTab === 'leaderboard' ? 'Push Reward Points' : 'Introducing Push Reward Points Program'; + + return ( + + + + {heading} + + + {heading} + + + + + {activeTab === 'dashboard' && } + + + {userPushSDKInstance && userPushSDKInstance?.readmode() && showConnectModal && ( + + + + )} + + ); +}; + +export { Rewards }; diff --git a/src/modules/rewards/Rewards.types.ts b/src/modules/rewards/Rewards.types.ts new file mode 100644 index 0000000000..c497239922 --- /dev/null +++ b/src/modules/rewards/Rewards.types.ts @@ -0,0 +1,3 @@ +export type RewardsTabs = 'dashboard' | 'activity' | 'leaderboard'; + +export type RewardsTabsList = Array<{ label: string; value: RewardsTabs }>; diff --git a/src/modules/rewards/components/ActivityButton.tsx b/src/modules/rewards/components/ActivityButton.tsx new file mode 100644 index 0000000000..3946a86874 --- /dev/null +++ b/src/modules/rewards/components/ActivityButton.tsx @@ -0,0 +1,62 @@ +// React and other libraries +import { FC } from 'react'; + +// Component +import { DiscordActivityButton } from './DiscordActivityButton'; +import { TwitterActivityButton } from './TwitterActivityButton'; +import { DefaultActivityButton } from './DefaultActivityButton'; +import { ActivityStatusButton } from './ActivityStatusButton'; + +//Queries +import { ActvityType, UsersActivity } from 'queries'; + +type ActivityButtonProps = { + userId: string; + activityTypeId: string; + activityType: ActvityType; + refetchActivity: () => void; + setErrorMessage: (errorMessage: string) => void; + usersSingleActivity: UsersActivity; +}; + +const ActivityButton: FC = ({ + userId, + activityTypeId, + refetchActivity, + activityType, + setErrorMessage, + usersSingleActivity +}) => { + + switch (usersSingleActivity.status) { + case 'COMPLETED': + return ; + case 'PENDING': + return ; + default: + switch (activityType) { + case 'follow_push_on_discord': + return + case 'follow_push_on_twitter': + return + default: + return + } + } +}; + +export { ActivityButton }; diff --git a/src/modules/rewards/components/ActivityStatusButton.tsx b/src/modules/rewards/components/ActivityStatusButton.tsx new file mode 100644 index 0000000000..cf01a25264 --- /dev/null +++ b/src/modules/rewards/components/ActivityStatusButton.tsx @@ -0,0 +1,47 @@ +// React and other libraries +import { FC } from 'react'; + +//Components +import { Box, Button, Skeleton } from 'blocks'; + + +type ActivityStatusButtonProps = { + label: string; + disabledLabel?: string; + isLoading?: boolean; + onClick?: () => void; + disabled: boolean; + +} + +const ActivityStatusButton: FC = ({ + label, + isLoading, + onClick, + disabled, + disabledLabel +}) => { + return ( + + + + + + ); +}; + +export { ActivityStatusButton }; \ No newline at end of file diff --git a/src/modules/rewards/components/DashboardSection.tsx b/src/modules/rewards/components/DashboardSection.tsx new file mode 100644 index 0000000000..11a391e3e5 --- /dev/null +++ b/src/modules/rewards/components/DashboardSection.tsx @@ -0,0 +1,75 @@ +// React and other libraries +import { FC } from 'react'; + +//Hooks +import { useAccount } from 'hooks'; +import { useGetUserRewardsDetails } from 'queries'; + +//helpers +import { walletToCAIP10 } from 'helpers/w2w'; + +//components +import { Box, Text } from 'blocks'; +import { DashboardSectionHeader } from './DashboardSectionHeader'; +import { DashboardSectionPoints } from './DashboardSectionPoints'; + +export type DashboardSectionProps = { + onGetStarted: () => void; +}; + +const DashboardSection: FC = ({ onGetStarted }) => { + const { isWalletConnected, account } = useAccount(); + const caip10WalletAddress = walletToCAIP10({ account }); + const { + data: userDetails, + isSuccess, + refetch, + isLoading: isUserLoading, + isFetching, + } = useGetUserRewardsDetails({ caip10WalletAddress: caip10WalletAddress, enabled: isWalletConnected }); + + const isLoading = isUserLoading || !isSuccess; + + return ( + <> + + + Dashboard + + + + + + refetch()} + /> + + + + + + ); +}; + +export { DashboardSection }; diff --git a/src/modules/rewards/components/DashboardSectionHeader.tsx b/src/modules/rewards/components/DashboardSectionHeader.tsx new file mode 100644 index 0000000000..ac86142456 --- /dev/null +++ b/src/modules/rewards/components/DashboardSectionHeader.tsx @@ -0,0 +1,75 @@ +// React and other libraries +import { FC } from 'react'; + +// Third party libraries +import { css } from 'styled-components'; + +//components +import { Box, Button, Points, Text } from 'blocks'; + +export type DashboardSectionHeaderProps = { + onGetStarted: () => void; +}; + +const DashboardSectionHeader: FC = ({ onGetStarted }) => { + return ( + + + + + + + Earn Rewards for Exploring! + + + + Push Points are the new way to prove that you belong to the Push community and access to some cool + surprises in the future. + + + + + + + + + ); +}; + +export { DashboardSectionHeader }; diff --git a/src/modules/rewards/components/DashboardSectionPoints.tsx b/src/modules/rewards/components/DashboardSectionPoints.tsx new file mode 100644 index 0000000000..f53a78a3fa --- /dev/null +++ b/src/modules/rewards/components/DashboardSectionPoints.tsx @@ -0,0 +1,131 @@ +// React and other libraries +import { FC } from 'react'; + +//Hooks +import { useAccount } from 'hooks'; + +//components +import { Box, HoverableSVG, Refresh, Skeleton, Text } from 'blocks'; + +export type DashboardSectionPointsProps = { + title: string; + points: number | undefined; + rank?: number; + usersInvited?: number; + refetch?: () => void; + isLoading: boolean; + isFetching?: boolean; +}; + +const DashboardSectionPoints: FC = ({ + title, + points, + rank, + usersInvited, + refetch, + isLoading, + isFetching, +}) => { + const { isWalletConnected } = useAccount(); + + return ( + + + + {title} + + + {refetch && isWalletConnected && ( + + } + > + + + {isFetching ? 'Updating...' : 'Update'} + + + + )} + + + + + {isWalletConnected && ( + + {points !== undefined ? points : '0'} + + )} + + + {!isWalletConnected && ( + + 0 + + )} + + {/* show rank only when user has points more than 0 */} + + {points && points > 0 && rank != null ? ( + + {rank > 0 && `Rank #${rank}`} + + ) : null} + + + + {usersInvited && usersInvited > 0 ? ( + + {usersInvited > 1 ? `${usersInvited} Users Invited` : `${usersInvited} User Invited`} + + ) : null} + + + + ); +}; + +export { DashboardSectionPoints }; diff --git a/src/modules/rewards/components/DefaultActivityButton.tsx b/src/modules/rewards/components/DefaultActivityButton.tsx new file mode 100644 index 0000000000..6d8eb0f9fb --- /dev/null +++ b/src/modules/rewards/components/DefaultActivityButton.tsx @@ -0,0 +1,27 @@ +// React and other libraries +import { FC } from 'react'; + +//Components +import { ActivityStatusButton } from './ActivityStatusButton'; + +type DefaultActivityButtonProps = { + userId: string; + activityTypeId: string; + refetchActivity: () => void; +}; + +const DefaultActivityButton: FC = ({ userId, activityTypeId, refetchActivity }) => { + const handleVerification = () => { + console.log(' Verification is called', userId, activityTypeId, refetchActivity); + }; + + return ( + + ); +}; + +export { DefaultActivityButton }; diff --git a/src/modules/rewards/components/DiscordActivityButton.tsx b/src/modules/rewards/components/DiscordActivityButton.tsx new file mode 100644 index 0000000000..a9f17ed9aa --- /dev/null +++ b/src/modules/rewards/components/DiscordActivityButton.tsx @@ -0,0 +1,133 @@ +// React and other libraries +import { FC, useEffect, useState } from 'react'; + +// Third-party libraries +import { PushAPI } from '@pushprotocol/restapi'; +import { useSelector } from 'react-redux'; + +import { appConfig } from 'config'; +import { useClaimRewardsActivity, useGetUserDiscordDetails } from 'queries'; + +// Components +import { ActivityStatusButton } from './ActivityStatusButton'; + +//helpers +import { generateVerificationProof } from '../utils/generateVerificationProof'; + +//Types +import { UserStoreType } from 'types'; + +type DiscordActivityButtonProps = { + userId: string; + activityTypeId: string; + refetchActivity: () => void; + setErrorMessage: (errorMessage: string) => void; +}; + +const DiscordActivityButton: FC = ({ + userId, + activityTypeId, + refetchActivity, + setErrorMessage, +}) => { + const token = sessionStorage.getItem('access_token'); + + const { userPushSDKInstance } = useSelector((state: UserStoreType) => state.user); + + useEffect(() => { + setErrorMessage(''); + }, []); + + const handleConnect = () => { + const clientID = appConfig.discord_client_id; + + const redirectURI = window.location.href; //it will redirect the user to the current page + + const scope = 'identify email guilds.members.read'; + + const authURL = `https://discord.com/api/oauth2/authorize?client_id=${clientID}&redirect_uri=${redirectURI}&response_type=token&scope=${scope}`; + + window.location.href = authURL; + }; + + const handleVerification = () => { + setErrorMessage(''); + if (token) { + handleVerify(userPushSDKInstance); + } else { + handleConnect(); + } + }; + + const { data: userDiscordDetails } = useGetUserDiscordDetails(token as string); + + useEffect(() => { + if (token && userDiscordDetails) { + handleVerify(userPushSDKInstance); + } + }, [token, userDiscordDetails]); + + const { mutate: claimRewardsActivity } = useClaimRewardsActivity({ + userId, + activityTypeId, + }); + + const [verifying, setVerifying] = useState(false); + + const handleVerify = async (userPushSDKInstance: PushAPI) => { + if (userDiscordDetails && token) { + setVerifying(true); + const data = { + discord: userDiscordDetails.global_name, + discord_token: token, + }; + + const verificationProof = await generateVerificationProof(data, userPushSDKInstance); + + if (verificationProof == null || verificationProof == undefined) { + if (userPushSDKInstance && userPushSDKInstance.readmode()) { + setVerifying(false); + setErrorMessage('Please Enable Push profile'); + } + return; + } + + claimRewardsActivity( + { + userId, + activityTypeId, + pgpPublicKey: userPushSDKInstance.pgpPublicKey as string, + data: data, + verificationProof: verificationProof as string, + }, + { + onSuccess: (response) => { + if (response.status === 'COMPLETED') { + refetchActivity(); + setVerifying(false); + setErrorMessage(''); + } + }, + onError: (error: any) => { + console.log('Error in creating activiy', error); + setVerifying(false); + if (error.name) { + setErrorMessage(error.response.data.error); + } + }, + } + ); + } + }; + + return ( + + ); +}; + +export { DiscordActivityButton }; diff --git a/src/modules/rewards/components/LeaderBoardList.tsx b/src/modules/rewards/components/LeaderBoardList.tsx new file mode 100644 index 0000000000..454685d9a8 --- /dev/null +++ b/src/modules/rewards/components/LeaderBoardList.tsx @@ -0,0 +1,80 @@ +// React and other libraries +import { FC } from 'react'; +import InfiniteScroll from 'react-infinite-scroller'; + +//Hooks +import { useGetRewardsLeaderboard, ModelledLeaderBoardUser } from 'queries'; + +//Components +import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; +import { LeaderboardListItem } from './LeaderBoardListItem'; +import { Box } from 'blocks'; + +//Helpers +import { caip10ToWallet } from 'helpers/w2w'; +import { LeaderBoardNullState } from './LeaderboardNullState'; +import { LeaderboardListColumns } from './LeaderBoardListColumns'; + +const LeaderBoardList: FC = () => { + const { data, isError, refetch, fetchNextPage, hasNextPage, isLoading, isFetchingNextPage } = + useGetRewardsLeaderboard({ pageSize: 20 }); + + // If there are channels then render them else render 10 skeletons + const leaderboardList = isLoading ? Array(10).fill(0) : data?.pages.flatMap((page) => page.users) || []; + + const hasMoreData = !isFetchingNextPage && hasNextPage; + + return !leaderboardList.length ? ( + + ) : ( + !!leaderboardList.length && ( + + + + fetchNextPage()} + hasMore={hasMoreData} + loader={ + + + + } + useWindow={false} + threshold={150} + > + {leaderboardList.map((item: ModelledLeaderBoardUser, index: number) => ( + + ))} + + + + ) + ); +}; + +export { LeaderBoardList }; diff --git a/src/modules/rewards/components/LeaderBoardListColumns.tsx b/src/modules/rewards/components/LeaderBoardListColumns.tsx new file mode 100644 index 0000000000..2f49ab172f --- /dev/null +++ b/src/modules/rewards/components/LeaderBoardListColumns.tsx @@ -0,0 +1,37 @@ +import { FC } from 'react'; +import { Box, Text } from 'blocks'; + +const LeaderboardListColumns: FC = () => { + return ( + + + + RANK + + + USER + + + + TOTAL POINTS + + + ); +}; + +export { LeaderboardListColumns }; diff --git a/src/modules/rewards/components/LeaderBoardListItem.tsx b/src/modules/rewards/components/LeaderBoardListItem.tsx new file mode 100644 index 0000000000..bbc8671d9f --- /dev/null +++ b/src/modules/rewards/components/LeaderBoardListItem.tsx @@ -0,0 +1,132 @@ +// React and other libraries +import { FC, useContext } from 'react'; + +// Third-party libraries +import { css } from 'styled-components'; +import BlockiesSvg from 'blockies-react-svg'; +//Hooks +import { useBlocksTheme } from 'blocks/Blocks.hooks'; + +//Components +import { Box, Skeleton, Text } from 'blocks'; + +import { AppContext } from 'contexts/AppContext'; + +//Hooks +import { useResolveWeb3Name } from 'hooks/useResolveWeb3Name'; + +//Utility helper +import { shortenText } from 'helpers/UtilityHelper'; + +//Types +import { AppContextType } from 'types/context'; + +export type LeaderboardListItemProps = { + rank: number; + address: string; + points: number; + isLoading: boolean; +}; + +const LeaderboardListItem: FC = ({ rank, address, points, isLoading }) => { + const { mode } = useBlocksTheme(); + const { web3NameList }: AppContextType = useContext(AppContext)!; + + useResolveWeb3Name(address); + + const web3Name = web3NameList[address]; + const displayName = web3Name ? web3Name : shortenText(address, 10, 10); + + return ( + + + + + + {rank} + + + + + + + + {displayName} + + + {displayName} + + + + + + + + + {points} + + + {points} + + + + + ); +}; + +export { LeaderboardListItem }; diff --git a/src/modules/rewards/components/LeaderBoardSection.tsx b/src/modules/rewards/components/LeaderBoardSection.tsx new file mode 100644 index 0000000000..094efcfdb7 --- /dev/null +++ b/src/modules/rewards/components/LeaderBoardSection.tsx @@ -0,0 +1,38 @@ +// React and other libraries +import { FC } from 'react'; + +//Components +import { LeaderBoardList } from './LeaderBoardList'; +import { Box, Text } from 'blocks'; + +export type LeaderBoardSectionProps = {}; + +const LeaderBoardSection: FC = () => { + return ( + + + Leaderboard + + + Leaderboard + + + + + ); +}; + +export { LeaderBoardSection }; diff --git a/src/modules/rewards/components/LeaderboardNullState.tsx b/src/modules/rewards/components/LeaderboardNullState.tsx new file mode 100644 index 0000000000..ad08856b24 --- /dev/null +++ b/src/modules/rewards/components/LeaderboardNullState.tsx @@ -0,0 +1,79 @@ +import { FC } from 'react'; + +import { QueryObserverResult, RefetchOptions } from '@tanstack/react-query'; + +//Hooks +import { LeaderboardModelledResponse } from 'queries'; + +//Components +import { Box, Button, Refresh, Text, UserSwitch } from 'blocks'; + +export type LeaderBoardNullStateProps = { + heading?: string; + subHeading?: string; + error?: boolean; + refetchLeaderboard?: + | (() => void) + | ((options?: RefetchOptions | undefined) => Promise>); +}; +const LeaderBoardNullState: FC = ({ + heading, + subHeading, + error = false, + refetchLeaderboard, +}) => { + return ( + + + + {heading && ( + + {heading} + + )} + {subHeading && ( + + {subHeading} + + )} + + + {error && ( + + )} + + ); +}; + +export { LeaderBoardNullState }; diff --git a/src/modules/rewards/components/ReferralSection.tsx b/src/modules/rewards/components/ReferralSection.tsx new file mode 100644 index 0000000000..e76593b46e --- /dev/null +++ b/src/modules/rewards/components/ReferralSection.tsx @@ -0,0 +1,137 @@ +// React and other libraries +import { FC } from 'react'; + +// third party libraries +import { css } from 'styled-components'; + +//hooks +import { useAccount, useCopy } from 'hooks'; +import { useGetUserRewardsDetails } from 'queries'; + +//helpers +import { walletToCAIP10 } from 'helpers/w2w'; +import { getPreviewBasePath } from '../../../../basePath'; + +// components +import { Box, Button, Copy, Text, Referral, Skeleton } from 'blocks'; + +export type ReferralSectionProps = {}; + +const ReferralSection: FC = () => { + const previewBasePath = getPreviewBasePath() || ''; + const baseUrl = window.location.origin + previewBasePath; + + const { isWalletConnected, account, connect } = useAccount(); + const caip10WalletAddress = walletToCAIP10({ account }); + + const { + data: userDetails, + isSuccess, + isLoading: isUserLoading, + } = useGetUserRewardsDetails({ + caip10WalletAddress: caip10WalletAddress, + enabled: isWalletConnected, + }); + + const isLoading = isUserLoading || !isSuccess; + + const { textRef, isCopied, copyToClipboard } = useCopy(); + + const handleConnectWallet = () => { + connect(); + }; + + return ( + + + + + Onboard Users on Push.
Earn Points. +
+ + + Earn +12% of any Points your invites earn, and +2% of any Points your invite’s invites earn. + + +
+ + {isWalletConnected && ( + + + + + {baseUrl}/points?ref={userDetails?.userId} + + + + + + )} + + {!isWalletConnected && ( + + + + )} +
+ + + + +
+ ); +}; + +export { ReferralSection }; diff --git a/src/modules/rewards/components/RewardsActivitiesList.tsx b/src/modules/rewards/components/RewardsActivitiesList.tsx new file mode 100644 index 0000000000..89514db2da --- /dev/null +++ b/src/modules/rewards/components/RewardsActivitiesList.tsx @@ -0,0 +1,82 @@ +// React and other libraries +import { FC } from 'react'; +import InfiniteScroll from 'react-infinite-scroller'; + +//Hooks +import { Activity } from 'queries'; +import { useAccount } from 'hooks'; +import { useGetRewardsActivities, useGetUserRewardsDetails } from 'queries/hooks/rewards'; + +//Components +import { RewardsActivitiesListItem } from './RewardsActivitiesListItem'; +import LoaderSpinner, { LOADER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; + +//Helpers +import { walletToCAIP10 } from 'helpers/w2w'; + +//Components +import { Box } from 'blocks'; + +export type RewardActivitiesProps = {}; + +const RewardsActivitiesList: FC = () => { + const { account } = useAccount(); + + const { + data: rewardActivitiesResponse, + isLoading: isLoadingActivities, + fetchNextPage, + hasNextPage, + isFetchingNextPage + } = useGetRewardsActivities({ pageSize: 5 }); + + // Getting user Id by wallet address + const caip10WalletAddress = walletToCAIP10({ account }); + const { data: userDetails, isLoading: isLoadingUserDetails } = useGetUserRewardsDetails({ + caip10WalletAddress: caip10WalletAddress, + }); + + const isLoading = isLoadingUserDetails || isLoadingActivities; + + // If there are activities then render them else render 5 skeletons + const activityList = isLoading ? Array(3).fill(0) : rewardActivitiesResponse?.pages.flatMap((page) => page.activities) || []; + + const hasMoreData = !isFetchingNextPage && hasNextPage; + + return ( + + fetchNextPage()} + hasMore={hasMoreData} + loader={ + + + + } + useWindow={false} + threshold={150} + > + {activityList.map((activity: Activity) => ( + + ))} + + + ); +}; + +export { RewardsActivitiesList }; diff --git a/src/modules/rewards/components/RewardsActivitiesListItem.tsx b/src/modules/rewards/components/RewardsActivitiesListItem.tsx new file mode 100644 index 0000000000..057b460eed --- /dev/null +++ b/src/modules/rewards/components/RewardsActivitiesListItem.tsx @@ -0,0 +1,208 @@ +// React and other libraries +import { FC, useState } from 'react'; + +//Hooks +import { Activity, useGetRewardsActivity } from 'queries'; + +//Components +import { Box, ErrorFilled, InfoFilled, Lozenge, RewardsBell, Skeleton, Text } from 'blocks'; +import { ActivityButton } from './ActivityButton'; +import { RewardsActivityIcon } from './RewardsActivityIcon'; +import { RewardsActivityTitle } from './RewardsActivityTitle'; +import { useSelector } from 'react-redux'; +import { UserStoreType } from 'types'; + +export type RewardActivitiesListItemProps = { + userId: string; + activity: Activity; + isLoadingItem: boolean; +}; + +const getUpdatedExpiryTime = (timestamp: number) => { + const date = new Date(timestamp * 1000); + const days = date.getDate(); + return days; +}; + +const RewardsActivitiesListItem: FC = ({ userId, activity, isLoadingItem }) => { + const { + data: usersSingleActivity, + isLoading, + refetch: refetchActivity, + } = useGetRewardsActivity({ userId, activityId: activity.id }, { enabled: !!userId }); + + const { userPushSDKInstance } = useSelector((state: UserStoreType) => state.user); + + const [errorMessage, setErrorMessage] = useState(''); + + return ( + + + + + + + {/* Rewards Contents */} + + {/* Rewards Description */} + + + + + {!!activity.expiryType && ( + + + {`Expires in ${getUpdatedExpiryTime(activity.expiryType)} days`.toUpperCase()} + + + )} + + + {/* We don't need to show the Description when the title is discord and twitter according to the design */} + {activity.activityType !== 'follow_push_on_discord' && + activity.activityType !== 'follow_push_on_twitter' && ( + + + {activity.activityDesc} + + + )} + + + {/* Rewards Points */} + + + + + {activity.points?.toLocaleString()} points + + + + + + {/* Buttons Logic */} + {usersSingleActivity && userPushSDKInstance && !userPushSDKInstance?.readmode() && ( + + + + )} + + + + {errorMessage && ( + + + + {errorMessage} + + + )} + + {usersSingleActivity?.status === 'PENDING' && ( + + + + + Verification Pending: Expected completion within 24-72 hours. + + + )} + + + ); +}; + +export { RewardsActivitiesListItem }; diff --git a/src/modules/rewards/components/RewardsActivitiesSection.tsx b/src/modules/rewards/components/RewardsActivitiesSection.tsx new file mode 100644 index 0000000000..86c9648499 --- /dev/null +++ b/src/modules/rewards/components/RewardsActivitiesSection.tsx @@ -0,0 +1,22 @@ +import { Box, Text } from 'blocks'; +import { RewardsActivitiesList } from './RewardsActivitiesList'; + +const RewardsActivitiesSection = () => { + return ( + + + Activities + + + + ); +}; + +export { RewardsActivitiesSection }; diff --git a/src/modules/rewards/components/RewardsActivityIcon.tsx b/src/modules/rewards/components/RewardsActivityIcon.tsx new file mode 100644 index 0000000000..a96e41c0e3 --- /dev/null +++ b/src/modules/rewards/components/RewardsActivityIcon.tsx @@ -0,0 +1,22 @@ +import { FC } from 'react'; + +import { Discord, RewardsActivity, Twitter } from 'blocks'; +import { ActvityType } from 'queries'; + +type RewardsActivityIconProp = { + type: ActvityType +} + +const RewardsActivityIcon: FC = ({ type }) => { + + if (type === 'follow_push_on_discord') { + return ; + } + + if (type === 'follow_push_on_twitter') { + return ; + } + + return ; +}; +export { RewardsActivityIcon }; \ No newline at end of file diff --git a/src/modules/rewards/components/RewardsActivityTitle.tsx b/src/modules/rewards/components/RewardsActivityTitle.tsx new file mode 100644 index 0000000000..f17b304fca --- /dev/null +++ b/src/modules/rewards/components/RewardsActivityTitle.tsx @@ -0,0 +1,37 @@ +import { Box, Link, Skeleton, Text } from 'blocks'; +import { FC } from 'react'; +import { getRewardsActivityTitle } from '../utils/getRewardsActivityTitle'; + +type RewardsActivityTitleProps = { + activityTitle: string; + isLoading: boolean; +}; + +const RewardsActivityTitle: FC = ({ activityTitle, isLoading }) => { + const extractedTitle = getRewardsActivityTitle(activityTitle); + + if (extractedTitle) { + const { preText, url, linkedText, postText } = extractedTitle; + return ( + + + {preText} + + + {linkedText} + + + {postText} + + + ); + } else { + return + + {activityTitle} + + + } +}; + +export { RewardsActivityTitle }; diff --git a/src/modules/rewards/components/RewardsTabs.tsx b/src/modules/rewards/components/RewardsTabs.tsx new file mode 100644 index 0000000000..4f2fd9e544 --- /dev/null +++ b/src/modules/rewards/components/RewardsTabs.tsx @@ -0,0 +1,61 @@ +import { FC } from 'react'; + +import { css } from 'styled-components'; + +import { rewardsTabsList } from '../Rewards.constants'; + +import { useBlocksTheme } from 'blocks/Blocks.hooks'; + +import { Box, Text } from 'blocks'; + +import { RewardsTabs as RewardsTabsType } from '../Rewards.types'; + +export type RewardsTabsProps = { + activeTab: RewardsTabsType; + handleSetActiveTab: (tab: RewardsTabsType) => void; +}; + +const RewardsTabs: FC = ({ activeTab, handleSetActiveTab }) => { + const { mode } = useBlocksTheme(); + return ( + + {rewardsTabsList.map((tab) => ( + handleSetActiveTab(tab.value)} + css={css` + margin-bottom: -1px; + border-bottom: ${tab.value === activeTab ? '2px solid var(--pink-400)' : '0px'}; + `} + > + + {tab.label} + + + {tab.label} + + + ))} + + ); +}; + +export { RewardsTabs }; diff --git a/src/modules/rewards/components/RewardsTabsContainer.tsx b/src/modules/rewards/components/RewardsTabsContainer.tsx new file mode 100644 index 0000000000..608ecc2bd7 --- /dev/null +++ b/src/modules/rewards/components/RewardsTabsContainer.tsx @@ -0,0 +1,58 @@ +import { FC, useEffect, useState } from 'react'; + +//Components +import { Box } from 'blocks'; +import { RewardsTabs } from './RewardsTabs'; +import { DashboardSection } from './DashboardSection'; +import { LeaderBoardSection } from './LeaderBoardSection'; +import { RewardsActivitiesSection } from './RewardsActivitiesSection'; + +//Types +import { RewardsTabs as RewardsTabsType } from '../Rewards.types'; +import { useSelector } from 'react-redux'; +import { UserStoreType } from 'types'; +import UnlockProfileWrapper from 'components/chat/unlockProfile/UnlockProfileWrapper'; +import { UNLOCK_PROFILE_TYPE } from 'components/chat/unlockProfile/UnlockProfile'; +import { css } from 'styled-components'; + +export type RewardsTabsContainerProps = { + activeTab: RewardsTabsType; + handleSetActiveTab: (tab: RewardsTabsType) => void; +}; + +const RewardsTabsContainer: FC = ({ + activeTab, + handleSetActiveTab, +}) => { + + + + return ( + + + + + {activeTab === 'dashboard' && handleSetActiveTab('activity')} />} + {activeTab === 'activity' && } + {activeTab === 'leaderboard' && } + + + + ); +}; + +export { RewardsTabsContainer }; diff --git a/src/modules/rewards/components/TwitterActivityButton.tsx b/src/modules/rewards/components/TwitterActivityButton.tsx new file mode 100644 index 0000000000..89bab18587 --- /dev/null +++ b/src/modules/rewards/components/TwitterActivityButton.tsx @@ -0,0 +1,140 @@ +// React and other libraries +import { FC, useEffect, useState } from 'react'; + +// Third-party libraries +import { initializeApp } from 'firebase/app'; +import { getAuth, signInWithPopup, TwitterAuthProvider, User } from 'firebase/auth'; +import { useSelector } from 'react-redux'; + +import { appConfig } from 'config'; + +//Components +import { generateVerificationProof } from '../utils/generateVerificationProof'; +import { ActivityStatusButton } from './ActivityStatusButton'; + +//Queries +import { useClaimRewardsActivity } from 'queries'; + +//types +import { UserStoreType } from 'types'; + +type TwitterActivityButtonProps = { + userId: string; + activityTypeId: string; + refetchActivity: () => void; + setErrorMessage: (errorMessage: string) => void; +}; + +const TwitterActivityButton: FC = ({ + userId, + activityTypeId, + refetchActivity, + setErrorMessage, +}) => { + const { userPushSDKInstance } = useSelector((state: UserStoreType) => state.user); + const [verifying, setVerifying] = useState(false); + + //For One case where the error is already present and user relogins the account + useEffect(() => { + setErrorMessage(''); + }, []); + + initializeApp(appConfig.firebaseConfig); + + const provider = new TwitterAuthProvider(); + const auth = getAuth(); + + const handleConnect = (): Promise => { + return signInWithPopup(auth, provider) + .then((result) => { + const credential = TwitterAuthProvider.credentialFromResult(result); + if (credential) { + const user = result.user; + return user; + } else { + return null; + } + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + const credential = TwitterAuthProvider.credentialFromError(error); + console.log('Error in connecting twitter >>>', errorCode, errorMessage, credential); + return null; + }); + }; + + const { mutate: claimRewardsActivity } = useClaimRewardsActivity({ + userId, + activityTypeId, + }); + + const handleVerification = async () => { + setErrorMessage(''); + const userDetails = await handleConnect(); + + if (userDetails) { + setVerifying(true); + + // @ts-expect-error + const twitterHandle = userDetails.reloadUserInfo.screenName; + + const verificationProof = await generateVerificationProof( + { + twitter: twitterHandle, + }, + userPushSDKInstance + ); + + if (verificationProof == null || verificationProof == undefined) { + if (userPushSDKInstance && userPushSDKInstance.readmode()) { + setVerifying(false); + setErrorMessage('Please Enable Push profile'); + } + return; + } + + claimRewardsActivity( + { + userId, + activityTypeId, + pgpPublicKey: userPushSDKInstance.pgpPublicKey as string, + data: { + twitter: twitterHandle, + }, + verificationProof: verificationProof as string, + }, + { + onSuccess: (response) => { + if (response.status === 'COMPLETED') { + refetchActivity(); + setVerifying(false); + } + if (response.status === 'PENDING') { + refetchActivity(); + setVerifying(false); + } + }, + onError: (error: any) => { + console.log('Error in creating activiy', error); + setVerifying(false); + if (error.name) { + setErrorMessage(error.response.data.error); + } + }, + } + ); + } + }; + + return ( + + ); +}; + +export { TwitterActivityButton }; diff --git a/src/modules/rewards/hooks/useDiscordSession.ts b/src/modules/rewards/hooks/useDiscordSession.ts new file mode 100644 index 0000000000..b5b1121cba --- /dev/null +++ b/src/modules/rewards/hooks/useDiscordSession.ts @@ -0,0 +1,28 @@ +import { useEffect } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +export const useDiscordSession = () => { + const location = useLocation(); + + const navigate = useNavigate(); + + useEffect(() => { + if (location.hash) { + const params = new URLSearchParams(location.hash.substring(1)); + const token = params.get('access_token'); + const expiresIn = params.get('expires_in'); + + if (token && expiresIn) { + sessionStorage.setItem('access_token', token); + sessionStorage.setItem('expires_in', expiresIn); + } + + // Splitting the url into baseURL+pathname and hashes of the url + const baseUrl = window.location.href.split('#')[0]; + // Generating a new URL + const url = new URL(baseUrl); + //navigating the user to the new URL without reloading the page + navigate(url, { replace: true }); + } + }, []); +}; diff --git a/src/modules/rewards/hooks/useGenerateUserId.tsx b/src/modules/rewards/hooks/useGenerateUserId.tsx new file mode 100644 index 0000000000..345ab950fb --- /dev/null +++ b/src/modules/rewards/hooks/useGenerateUserId.tsx @@ -0,0 +1,95 @@ +// React and other libraries +import { useEffect, useState } from 'react'; + +// third party libraries +import { useSelector } from 'react-redux'; +import { useSearchParams } from 'react-router-dom'; + +//Hooks +import { useGetUserRewardsDetails, useCreateRewardsUser } from 'queries'; + +//Types +import { UserStoreType } from 'types'; +import { AxiosError } from 'axios'; + +//helpers +import { generateVerificationProof } from '../utils/generateVerificationProof'; + +const useGenerateUserId = (caip10WalletAddress: string) => { + const [searchParams] = useSearchParams(); + + const ref = searchParams.get('ref'); + const [showConnectModal, setConnectModalVisibility] = useState(false); + + const { userPushSDKInstance } = useSelector((state: UserStoreType) => state.user); + + const { + data: userDetails, + status, + refetch, + error, + } = useGetUserRewardsDetails({ + caip10WalletAddress: caip10WalletAddress, + }); + + const { mutate: createUser } = useCreateRewardsUser(); + + const errorMessage = 'Failed to retrieve user'; + + useEffect(() => { + if (status === 'error' && error instanceof AxiosError && error?.response?.data?.error === errorMessage) { + // generate userId + generateUserId(ref); + } + // bad request error + }, [userDetails, status]); + + const generateUserId = async (ref: string | null) => { + // userPushSDKInstance null check + if (!userPushSDKInstance) return; + + // if ref is present, add it to the data needed to generate verification proof, if not - send only user wallet + const data = { + ...(ref && { refPrimary: ref }), + userWallet: caip10WalletAddress, + }; + + // generate verification proof + const verificationProof = await generateVerificationProof(data, userPushSDKInstance); + + //if verification proof is null, unlock push profile to get the decrypted pgp pvt key needed for the verification proof + if (verificationProof == null || verificationProof == undefined) { + if (userPushSDKInstance && userPushSDKInstance.readmode()) { + setConnectModalVisibility(true); + } + return; + } + + console.log(verificationProof, ref, errorMessage); + + // mutate action to make the request and on success - call the get user by wallet address fn with the id again + createUser( + { + pgpPublicKey: userPushSDKInstance?.pgpPublicKey, + userWallet: caip10WalletAddress, + verificationProof: verificationProof as string, + refPrimary: ref, + }, + { + onSuccess: () => { + refetch(); + }, + onError: (err) => { + console.error('Error', err); + }, + } + ); + }; + + return { + showConnectModal, + setConnectModalVisibility, + }; +}; + +export { useGenerateUserId }; diff --git a/src/modules/rewards/hooks/useRewardsTabs.ts b/src/modules/rewards/hooks/useRewardsTabs.ts new file mode 100644 index 0000000000..f61e188c4f --- /dev/null +++ b/src/modules/rewards/hooks/useRewardsTabs.ts @@ -0,0 +1,37 @@ +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +// Constants +import { rewardsTabsList } from '../Rewards.constants'; + +// Types +import { RewardsTabs } from '../Rewards.types'; + +const useRewardsTabs = () => { + const [activeTab, setActiveTab] = useState(rewardsTabsList[0].value); + const location = useLocation(); + const navigate = useNavigate(); + + useEffect(() => { + const locationArray = location.pathname.split('/'); + const currentTab = locationArray[2]; + if (currentTab && currentTab !== activeTab) { + setActiveTab(currentTab as RewardsTabs); + } else if (!currentTab && activeTab !== rewardsTabsList[0].value) { + setActiveTab(rewardsTabsList[0].value); + } + }, [location, activeTab]); + + const handleSetActiveTab = (tab: RewardsTabs) => { + if (tab !== activeTab) { + setActiveTab(tab); + if (tab == 'dashboard') { + navigate(`/points`); + } else { + navigate(`/points/${tab}`); + } + } + }; + return { activeTab, handleSetActiveTab }; +}; +export { useRewardsTabs }; diff --git a/src/modules/rewards/index.ts b/src/modules/rewards/index.ts new file mode 100644 index 0000000000..a1567a147b --- /dev/null +++ b/src/modules/rewards/index.ts @@ -0,0 +1 @@ +export { Rewards, type RewardsProps } from './Rewards'; diff --git a/src/modules/rewards/utils/generateVerificationProof.ts b/src/modules/rewards/utils/generateVerificationProof.ts new file mode 100644 index 0000000000..cd57181162 --- /dev/null +++ b/src/modules/rewards/utils/generateVerificationProof.ts @@ -0,0 +1,22 @@ +import { PushAPI } from '@pushprotocol/restapi'; +import * as openpgp from 'openpgp'; + +export const generateVerificationProof = async (data?: any, userPushSDKInstance?: PushAPI) => { + if (userPushSDKInstance && !userPushSDKInstance?.readmode()) { + const sigingingMessage = JSON.stringify(data); + + const messageObject: openpgp.Message = await openpgp.createMessage({ + text: sigingingMessage + }); + const privateKey: openpgp.PrivateKey = await openpgp.readPrivateKey({ + armoredKey: userPushSDKInstance.decryptedPgpPvtKey as string + }); + const verificationProof = await openpgp.sign({ + message: messageObject, + signingKeys: privateKey, + detached: true + }); + + return verificationProof; + } +}; diff --git a/src/modules/rewards/utils/getRewardsActivityTitle.ts b/src/modules/rewards/utils/getRewardsActivityTitle.ts new file mode 100644 index 0000000000..46a6ac6d9b --- /dev/null +++ b/src/modules/rewards/utils/getRewardsActivityTitle.ts @@ -0,0 +1,21 @@ +export const getRewardsActivityTitle = (activityTitle: string) => { + const regex = /\[([^\]]+)\]\(([^)]+)\)/; + const match = activityTitle?.match(regex); + if (match) { + const preText = activityTitle.substring(0, match.index); + const linkedText = match[1]; + const url = match[2]; + let postText = ''; + if (match.index) { + postText = activityTitle.substring(match.index + match[0].length); + } + return { + preText, + linkedText, + url, + postText + }; + } else { + return null; + } +}; diff --git a/src/pages/PointsVaultPage.tsx b/src/pages/PointsVaultPage.tsx new file mode 100644 index 0000000000..4fbae26f13 --- /dev/null +++ b/src/pages/PointsVaultPage.tsx @@ -0,0 +1,12 @@ +//components +import { PointsVault } from 'modules/pointsVault'; +import { ContentLayout } from 'common'; + +const PointsVaultPage = () => { + return ( + + + + ); +}; +export default PointsVaultPage; diff --git a/src/pages/RewardPointsPage.tsx b/src/pages/RewardPointsPage.tsx new file mode 100644 index 0000000000..13d76b37bd --- /dev/null +++ b/src/pages/RewardPointsPage.tsx @@ -0,0 +1,12 @@ +//components +import { Rewards } from 'modules/rewards'; +import { ContentLayout } from 'common'; + +const RewardsPointsPage = () => { + return ( + + + + ); +}; +export default RewardsPointsPage; diff --git a/src/pages/WelcomeDashboardPage.tsx b/src/pages/WelcomeDashboardPage.tsx new file mode 100644 index 0000000000..2d692efa36 --- /dev/null +++ b/src/pages/WelcomeDashboardPage.tsx @@ -0,0 +1,12 @@ +//components +import { Dashboard } from 'modules/dashboard'; +import { ContentLayout } from 'common'; + +const WelcomeDashboardPage = () => { + return ( + + + + ); +}; +export default WelcomeDashboardPage; diff --git a/src/primaries/Profile.tsx b/src/primaries/Profile.tsx index 1e3a4039a5..4f2224c523 100644 --- a/src/primaries/Profile.tsx +++ b/src/primaries/Profile.tsx @@ -25,7 +25,7 @@ import { getPublicAssetPath } from 'helpers/RoutesHelper.js'; // Create Header const Profile = ({ isDarkMode }: { isDarkMode: boolean }) => { - const { web3NameList, removePGPKeyForUser }: AppContextType = useContext(AppContext); + const { web3NameList, removePGPKeyForUser, initializePushSdkReadMode }: AppContextType = useContext(AppContext); const { setReadOnlyWallet, setMode }: GlobalContextType = useContext(GlobalContext); const { authError } = useContext(ErrorContext); const toggleArrowRef = useRef(null); @@ -51,21 +51,21 @@ const Profile = ({ isDarkMode }: { isDarkMode: boolean }) => { id: 'walletAddress', value: account, title: account, - function: () => {}, + function: () => { }, invertedIcon: getPublicAssetPath('copy.svg'), }, { id: 'userSettings', value: '', title: 'Settings', - function: () => {}, + function: () => { }, to: APP_PATHS.UserSettings, invertedIcon: getPublicAssetPath('svg/setting.svg'), }, { id: 'prodDapp', value: '', - function: () => {}, + function: () => { }, link: `https://${envUtil.prod}`, title: 'Production dapp', invertedIcon: getPublicAssetPath('prod.svg'), @@ -79,6 +79,7 @@ const Profile = ({ isDarkMode }: { isDarkMode: boolean }) => { setMode(ReadOnlyWalletMode.GUEST_MODE); setReadOnlyWallet('0x0000000000000000000000000000000000000001'); setShowDropdown(false); + await initializePushSdkReadMode(); }, title: 'Logout', invertedIcon: getPublicAssetPath('logout.svg'), diff --git a/src/queries/baseURL.ts b/src/queries/baseURL.ts new file mode 100644 index 0000000000..ac123821dc --- /dev/null +++ b/src/queries/baseURL.ts @@ -0,0 +1,21 @@ +import { appConfig } from 'config'; + +/** + * Rewards system is a separate micro-service backend so we have separate baseURLs for it. + * + * - prod, staging and dev env will only allow request from app.push.org, dev.push.org and staging.push.org + * - w2w and alpha will make request to the dev baseURL. + * - preview will also make request to staging.push.org + */ +export const getRewardsBaseURL = () => { + switch (appConfig.appEnv) { + case 'prod': + return `https://us-east1-push-stage-apps.cloudfunctions.net/pushpointsrewardsystem`; + case 'staging': + return `https://us-east1-push-dev-apps.cloudfunctions.net/pushpointsrewardsystem`; + case 'dev': + return `https://us-east1-push-dev-apps.cloudfunctions.net/helloWorld`; + default: + return `https://us-east1-push-dev-apps.cloudfunctions.net/helloWorld`; + } +}; diff --git a/src/queries/hooks/index.ts b/src/queries/hooks/index.ts index 7732dd494c..fdcb1f07a9 100644 --- a/src/queries/hooks/index.ts +++ b/src/queries/hooks/index.ts @@ -1,2 +1,4 @@ export * from './channels'; export * from './user'; +export * from './rewards'; +export * from './pointsVault'; diff --git a/src/queries/hooks/pointsVault/index.ts b/src/queries/hooks/pointsVault/index.ts new file mode 100644 index 0000000000..3e18ec3099 --- /dev/null +++ b/src/queries/hooks/pointsVault/index.ts @@ -0,0 +1,9 @@ +export * from './usePointsVaultUserLogin'; +export * from './useApprovePointsVaultUser'; +export * from './useRejectPointsVaultUser'; +export * from './useGetUserTwitterDetails'; +export * from './useGetUserTwitterDetails'; +export * from './usePointsVaultToken'; +export * from './useGetPointsVaultApprovedUsers'; +export * from './useGetPointsVaultPendingUsers'; +export * from './useGetPointsVaultRejectedUsers'; diff --git a/src/queries/hooks/pointsVault/useApprovePointsVaultUser.ts b/src/queries/hooks/pointsVault/useApprovePointsVaultUser.ts new file mode 100644 index 0000000000..c9edf6bcfb --- /dev/null +++ b/src/queries/hooks/pointsVault/useApprovePointsVaultUser.ts @@ -0,0 +1,10 @@ +import { useMutation } from '@tanstack/react-query'; +import { approveVaultUser } from 'queries/queryKeys'; +import { approvePointsVaultUser } from 'queries/services'; + +export const useApprovePointsVaultUser = () => { + return useMutation({ + mutationFn: approvePointsVaultUser, + mutationKey: [approveVaultUser], + }); +}; diff --git a/src/queries/hooks/pointsVault/useGetPointsVaultApprovedUsers.ts b/src/queries/hooks/pointsVault/useGetPointsVaultApprovedUsers.ts new file mode 100644 index 0000000000..e5d5062f97 --- /dev/null +++ b/src/queries/hooks/pointsVault/useGetPointsVaultApprovedUsers.ts @@ -0,0 +1,32 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; +import { pointsVaultApprovedUsers } from '../../queryKeys'; +import { PointsVaultGetUsersPayload } from 'queries/types'; +import { getPointsVaultUsers } from 'queries/services'; + +export const useGetPointsVaultApprovedUsers = ({ + status, + pageSize = 20, + token, + twitter, + wallet, +}: PointsVaultGetUsersPayload) => { + return useInfiniteQuery({ + queryKey: [pointsVaultApprovedUsers, `${twitter}-${wallet}`], + initialPageParam: 1, + queryFn: ({ pageParam }) => + getPointsVaultUsers({ status, page: pageParam as number, pageSize, token, twitter, wallet }), + getNextPageParam: ({ page, total }) => { + if (page >= total) { + return null; + } + return page + 1; + }, + enabled: !!token, + refetchInterval: false, + refetchIntervalInBackground: false, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryOnMount: false, + }); +}; diff --git a/src/queries/hooks/pointsVault/useGetPointsVaultPendingUsers.ts b/src/queries/hooks/pointsVault/useGetPointsVaultPendingUsers.ts new file mode 100644 index 0000000000..34a5c74b19 --- /dev/null +++ b/src/queries/hooks/pointsVault/useGetPointsVaultPendingUsers.ts @@ -0,0 +1,32 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; +import { pointsVaultPendingUsers } from '../../queryKeys'; +import { PointsVaultGetUsersPayload } from 'queries/types'; +import { getPointsVaultUsers } from 'queries/services'; + +export const useGetPointsVaultPendingUsers = ({ + status, + pageSize = 20, + token, + twitter, + wallet, +}: PointsVaultGetUsersPayload) => { + return useInfiniteQuery({ + queryKey: [pointsVaultPendingUsers, `${twitter}-${wallet}`], + initialPageParam: 1, + queryFn: ({ pageParam }) => + getPointsVaultUsers({ status, page: pageParam as number, pageSize, token, twitter, wallet }), + getNextPageParam: ({ page, total }) => { + if (page >= total) { + return null; + } + return page + 1; + }, + enabled: !!token, + refetchInterval: false, + refetchIntervalInBackground: false, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryOnMount: false, + }); +}; diff --git a/src/queries/hooks/pointsVault/useGetPointsVaultRejectedUsers.ts b/src/queries/hooks/pointsVault/useGetPointsVaultRejectedUsers.ts new file mode 100644 index 0000000000..e66f15f421 --- /dev/null +++ b/src/queries/hooks/pointsVault/useGetPointsVaultRejectedUsers.ts @@ -0,0 +1,32 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; +import { pointsVaultRejectedUsers } from '../../queryKeys'; +import { PointsVaultGetUsersPayload } from 'queries/types'; +import { getPointsVaultUsers } from 'queries/services'; + +export const useGetPointsVaultRejectedUsers = ({ + status, + pageSize = 20, + token, + twitter, + wallet, +}: PointsVaultGetUsersPayload) => { + return useInfiniteQuery({ + queryKey: [pointsVaultRejectedUsers, `${twitter}-${wallet}`], + initialPageParam: 1, + queryFn: ({ pageParam }) => + getPointsVaultUsers({ status, page: pageParam as number, pageSize, token, twitter, wallet }), + getNextPageParam: ({ page, total }) => { + if (page >= total) { + return null; + } + return page + 1; + }, + enabled: !!token, + refetchInterval: false, + refetchIntervalInBackground: false, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryOnMount: false, + }); +}; diff --git a/src/queries/hooks/pointsVault/useGetUserTwitterDetails.ts b/src/queries/hooks/pointsVault/useGetUserTwitterDetails.ts new file mode 100644 index 0000000000..cf80568b4b --- /dev/null +++ b/src/queries/hooks/pointsVault/useGetUserTwitterDetails.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; +import { userTwitterDetails } from 'queries/queryKeys'; +import { getUserTwitterDetails } from 'queries/services'; + +export const useGetUserTwitterDetails = (twitterHandle: string, token: string) => { + return useQuery({ + queryKey: [userTwitterDetails, twitterHandle], + queryFn: () => getUserTwitterDetails({ twitterHandle, token }), + staleTime: Infinity, + retry: false, + refetchInterval: false, + refetchIntervalInBackground: false, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + retryOnMount: false, + enabled: !!token && !!twitterHandle, + }); +}; diff --git a/src/queries/hooks/pointsVault/usePointsVaultToken.ts b/src/queries/hooks/pointsVault/usePointsVaultToken.ts new file mode 100644 index 0000000000..442cce16c8 --- /dev/null +++ b/src/queries/hooks/pointsVault/usePointsVaultToken.ts @@ -0,0 +1,11 @@ +import { useMutationState } from '@tanstack/react-query'; +import { pointsVaultUserLoginKey } from 'queries/queryKeys'; + +export const usePointsVaultToken = () => { + const data: any = useMutationState({ + filters: { mutationKey: [pointsVaultUserLoginKey] }, + select: (mutation) => mutation.state.data, + }); + + return data?.[0]?.token as string; +}; diff --git a/src/queries/hooks/pointsVault/usePointsVaultUserLogin.ts b/src/queries/hooks/pointsVault/usePointsVaultUserLogin.ts new file mode 100644 index 0000000000..42d56adaac --- /dev/null +++ b/src/queries/hooks/pointsVault/usePointsVaultUserLogin.ts @@ -0,0 +1,11 @@ +import { useMutation } from '@tanstack/react-query'; + +import { pointsVaultUserLoginKey } from '../../queryKeys'; + +import { pointsVaultUserLogin as pointsVaultUserLoginfunction } from '../../services'; + +export const usePointsVaultUserLogin = () => + useMutation({ + mutationKey: [pointsVaultUserLoginKey], + mutationFn: pointsVaultUserLoginfunction, + }); diff --git a/src/queries/hooks/pointsVault/useRejectPointsVaultUser.ts b/src/queries/hooks/pointsVault/useRejectPointsVaultUser.ts new file mode 100644 index 0000000000..0aaa238cb1 --- /dev/null +++ b/src/queries/hooks/pointsVault/useRejectPointsVaultUser.ts @@ -0,0 +1,10 @@ +import { useMutation } from '@tanstack/react-query'; +import { rejectVaultUser } from 'queries/queryKeys'; +import { rejectPointsVaultUser } from 'queries/services'; + +export const useRejectPointsVaultUser = () => { + return useMutation({ + mutationFn: rejectPointsVaultUser, + mutationKey: [rejectVaultUser], + }); +}; diff --git a/src/queries/hooks/rewards/claimRewardsActivity.ts b/src/queries/hooks/rewards/claimRewardsActivity.ts new file mode 100644 index 0000000000..65103f6744 --- /dev/null +++ b/src/queries/hooks/rewards/claimRewardsActivity.ts @@ -0,0 +1,9 @@ +import { useMutation } from '@tanstack/react-query'; +import { claimRewards } from 'queries/queryKeys'; +import { claimRewardsActivity } from 'queries/services'; + +export const useClaimRewardsActivity = (payload: { userId: string; activityTypeId: string }) => + useMutation({ + mutationKey: [claimRewards, payload.userId, payload.activityTypeId], + mutationFn: claimRewardsActivity + }); diff --git a/src/queries/hooks/rewards/index.ts b/src/queries/hooks/rewards/index.ts new file mode 100644 index 0000000000..85bd786ba1 --- /dev/null +++ b/src/queries/hooks/rewards/index.ts @@ -0,0 +1,7 @@ +export * from './useGetRewardsActivities'; +export * from './useGetRewardsActivity'; +export * from './useGetUserDiscordDetails'; +export * from './claimRewardsActivity'; +export * from './useGetUserRewardsDetails'; +export * from './useCreateRewardsUser'; +export * from './useGetRewardsLedearboard'; diff --git a/src/queries/hooks/rewards/useCreateRewardsUser.ts b/src/queries/hooks/rewards/useCreateRewardsUser.ts new file mode 100644 index 0000000000..b520f15dcf --- /dev/null +++ b/src/queries/hooks/rewards/useCreateRewardsUser.ts @@ -0,0 +1,9 @@ +import { useMutation } from '@tanstack/react-query'; +import { createUserRewardsDetails } from '../../queryKeys'; +import { createUserRewardsDetail } from '../../services'; + +export const useCreateRewardsUser = () => + useMutation({ + mutationKey: [createUserRewardsDetails], + mutationFn: createUserRewardsDetail, + }); diff --git a/src/queries/hooks/rewards/useGetRewardsActivities.ts b/src/queries/hooks/rewards/useGetRewardsActivities.ts new file mode 100644 index 0000000000..8186e383b3 --- /dev/null +++ b/src/queries/hooks/rewards/useGetRewardsActivities.ts @@ -0,0 +1,21 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; +import { allActivities } from 'queries/queryKeys'; +import { getRewardsActivities } from 'queries/services/rewards'; +import { ActivitiesParams } from 'queries/types'; + +export const useGetRewardsActivities = ({ pageSize }: ActivitiesParams) => + useInfiniteQuery({ + queryKey: [allActivities], + initialPageParam: 1, + queryFn: ({ pageParam }) => + getRewardsActivities({ + pageSize, + pageNumber: pageParam as number + }), + getNextPageParam: ({ page, total, size }) => { + if (size * page >= total) { + return null; + } + return page + 1; + } + }); diff --git a/src/queries/hooks/rewards/useGetRewardsActivity.ts b/src/queries/hooks/rewards/useGetRewardsActivity.ts new file mode 100644 index 0000000000..29d2800a0c --- /dev/null +++ b/src/queries/hooks/rewards/useGetRewardsActivity.ts @@ -0,0 +1,14 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; +import { rewardsActivity } from '../../queryKeys'; +import { getRewardsActivity } from '../../services/rewards'; +import { UsersActivity } from '../../types'; + +export const useGetRewardsActivity = ( + { userId, activityId }: { userId: string; activityId: string }, + config?: Partial> +) => + useQuery({ + queryKey: [rewardsActivity, userId, activityId], + queryFn: () => getRewardsActivity(userId, activityId), + ...config + }); diff --git a/src/queries/hooks/rewards/useGetRewardsLedearboard.ts b/src/queries/hooks/rewards/useGetRewardsLedearboard.ts new file mode 100644 index 0000000000..6d33d3ed2e --- /dev/null +++ b/src/queries/hooks/rewards/useGetRewardsLedearboard.ts @@ -0,0 +1,23 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +import { rewardsLeaderboard } from '../../queryKeys'; +import { getRewardsLeaderboard } from '../../services'; +import { LeaderboardModelledResponse, LeaderboardParams } from '../../types'; + +export const useGetRewardsLeaderboard = ({ order, pageSize }: LeaderboardParams) => + useInfiniteQuery({ + queryKey: [rewardsLeaderboard], + initialPageParam: 1, + queryFn: ({ pageParam }) => + getRewardsLeaderboard({ + order, + pageSize, + pageNumber: pageParam as number, + }), + getNextPageParam: ({ page, total, pageSize }) => { + if (pageSize * page >= total) { + return null; + } + return page + 1; + }, + }); diff --git a/src/queries/hooks/rewards/useGetUserDiscordDetails.ts b/src/queries/hooks/rewards/useGetUserDiscordDetails.ts new file mode 100644 index 0000000000..e058326cc3 --- /dev/null +++ b/src/queries/hooks/rewards/useGetUserDiscordDetails.ts @@ -0,0 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; +import { discordDetails } from 'queries/queryKeys'; +import { getUserDiscordDetails } from 'queries/services/rewards'; + +export const useGetUserDiscordDetails = (token: string | undefined) => { + const query = useQuery({ + queryKey: [discordDetails, token], + queryFn: () => getUserDiscordDetails(token!), + enabled: !!token, + }); + + return query; +}; diff --git a/src/queries/hooks/rewards/useGetUserRewardsDetails.ts b/src/queries/hooks/rewards/useGetUserRewardsDetails.ts new file mode 100644 index 0000000000..af6827df7c --- /dev/null +++ b/src/queries/hooks/rewards/useGetUserRewardsDetails.ts @@ -0,0 +1,12 @@ +import { useQuery } from '@tanstack/react-query'; + +import { UserRewardsDetailParams } from '../../types'; +import { userRewardsDetails } from '../../queryKeys'; +import { getUserRewardsDetail } from '../../services'; + +export const useGetUserRewardsDetails = (options: UserRewardsDetailParams) => + useQuery({ + queryKey: [userRewardsDetails, options.caip10WalletAddress], + queryFn: () => getUserRewardsDetail(options), + enabled: options.enabled, + }); diff --git a/src/queries/index.ts b/src/queries/index.ts index 81fadee214..4ea0198f07 100644 --- a/src/queries/index.ts +++ b/src/queries/index.ts @@ -2,3 +2,4 @@ export * from './hooks'; export * from './models'; export * from './services'; export * from './types'; +export * from './queryKeys'; diff --git a/src/queries/models/index.ts b/src/queries/models/index.ts index 7732dd494c..fdcb1f07a9 100644 --- a/src/queries/models/index.ts +++ b/src/queries/models/index.ts @@ -1,2 +1,4 @@ export * from './channels'; export * from './user'; +export * from './rewards'; +export * from './pointsVault'; diff --git a/src/queries/models/pointsVault/getPVUsersModelCreator.ts b/src/queries/models/pointsVault/getPVUsersModelCreator.ts new file mode 100644 index 0000000000..ff924a6a5a --- /dev/null +++ b/src/queries/models/pointsVault/getPVUsersModelCreator.ts @@ -0,0 +1,4 @@ +import { PointsVaultActivitiesResponse } from 'queries/types'; + +export const getPVUsersModelCreator = (response: PointsVaultActivitiesResponse): PointsVaultActivitiesResponse => + response; diff --git a/src/queries/models/pointsVault/getUserTwitterDetailsModelCreator.ts b/src/queries/models/pointsVault/getUserTwitterDetailsModelCreator.ts new file mode 100644 index 0000000000..5504c86216 --- /dev/null +++ b/src/queries/models/pointsVault/getUserTwitterDetailsModelCreator.ts @@ -0,0 +1,16 @@ +import { UserTwitterDetailsModelledResponse, UserTwitterDetailsResponse } from 'queries/types'; + +export const getUserTwitterDetailsModelCreator = ( + response: UserTwitterDetailsResponse +): UserTwitterDetailsModelledResponse => { + return { + id: response.data.id, + followersCount: response.data.public_metrics.followers_count, + followingCount: response.data.public_metrics.following_count, + likeCount: response.data.public_metrics.like_count, + listedCount: response.data.public_metrics.listed_count, + name: response.data.name, + tweetCount: response.data.public_metrics.tweet_count, + username: response.data.username, + }; +}; diff --git a/src/queries/models/pointsVault/index.ts b/src/queries/models/pointsVault/index.ts new file mode 100644 index 0000000000..a780b0ba85 --- /dev/null +++ b/src/queries/models/pointsVault/index.ts @@ -0,0 +1,3 @@ +export * from './pointsVaultUserLoginModelCreator'; +export * from './getPVUsersModelCreator'; +export * from './getUserTwitterDetailsModelCreator'; diff --git a/src/queries/models/pointsVault/pointsVaultUserLoginModelCreator.ts b/src/queries/models/pointsVault/pointsVaultUserLoginModelCreator.ts new file mode 100644 index 0000000000..7b5695f3be --- /dev/null +++ b/src/queries/models/pointsVault/pointsVaultUserLoginModelCreator.ts @@ -0,0 +1,4 @@ +import { PointsVaultUserLoginPayload } from 'queries/types'; + +export const pointsVaultUserLoginModelCreator = (response: PointsVaultUserLoginPayload): PointsVaultUserLoginPayload => + response; diff --git a/src/queries/models/rewards/claimRewardsActivityModelCreator.ts b/src/queries/models/rewards/claimRewardsActivityModelCreator.ts new file mode 100644 index 0000000000..3c9f232bd0 --- /dev/null +++ b/src/queries/models/rewards/claimRewardsActivityModelCreator.ts @@ -0,0 +1,3 @@ +import { ClaimActivitesResponse } from 'queries/types'; + +export const claimRewardsActivityModelCreator = (response: ClaimActivitesResponse): ClaimActivitesResponse => response; diff --git a/src/queries/models/rewards/createUserRewardsDetailsModel.ts b/src/queries/models/rewards/createUserRewardsDetailsModel.ts new file mode 100644 index 0000000000..ce12240975 --- /dev/null +++ b/src/queries/models/rewards/createUserRewardsDetailsModel.ts @@ -0,0 +1,4 @@ +import { createUserRewardsDetailsResponse } from '../../types/rewards'; + +export const createUserRewardsDetailModel = (response: UserRewardsDetailResponse): UserRewardsDetailResponse => + response; diff --git a/src/queries/models/rewards/getRewardsActivitiesModelCreator.ts b/src/queries/models/rewards/getRewardsActivitiesModelCreator.ts new file mode 100644 index 0000000000..75abdfaea9 --- /dev/null +++ b/src/queries/models/rewards/getRewardsActivitiesModelCreator.ts @@ -0,0 +1,10 @@ +import { RewardsAcitivitesResponse } from 'queries/types'; + +export const getRewardsActivitiesModelCreator = (response: RewardsAcitivitesResponse): RewardsAcitivitesResponse => { + return { + activities: response.activities, + page: response.page, + size: response.size, + total: response.total + }; +}; diff --git a/src/queries/models/rewards/getRewardsActivityModelCreator.ts b/src/queries/models/rewards/getRewardsActivityModelCreator.ts new file mode 100644 index 0000000000..70b7483a6e --- /dev/null +++ b/src/queries/models/rewards/getRewardsActivityModelCreator.ts @@ -0,0 +1,3 @@ +import { UsersActivity } from 'queries/types'; + +export const getRewardsActivityModelCreator = (response: UsersActivity): UsersActivity => response; diff --git a/src/queries/models/rewards/getRewardsLeaderboardModalCreator.ts b/src/queries/models/rewards/getRewardsLeaderboardModalCreator.ts new file mode 100644 index 0000000000..88b56953f3 --- /dev/null +++ b/src/queries/models/rewards/getRewardsLeaderboardModalCreator.ts @@ -0,0 +1,15 @@ +import { LeaderboardModelledResponse, LeaderboardResponse } from '../../types'; + +export const getRewardsLeaderboardModalCreator = (response: LeaderboardResponse): LeaderboardModelledResponse => { + return { + users: response.users.map((user) => ({ + userId: user.userId, + userWallet: user.userWallet, + totalPoints: user.totalPoints, + rank: user.rank, + })), + page: response.page, + pageSize: response.pageSize, + total: response.total, + }; +}; diff --git a/src/queries/models/rewards/getUserDiscordDetailsModelCreator.ts b/src/queries/models/rewards/getUserDiscordDetailsModelCreator.ts new file mode 100644 index 0000000000..5390203e46 --- /dev/null +++ b/src/queries/models/rewards/getUserDiscordDetailsModelCreator.ts @@ -0,0 +1,3 @@ +import { DiscordDetails } from 'queries/types'; + +export const getUserDiscordDetailsModelCreator = (response: DiscordDetails): DiscordDetails => response; diff --git a/src/queries/models/rewards/getUserRewardDetailsModel.ts b/src/queries/models/rewards/getUserRewardDetailsModel.ts new file mode 100644 index 0000000000..1fcb97afb3 --- /dev/null +++ b/src/queries/models/rewards/getUserRewardDetailsModel.ts @@ -0,0 +1,3 @@ +import { UserRewardsDetailResponse } from '../../types/rewards'; + +export const getUserRewardsDetailModel = (response: UserRewardsDetailResponse): UserRewardsDetailResponse => response; diff --git a/src/queries/models/rewards/index.ts b/src/queries/models/rewards/index.ts new file mode 100644 index 0000000000..c7e2db9da7 --- /dev/null +++ b/src/queries/models/rewards/index.ts @@ -0,0 +1,7 @@ +export * from './getRewardsActivitiesModelCreator'; +export * from './getRewardsActivityModelCreator'; +export * from './getUserDiscordDetailsModelCreator'; +export * from './claimRewardsActivityModelCreator'; +export * from './getUserRewardDetailsModel'; +export * from './createUserRewardsDetailsModel'; +export * from './getRewardsLeaderboardModalCreator'; diff --git a/src/queries/queryKeys.ts b/src/queries/queryKeys.ts index bd671d9b6e..2bcd343398 100644 --- a/src/queries/queryKeys.ts +++ b/src/queries/queryKeys.ts @@ -2,3 +2,20 @@ export const trendingChannels = 'trendingChannels'; export const allUserSubscriptions = 'allUserSubscriptions'; export const userSubscription = 'userSubscription'; export const channelDetails = 'channelDetails'; +export const allActivities = 'allActivities'; +export const rewardsActivity = 'rewardsActivity'; +export const discordDetails = 'discordDetails'; +export const claimRewards = 'claimRewards'; +export const generateUserIdByWallet = 'generateUserIdByWallet'; +export const UserRewardsDetails = 'userRewardsDetails'; +export const userRewardsDetails = 'userRewardsDetails'; +export const createUserRewardsDetails = 'createUserRewardsDetails'; +export const rewardsLeaderboard = 'rewardsLeaderboard'; +export const pointsVaultUserLoginKey = 'pointsVaultUserLogin'; +export const pointsVaultPendingUsers = 'pointsVaultPendingUsers'; +export const pointsVaultApprovedUsers = 'pointsVaultApprovedUsers'; +export const pointsVaultRejectedUsers = 'pointsVaultRejectedUsers'; +export const userTwitterDetails = 'userTwitterDetails'; +export const pointsVaultSearch = 'pointsVaultSearch'; +export const approveVaultUser = 'approveVaultUser'; +export const rejectVaultUser = 'rejectVaultUser'; diff --git a/src/queries/services/index.ts b/src/queries/services/index.ts index 7732dd494c..fdcb1f07a9 100644 --- a/src/queries/services/index.ts +++ b/src/queries/services/index.ts @@ -1,2 +1,4 @@ export * from './channels'; export * from './user'; +export * from './rewards'; +export * from './pointsVault'; diff --git a/src/queries/services/pointsVault/approvePointsVaultUser.ts b/src/queries/services/pointsVault/approvePointsVaultUser.ts new file mode 100644 index 0000000000..41c96195df --- /dev/null +++ b/src/queries/services/pointsVault/approvePointsVaultUser.ts @@ -0,0 +1,16 @@ +import axios from 'axios'; +import { getRewardsBaseURL } from 'queries/baseURL'; +import { ApprovePointsVaultUserPayload } from 'queries/types'; + +export const approvePointsVaultUser = ({ activityId, token }: ApprovePointsVaultUserPayload) => + axios({ + method: 'POST', + url: `${getRewardsBaseURL()}/activities/approved`, + data: { + activities: [{ activityId }], + }, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + }).then((response) => response.data); diff --git a/src/queries/services/pointsVault/getPointsVaultUsers.ts b/src/queries/services/pointsVault/getPointsVaultUsers.ts new file mode 100644 index 0000000000..5c3d599dec --- /dev/null +++ b/src/queries/services/pointsVault/getPointsVaultUsers.ts @@ -0,0 +1,21 @@ +import axios from 'axios'; +import { getRewardsBaseURL } from 'queries/baseURL'; +import { getPVUsersModelCreator } from 'queries/models'; +import { PointsVaultGetUsersPayload } from 'queries/types'; + +export const getPointsVaultUsers = ({ page, status, pageSize, token, wallet, twitter }: PointsVaultGetUsersPayload) => + axios({ + method: 'GET', + url: `${getRewardsBaseURL()}/activities/list`, + params: { + status, + page, + pageSize, + wallet, + twitter, + }, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + }).then((response) => getPVUsersModelCreator(response.data)); diff --git a/src/queries/services/pointsVault/getUserTwitterDetails.ts b/src/queries/services/pointsVault/getUserTwitterDetails.ts new file mode 100644 index 0000000000..1768695c93 --- /dev/null +++ b/src/queries/services/pointsVault/getUserTwitterDetails.ts @@ -0,0 +1,14 @@ +import axios from 'axios'; +import { getRewardsBaseURL } from 'queries/baseURL'; +import { getUserTwitterDetailsModelCreator } from 'queries/models'; +import { UserTwitterDetailsPayload } from 'queries/types'; + +export const getUserTwitterDetails = ({ twitterHandle, token }: UserTwitterDetailsPayload) => + axios({ + method: 'GET', + url: `${getRewardsBaseURL()}/twitter/user/${twitterHandle}`, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + }).then((response) => getUserTwitterDetailsModelCreator(response.data)); diff --git a/src/queries/services/pointsVault/index.ts b/src/queries/services/pointsVault/index.ts new file mode 100644 index 0000000000..2d0084f25a --- /dev/null +++ b/src/queries/services/pointsVault/index.ts @@ -0,0 +1,5 @@ +export * from './pointsVaultUserLogin'; +export * from './approvePointsVaultUser'; +export * from './getPointsVaultUsers'; +export * from './getUserTwitterDetails'; +export * from './rejectPointsVaultUser'; diff --git a/src/queries/services/pointsVault/pointsVaultUserLogin.ts b/src/queries/services/pointsVault/pointsVaultUserLogin.ts new file mode 100644 index 0000000000..fef40c3663 --- /dev/null +++ b/src/queries/services/pointsVault/pointsVaultUserLogin.ts @@ -0,0 +1,17 @@ +import axios from 'axios'; +import { getRewardsBaseURL } from '../../baseURL'; +import { PointsVaultUserLoginProps } from '../../types'; +import { pointsVaultUserLoginModelCreator } from '../../models'; + +export const pointsVaultUserLogin = (payload: PointsVaultUserLoginProps) => + axios({ + method: 'POST', + url: `${getRewardsBaseURL()}/auth/login`, + data: { + username: payload.username, + password: payload.password, + }, + headers: { + 'Content-Type': 'application/json', + }, + }).then((response) => pointsVaultUserLoginModelCreator(response.data)); diff --git a/src/queries/services/pointsVault/rejectPointsVaultUser.ts b/src/queries/services/pointsVault/rejectPointsVaultUser.ts new file mode 100644 index 0000000000..0729e1810e --- /dev/null +++ b/src/queries/services/pointsVault/rejectPointsVaultUser.ts @@ -0,0 +1,14 @@ +import axios from 'axios'; +import { getRewardsBaseURL } from 'queries/baseURL'; +import { RejectPointsVaultUserPayload } from 'queries/types'; + +export const rejectPointsVaultUser = ({ activityId, token }: RejectPointsVaultUserPayload) => + axios({ + method: 'POST', + url: `${getRewardsBaseURL()}/activities/rejected`, + data: { activities: [{ activityId }] }, + headers: { + 'Content-Type': 'application/json', + Authorization: token, + }, + }).then((response) => response.data); diff --git a/src/queries/services/rewards/claimRewardsActivity.ts b/src/queries/services/rewards/claimRewardsActivity.ts new file mode 100644 index 0000000000..020fe8c63a --- /dev/null +++ b/src/queries/services/rewards/claimRewardsActivity.ts @@ -0,0 +1,17 @@ +import axios from 'axios'; +import { getRewardsBaseURL } from 'queries/baseURL'; +import { claimRewardsActivityModelCreator } from 'queries/models/rewards'; +import { ClaimRewardsActivityProps } from 'queries/types'; + +export const claimRewardsActivity = (payload: ClaimRewardsActivityProps) => + axios({ + method: 'POST', + url: `${getRewardsBaseURL()}/users/${payload.userId}/activity/${payload.activityTypeId}`, + data: { + data: payload.data, + verificationProof: payload.verificationProof, + }, + headers: { + 'Content-Type': 'application/json', + }, + }).then((response) => claimRewardsActivityModelCreator(response.data)); diff --git a/src/queries/services/rewards/createUserRewardsDetail.ts b/src/queries/services/rewards/createUserRewardsDetail.ts new file mode 100644 index 0000000000..e6377abaae --- /dev/null +++ b/src/queries/services/rewards/createUserRewardsDetail.ts @@ -0,0 +1,19 @@ +import axios from 'axios'; + +import { createUserRewardsDetailModel } from '../../models'; +import { getRewardsBaseURL } from 'queries/baseURL'; + +export const createUserRewardsDetail = (payload: any) => + axios({ + method: 'POST', + url: `${getRewardsBaseURL()}/users/`, + data: { + userWallet: payload.userWallet, + pgpPublicKey: payload.pgpPublicKey, + verificationProof: payload.verificationProof, + refPrimary: payload.refPrimary, + }, + headers: { + 'Content-Type': 'application/json', + }, + }).then((response) => createUserRewardsDetailModel(response.data)); diff --git a/src/queries/services/rewards/getRewardsActivities.ts b/src/queries/services/rewards/getRewardsActivities.ts new file mode 100644 index 0000000000..56be7b4a88 --- /dev/null +++ b/src/queries/services/rewards/getRewardsActivities.ts @@ -0,0 +1,14 @@ +import axios from 'axios'; +import { getRewardsActivitiesModelCreator } from '../../models/rewards'; +import { getRewardsBaseURL } from '../../baseURL'; +import { ActivitiesParams } from 'queries/types'; + +export const getRewardsActivities = ({ pageSize, pageNumber }: ActivitiesParams) => + axios({ + method: 'GET', + url: `${`${getRewardsBaseURL()}`}/activities/all`, + params: { + pageSize: pageSize || 1, + pageNumber: pageNumber || 1 + } + }).then((response) => getRewardsActivitiesModelCreator(response.data)); diff --git a/src/queries/services/rewards/getRewardsActivity.ts b/src/queries/services/rewards/getRewardsActivity.ts new file mode 100644 index 0000000000..62e3f8834a --- /dev/null +++ b/src/queries/services/rewards/getRewardsActivity.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; +import { getRewardsBaseURL } from 'queries/baseURL'; +import { getRewardsActivityModelCreator } from 'queries/models'; + +export const getRewardsActivity = (userId: string, activityId: string) => + axios({ + method: 'GET', + + url: `${getRewardsBaseURL()}/users/${userId}/activity/${activityId}`, + }).then((response) => getRewardsActivityModelCreator(response.data)); diff --git a/src/queries/services/rewards/getRewardsLeaderboard.ts b/src/queries/services/rewards/getRewardsLeaderboard.ts new file mode 100644 index 0000000000..a0187a8fea --- /dev/null +++ b/src/queries/services/rewards/getRewardsLeaderboard.ts @@ -0,0 +1,16 @@ +import axios from 'axios'; + +import { getRewardsLeaderboardModalCreator } from '../../models'; +import { LeaderboardParams } from '../../types'; +import { getRewardsBaseURL } from '../../baseURL'; + +export const getRewardsLeaderboard = async ({ order, pageSize, pageNumber }: LeaderboardParams) => + axios({ + method: 'GET', + url: `${getRewardsBaseURL()}/users/leaderboard`, + params: { + order: order || 'desc', + pageSize: pageSize || 20, + pageNumber: pageNumber || 1, + }, + }).then((response) => getRewardsLeaderboardModalCreator(response.data)); diff --git a/src/queries/services/rewards/getUserDiscordDetails.ts b/src/queries/services/rewards/getUserDiscordDetails.ts new file mode 100644 index 0000000000..d4f721bb3a --- /dev/null +++ b/src/queries/services/rewards/getUserDiscordDetails.ts @@ -0,0 +1,11 @@ +import axios from 'axios'; +import { getUserDiscordDetailsModelCreator } from 'queries/models/rewards'; + +export const getUserDiscordDetails = (token: string) => + axios({ + method: 'GET', + url: `https://discord.com/api/users/@me`, + headers: { + Authorization: `Bearer ${token}`, + }, + }).then((response) => getUserDiscordDetailsModelCreator(response.data)); diff --git a/src/queries/services/rewards/getUserRewardsDetail.ts b/src/queries/services/rewards/getUserRewardsDetail.ts new file mode 100644 index 0000000000..ca9767cd89 --- /dev/null +++ b/src/queries/services/rewards/getUserRewardsDetail.ts @@ -0,0 +1,11 @@ +import axios from 'axios'; + +import { getUserRewardsDetailModel } from '../../models'; +import { UserRewardsDetailParams } from '../../types/rewards'; +import { getRewardsBaseURL } from '../../baseURL'; + +export const getUserRewardsDetail = ({ caip10WalletAddress }: UserRewardsDetailParams) => + axios({ + method: 'GET', + url: `${getRewardsBaseURL()}/users/wallet/${caip10WalletAddress}`, + }).then((response) => getUserRewardsDetailModel(response.data)); diff --git a/src/queries/services/rewards/index.ts b/src/queries/services/rewards/index.ts new file mode 100644 index 0000000000..4c24a2a16d --- /dev/null +++ b/src/queries/services/rewards/index.ts @@ -0,0 +1,7 @@ +export * from './getRewardsActivities'; +export * from './getRewardsActivity.ts'; +export * from './getUserDiscordDetails'; +export * from './claimRewardsActivity.ts'; +export * from './getUserRewardsDetail.ts'; +export * from './createUserRewardsDetail.ts'; +export * from './getRewardsLeaderboard'; diff --git a/src/queries/types/index.ts b/src/queries/types/index.ts index 7732dd494c..fdcb1f07a9 100644 --- a/src/queries/types/index.ts +++ b/src/queries/types/index.ts @@ -1,2 +1,4 @@ export * from './channels'; export * from './user'; +export * from './rewards'; +export * from './pointsVault'; diff --git a/src/queries/types/pointsVault.ts b/src/queries/types/pointsVault.ts new file mode 100644 index 0000000000..f1eb2085b7 --- /dev/null +++ b/src/queries/types/pointsVault.ts @@ -0,0 +1,84 @@ +export type PointsVaultStatus = 'COMPLETED' | 'PENDING' | 'REJECTED'; + +export type PointsVaultGetUsersPayload = { + status: PointsVaultStatus; + page?: number; + pageSize?: number; + token: string; + twitter?: string; + wallet?: string; +}; + +export type PointsVaultActivity = { + userId: string; + activityId: string; + data: { + twitter: string; + }; + activityTypeId: string; + status: PointsVaultStatus; + points: number; + multiplier: number; + verificationProof: string; + createdAt: string; // ISO 8601 date string + updatedAt: string; // ISO 8601 date string + userWallet: string; +}; + +export type PointsVaultActivitiesResponse = { + activities: Array; + total: number; + page: number; + pageSize: number; +}; + +export type RejectPointsVaultUserPayload = { + activityId: string; + currentStatus: PointsVaultStatus; + token: string; +}; + +export type ApprovePointsVaultUserPayload = { + activityId: string; + currentStatus: PointsVaultStatus; + token: string; +}; + +export type UserTwitterDetailsPayload = { + twitterHandle: string; + token: string; +}; + +export type UserTwitterDetailsResponse = { + data: { + public_metrics: { + followers_count: number; + following_count: number; + tweet_count: number; + listed_count: number; + like_count: number; + }; + username: string; + id: string; + name: string; + }; +}; + +export type UserTwitterDetailsModelledResponse = { + followersCount: number; + followingCount: number; + tweetCount: number; + listedCount: number; + likeCount: number; + username: string; + id: string; + name: string; +}; + +export type PointsVaultSearchPayload = { + query: string; + token?: string; + page?: number; + pageSize?: number; + status?: PointsVaultStatus; +}; diff --git a/src/queries/types/rewards.ts b/src/queries/types/rewards.ts new file mode 100644 index 0000000000..fccbb6cf08 --- /dev/null +++ b/src/queries/types/rewards.ts @@ -0,0 +1,160 @@ +export type RewardsAcitivitesResponse = { + activities: Activity[]; + total: number; + page: number; + size: number; +}; + +//TODO: Remove the test expiry type +export type ActvityType = 'follow_push_on_discord' | 'follow_push_on_twitter' | 'test_expiry_type'; + +export type Activity = { + id: string; + activityType: ActvityType; + activityTitle: string; + activityDesc: string; + points: number; + multiplier: number; + expiryType: number; + name?: string; + JoinURL: string; +}; + +export type UsersAllActivitiesResponse = { + activities: UsersActivity[]; + total: number; + page: number; + size: number; +}; + +export type UsersActivity = { + activityId: string; + userId: string; + activityTypeId: string; + data: { twitter?: string; discord?: string }; + status: 'COMPLETED' | 'PENDING'; + points: number; + multiplier: number; + verificationProof: string; + createdAt: string; // ISO 8601 date string + updatedAt: string; // ISO 8601 date string +}; + +type Prop = { + [key: string]: string; +}; + +export type ClaimRewardsActivityProps = { + userId: string; + activityTypeId: string; + data: Prop; + verificationProof: string; + pgpPublicKey: string; +}; + +export type PointsVaultUserLoginProps = { + username: string; + password: string; +}; +export type PointsVaultUserLoginPayload = { + token: string; +}; + +export type ClaimActivitesResponse = { + status: 'COMPLETED' | 'PENDING'; +}; + +export type DiscordDetails = { + id: string; + username: string; + avatar: null | string; + discriminator: string; + public_flags: number; + flags: number; + banner: null | string; + accent_color: null | string; + global_name: string; + avatar_decoration_data: null | object; + banner_color: null | string; + clan: null | object; + mfa_enabled: boolean; + locale: string; + premium_type: number; +}; + +export type UserRewardsDetailParams = { + caip10WalletAddress: string; + enabled?: boolean; +}; + +export type UserRewardsDetailResponse = { + userId: string; + userWallet: string; + refPrimary: string | null; + refSecondary: string | null; + verificationProof: string; + activityPoints: number; + referralPoints: number; + totalPoints: number; + multiplier: number; + rank: number; + lastUpdated: string; + pgpPublicKey: string; + usersInvited: number; +}; + +export type createUserRewardsDetailsProps = { + userWallet: string; + pgpPublicKey: string; + verificationProof: string; + refPrimary?: string; +}; + +export type createUserRewardsDetailsResponse = {}; + +type LeaderBoardUser = { + userId: string; + userWallet: string; + refPrimary: string | null; + refSecondary: string | null; + verificationProof: string; + activityPoints: number; + referralPoints: number; + totalPoints: number; + multiplier: number; + lastUpdated: string; + pgpPublicKey: string; + rank: number; +}; + +export type ModelledLeaderBoardUser = { + userId: string; + userWallet: string; + totalPoints: number; + rank: number; +}; + +export type LeaderboardResponse = { + users: Array; + page: number; + pageSize: number; + total: number; +}; + +export type LeaderboardModelledResponse = { + users: Array; + page: number; + pageSize: number; + total: number; +}; + +export type LeaderboardParams = { + order?: string; + pageSize?: number; + pageNumber?: number; +}; + +export type ActivitiesParams = { + pageSize?: number; + pageNumber?: number; +}; diff --git a/src/structure/Header.tsx b/src/structure/Header.tsx index 839f3321fc..a091a49e5d 100644 --- a/src/structure/Header.tsx +++ b/src/structure/Header.tsx @@ -1,7 +1,6 @@ import { Suspense, useContext, useEffect, useRef, useState } from 'react'; // React + Web3 Essentials -import { Link } from 'react-router-dom'; // External Packages import { AiOutlineMenu } from 'react-icons/ai'; @@ -10,13 +9,10 @@ import { DarkModeSwitch } from 'react-toggle-dark-mode'; import styled, { useTheme } from 'styled-components'; // Internal Components -import OpenLink from 'assets/snap/GoToImage.svg?react'; -import MetamaskLogo from 'assets/snap/metamasksnap.svg?react'; +import { Box, Link, Text, Star, Lozenge, RewardsBell } from 'blocks'; import { LOADER_SPINNER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; import Spinner from 'components/reusables/spinners/SpinnerUnit'; -import { AppContext } from 'contexts/AppContext'; import { ErrorContext } from 'contexts/ErrorContext'; -import { GlobalContext } from 'contexts/GlobalContext'; import { NavigationContext } from 'contexts/NavigationContext'; import Profile from 'primaries/Profile'; @@ -26,18 +22,30 @@ import PushLogoLight from '../assets/pushLight.svg'; // Internal Configs import ChainIndicator from 'components/ChainIndicator'; -import { SpanV2 } from 'components/reusables/SharedStylingV2'; import APP_PATHS from 'config/AppPaths'; import GLOBALS from 'config/Globals'; import { themeDark, themeLight } from 'config/Themization'; -import { appConfig } from 'config/index.js'; -import { UnsupportedChainIdError } from 'connectors/error'; import { useAccount, useDeviceWidthCheck } from 'hooks'; import { useClickAway } from 'react-use'; import MobileNavigation from './MobileNavigation'; import { getPublicAssetPath } from 'helpers/RoutesHelper'; +import { walletToCAIP10 } from 'helpers/w2w'; + +// hooks +import { useGetUserRewardsDetails } from 'queries'; // header tags for pages that are not there in navigationList (Sidebar) +const REWARDS_HEADER_TAG = { + title: 'Reward Points', + light: { + bg: GLOBALS.COLORS.GRADIENT_PRIMARY, + fg: themeLight.headerTagFg, + }, + dark: { + bg: themeDark.headerTagBg, + fg: themeDark.headerTagFg, + }, +}; const EXTRA_HEADER_TAGS = { [APP_PATHS.UserSettings]: { title: 'Settings', @@ -61,6 +69,52 @@ const EXTRA_HEADER_TAGS = { fg: themeDark.headerTagFg, }, }, + [APP_PATHS.Rewards]: REWARDS_HEADER_TAG, + [APP_PATHS.RewardsActivities]: REWARDS_HEADER_TAG, + [APP_PATHS.RewardsLeaderboard]: REWARDS_HEADER_TAG, +}; + +const RewardsHeaderLink = ({ caip10WalletAddress }: { caip10WalletAddress: string }) => { + const { data: userDetails } = useGetUserRewardsDetails({ + caip10WalletAddress: caip10WalletAddress, + }); + return ( + + + + + + {userDetails && userDetails?.totalPoints > 0 ? userDetails?.totalPoints : ''} + + + {userDetails && userDetails?.totalPoints > 0 ? userDetails?.totalPoints : ''} + + }>NEW + + + + ); }; // Create Header @@ -70,9 +124,10 @@ function Header({ isDarkMode, darkModeToggle }) { const navRef = useRef(); const { navigationSetup } = useContext(NavigationContext); - const { setSnapInstalled, snapInstalled } = useContext(AppContext); - const { isActive, switchChain, wallet } = useAccount(); + const { isActive, wallet, account } = useAccount(); + const caip10WalletAddress = walletToCAIP10({ account }); + const { authError: error } = useContext(ErrorContext); const [showLoginControls, setShowLoginControls] = useState(false); @@ -85,8 +140,7 @@ function Header({ isDarkMode, darkModeToggle }) { // Get Location const location = useLocation(); - - // const [snapInstalled, setSnapInstalled] = useState(false); + const isSnapPage = location?.pathname === '/snap'; useEffect(() => { // runs when navigation setup is updated, will run on init @@ -118,66 +172,7 @@ function Header({ isDarkMode, darkModeToggle }) { setShowNavBar(!showNavBar); }); - // handle error functions - function getErrorMessage(error: Error) { - if (error instanceof UnsupportedChainIdError) { - switchChain(appConfig.coreContractChain); - if (appConfig.coreContractChain === 42) - return 'Unsupported Network, please connect to the Ethereum Kovan network or Polygon Amoy network'; - else if (appConfig.coreContractChain === 11155111) - return 'Unsupported Network, please connect to the Ethereum Sepolia, Polygon Amoy, BNB testnet, Optimism Sepolia, Arbitrum Sepolia or Polygon zkEVM testnet'; - else - return 'Unsupported Network, please connect to the Ethereum, Polygon, BNB, Optimism, Arbitrum or Polygon zkEVM Mainnet'; - } else { - console.error(error); - return 'An unknown error occurred. Check the console for more details'; - } - } - - const bellPressed = () => { - setShowLoginControls(!showLoginControls); - }; - const isMobile = useDeviceWidthCheck(600); - const showSnapMobile = useDeviceWidthCheck(600); - const isSnapPage = location?.pathname === '/snap'; - - const isSnapInstalled = async () => { - const installedSnaps = await window.ethereum.request({ - method: 'wallet_getSnaps', - }); - Object.keys(installedSnaps).forEach((snap) => { - if (snap == 'npm:@pushprotocol/snap') { - setSnapInstalled(true); - } - }); - }; - - useEffect(() => { - isSnapInstalled(); - }, []); - - const SnapHeader = () => { - return ( - - - - - Get Notifications directly in MetaMask - - - Install Push Snap - - - - ); - }; return ( - {showSnapMobile && } + {/* + + */} @@ -230,91 +227,111 @@ function Header({ isDarkMode, darkModeToggle }) { )} - - - {headerTag && !error && !isSnapPage && ( - - - {headerTag.title} - - - )} - - - } + + - {!showSnapMobile && !snapInstalled && } - - - {isActive && !showLoginControls && !error && ( - - )} - - {isActive && !error && ( - - - - )} - - + + + + + + - {/* {!!error && {getErrorMessage(error)}} */} - {/* {!isActive && !error && Please connect to a Web3 Network} */} - {/* {!!error && Please connect to a Web3 Network} */} - {/* {isActive && !showLoginControls && !error && ( + + + + {isActive && !showLoginControls && !error && ( + + )} + + {isActive && !error && ( + + + + )} + + + {/* {!!error && {getErrorMessage(error)}} */} + {/* {!isActive && !error && Please connect to a Web3 Network} */} + {/* {!!error && Please connect to a Web3 Network} */} + {/* {isActive && !showLoginControls && !error && ( )}{' '} */} - - {/* //TODO: The chain Indicator should be removed in guest mode */} - {wallet?.accounts?.length > 0 && } - - - - + + {/* //TODO: The chain Indicator should be removed in guest mode */} + {wallet?.accounts?.length > 0 && } + + + + + ); } @@ -323,9 +340,11 @@ function Header({ isDarkMode, darkModeToggle }) { const Container = styled(Section)` background: ${(props) => props.theme.header.bg}; height: ${GLOBALS.CONSTANTS.HEADER_HEIGHT}px; + gap: 16px; padding: 0 1.5rem; @media (max-width: 425px) { padding: 0 1rem; + gap: 12px; } `; @@ -398,27 +417,6 @@ const NavMenuInner = styled(Item)` height: calc(100vh - 100px); `; -const Notice = styled.span` - border: 0; - outline: 0; - display: flex; - align-items: center; - justify-content: center; - padding: 8px 15px; - margin: 10px; - color: #fff; - border-radius: 20px; - font-size: 14px; -`; - -const PrimaryTheme = styled(Notice)` - background: #e20880; -`; - -const ThirdTheme = styled(Notice)` - background: #674c9f; -`; - const HeaderTag = styled(Item)` flex: 1; margin: 0px 5px; @@ -438,57 +436,5 @@ const HeaderTag = styled(Item)` } `; -const DarkMode = styled(Item)` - @media (max-width: 768px) { - display: none; - } -`; - -const SnapSection = styled.div` - width: 251px; - height: 28px; - display: flex; - flex-direction: row; - border-radius: 12px; - border: 1px solid #d4dcea; - border: 1px solid ${(props) => props.theme.default.border}; - background: ${(props) => props.theme.default.bg}; - padding: 12px 16px; - align-items: center; - gap: 9px; - @media (max-width: 600px) { - width: auto; - padding: 12px 14px; - } -`; - -const InstallText = styled.div` - display: flex; - flex-direction: column; - - @media (max-width: 600px) { - display: block; - width: auto; - } -`; - -const StyledLink = styled(Link)` - cursor: pointer; - font-size: 12px; - font-weight: 400; - color: #d53a94; - text-align: start; - text-decoration: none; - - @media (max-width: 600px) { - margin-left: 5px; - } - - &:hover { - text-decoration: underline; - text-underline-position: under; - } -`; - // Export Default export default Header; diff --git a/src/structure/MasterInterfacePage.tsx b/src/structure/MasterInterfacePage.tsx index 34a55464a8..d30db9404a 100644 --- a/src/structure/MasterInterfacePage.tsx +++ b/src/structure/MasterInterfacePage.tsx @@ -39,7 +39,9 @@ const TutorialPage = lazy(() => import('pages/TutorialPage')); const YieldFarmingV2Page = lazy(() => import('pages/YieldFarmingPageV2')); const UserSettingsPage = lazy(() => import('pages/UserSettingsPage')); const ClaimGalxePage = lazy(() => import('pages/ClaimGalxePage')); -const Dashboard = lazy(() => import('modules/dashboard')); +const WelcomDashboardPage = lazy(() => import('pages/WelcomeDashboardPage')); +const RewardPointsPage = lazy(() => import('pages/RewardPointsPage')); +const PointsVaultPage = lazy(() => import('pages/PointsVaultPage')); // import AirdropPage from 'pages/AirdropPage'; // import ChannelDashboardPage from 'pages/ChannelDashboardPage'; @@ -68,7 +70,10 @@ import { MODAL_POSITION } from 'hooks/useModalBlur'; import MetamaskPushSnapModal from 'modules/receiveNotifs/MetamaskPushSnapModal'; import SnapPage from 'pages/SnapPage'; import { AppContextType } from 'types/context'; -import { getPublicAssetPath } from 'helpers/RoutesHelper'; +import { useBlocksTheme } from 'blocks/Blocks.hooks'; +import { ModeProp } from 'blocks'; + +const rewardsPointsPagePaths = [APP_PATHS.Rewards, APP_PATHS.RewardsActivities, APP_PATHS.RewardsLeaderboard]; // Create Header function MasterInterfacePage() { @@ -85,6 +90,7 @@ function MasterInterfacePage() { const { MetamaskPushSnapModalComponent, blockedLoading }: AppContextType = useContext(AppContext); const { showMetamaskPushSnap } = useContext(AppContext); + const { mode } = useBlocksTheme(); useEffect(() => { if (location.hash == '#receive-notifications') { @@ -140,7 +146,7 @@ function MasterInterfacePage() { // Render return ( - + } + element={} + /> + {rewardsPointsPagePaths.map((path, index) => ( + } + /> + ))} + + } /> } /> + } @@ -390,7 +409,7 @@ function MasterInterfacePage() { } // css style -const Container = styled.div` +const Container = styled.div` display: flex; flex: 1; flex-direction: column; diff --git a/src/structure/Navigation.tsx b/src/structure/Navigation.tsx index 819561bd14..80ad3e71a8 100644 --- a/src/structure/Navigation.tsx +++ b/src/structure/Navigation.tsx @@ -129,7 +129,6 @@ function Navigation() { third: thirdList, navigation: navList, }; - setNavigationSetup(finalList); }, []); @@ -200,6 +199,7 @@ function Navigation() { useEffect(() => { if (navigationSetup) { // loop and find the item in question + Object.entries(navigationSetup).forEach(([key, value]) => { if ( key === 'primary' || @@ -340,6 +340,17 @@ function Navigation() { return transformedMenuList; }; + //checks if the navigation item is active + const checkIfNavigationItemIsActive = (item) => { + if (location.pathname === item.data.href && item.active) return true; + return false; + }; + + //returns navigationBgColor color + const returnNavigationBgColor = (isActive: boolean) => { + return isActive ? theme.nav.activeColor : 'transparent'; + }; + const returnNavList = (lists, count) => { let transformedList = []; @@ -417,7 +428,6 @@ function Navigation() { } let rendered = Object.keys(items).map(function (key) { const section = items[key]; - // console.log(section) const data = section.data; const uid = section.data.uid; // if(uid === 2 ){ @@ -466,8 +476,8 @@ function Navigation() { item={section} data={data} sectionID={sectionID} - active={section.active} - bg={!section.active ? 'transparent' : theme.nav.activeColor} + active={checkIfNavigationItemIsActive(section)} + bg={returnNavigationBgColor(checkIfNavigationItemIsActive(section))} /> @@ -544,8 +554,8 @@ function Navigation() { item={section} data={data} sectionID={sectionID} - active={section.active} - bg={!section.active ? 'transparent' : theme.nav.activeColor} + active={checkIfNavigationItemIsActive(section)} + bg={returnNavigationBgColor(checkIfNavigationItemIsActive(section))} /> @@ -628,8 +638,8 @@ function Navigation() { item={item} data={data} sectionID={sectionID} - active={item.active} - bg={!item.active ? 'transparent' : theme.nav.activeColor} + active={checkIfNavigationItemIsActive(item)} + bg={returnNavigationBgColor(checkIfNavigationItemIsActive(item))} /> diff --git a/yarn.lock b/yarn.lock index 9475443891..2492ea4b46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2284,6 +2284,534 @@ __metadata: languageName: node linkType: hard +"@firebase/analytics-compat@npm:0.2.10": + version: 0.2.10 + resolution: "@firebase/analytics-compat@npm:0.2.10" + dependencies: + "@firebase/analytics": "npm:0.10.4" + "@firebase/analytics-types": "npm:0.8.2" + "@firebase/component": "npm:0.6.7" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/6dd463dab0279d22d8413accee1a1326e4f1937440b96fb25ae96490ee6ad6c5b32836ac1202f0baaf63d3bdb634e2470a970e7a37e9020b22d6323d4a59b134 + languageName: node + linkType: hard + +"@firebase/analytics-types@npm:0.8.2": + version: 0.8.2 + resolution: "@firebase/analytics-types@npm:0.8.2" + checksum: 10/297fb7becbc51950c7de5809fed896c391d1e87b4d8bb4bf88f4e8760b2e32f903a7dd8e92de4424b49c4e2ecb60a44d49e2f9c68ac3f7ffe3a0194f78910392 + languageName: node + linkType: hard + +"@firebase/analytics@npm:0.10.4": + version: 0.10.4 + resolution: "@firebase/analytics@npm:0.10.4" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/installations": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app": 0.x + checksum: 10/0b8f75ff08e7547399ae0f7bc68a76f54b55ef02f50b603abb0ffe36d60de2c006ecde8db825aff9a48cbf047b4225f8063afe9051383d28dae6498d76317132 + languageName: node + linkType: hard + +"@firebase/app-check-compat@npm:0.3.11": + version: 0.3.11 + resolution: "@firebase/app-check-compat@npm:0.3.11" + dependencies: + "@firebase/app-check": "npm:0.8.4" + "@firebase/app-check-types": "npm:0.5.2" + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/db8f2342ad4b7b3053d082472fe2b4b38572406228828513189adf13a827c80a65d4ce0b19c5f695baadea452fb695b774c9ae5b639a4b733a002df44cb3bbb1 + languageName: node + linkType: hard + +"@firebase/app-check-interop-types@npm:0.3.2": + version: 0.3.2 + resolution: "@firebase/app-check-interop-types@npm:0.3.2" + checksum: 10/3effe656a4762c541838f4bde91b4498e51d48389046b930dc3dbb012e54b6ab0727f7c68a3e94198f633d57833346fc337a0847b6b03d2407030e1489d466fe + languageName: node + linkType: hard + +"@firebase/app-check-types@npm:0.5.2": + version: 0.5.2 + resolution: "@firebase/app-check-types@npm:0.5.2" + checksum: 10/2b33a7adfb7b6ebf5423940bf0af5909df69bf2d6184e12e989f6c76062077be16c31193795349862b4f8aab6b3059806b732a92995cae30fd77419f19a86c6e + languageName: node + linkType: hard + +"@firebase/app-check@npm:0.8.4": + version: 0.8.4 + resolution: "@firebase/app-check@npm:0.8.4" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app": 0.x + checksum: 10/0f4ea3c930141ae08c98dc67463d7b6c9b67c0a98a10768a6be7ba1ec56181ffcdeb5e26f60f6cb721794bd03d7e6132541148417f8da45473139c8a138a7d94 + languageName: node + linkType: hard + +"@firebase/app-compat@npm:0.2.35": + version: 0.2.35 + resolution: "@firebase/app-compat@npm:0.2.35" + dependencies: + "@firebase/app": "npm:0.10.5" + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + checksum: 10/6de5c8666af2941b56e550f4207b151fade057b91c995cccc5a1f66abb809b9b92c30b3932d473903d17c57b0fa454bae432c3855ac5a5c6b2785e387852b4a2 + languageName: node + linkType: hard + +"@firebase/app-types@npm:0.9.2": + version: 0.9.2 + resolution: "@firebase/app-types@npm:0.9.2" + checksum: 10/566b3714a4d7e8180514258e4b1549bf5b28ae0383b4ff53d3532a45e114048afdd27c1fef8688d871dd9e5ad5307e749776e23f094122655ac6b0fb550eb11a + languageName: node + linkType: hard + +"@firebase/app@npm:0.10.5": + version: 0.10.5 + resolution: "@firebase/app@npm:0.10.5" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + idb: "npm:7.1.1" + tslib: "npm:^2.1.0" + checksum: 10/55c4190e578efd083c8e22a7ba7b078b2c4c4911e08e79d5187b2779121f9552ee35490373d2f8a101285e6c57c4b8ea1b019137b0274eaee72aa10a790fb564 + languageName: node + linkType: hard + +"@firebase/auth-compat@npm:0.5.9": + version: 0.5.9 + resolution: "@firebase/auth-compat@npm:0.5.9" + dependencies: + "@firebase/auth": "npm:1.7.4" + "@firebase/auth-types": "npm:0.12.2" + "@firebase/component": "npm:0.6.7" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + undici: "npm:5.28.4" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/671203fc6e2fe7d4fc4332129848f552839a771f405fff18502f5035b4e9eda76ad067c2e75b1cbe3e2d4d365d899b092a5f0bd1b395c0ac43c1464e213992fe + languageName: node + linkType: hard + +"@firebase/auth-interop-types@npm:0.2.3": + version: 0.2.3 + resolution: "@firebase/auth-interop-types@npm:0.2.3" + checksum: 10/e55b8ded6bd1a5e6a2845c9c7ed520bb9a8a76e4ddf90249bf685986ac7b1fb079be2fa4edcb6a3aa81d1d56870a470eadcd5a8f20b797dccd803d72ed4c80aa + languageName: node + linkType: hard + +"@firebase/auth-types@npm:0.12.2": + version: 0.12.2 + resolution: "@firebase/auth-types@npm:0.12.2" + peerDependencies: + "@firebase/app-types": 0.x + "@firebase/util": 1.x + checksum: 10/f55449381de8e2a24ffaf19f12b5c4a093c8323034253ea7a5f7afc946327d20b09f32a483c12960862a1c4814645ea80bc4343f0a9f22db5dc048ca82773132 + languageName: node + linkType: hard + +"@firebase/auth@npm:1.7.4": + version: 1.7.4 + resolution: "@firebase/auth@npm:1.7.4" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + undici: "npm:5.28.4" + peerDependencies: + "@firebase/app": 0.x + "@react-native-async-storage/async-storage": ^1.18.1 + peerDependenciesMeta: + "@react-native-async-storage/async-storage": + optional: true + checksum: 10/e61a500b1d67cc1cdc3fff824f20400deb480a0015db1eaee5744026329bbc87d8c755ae6da8b22b9aed0fd42e0d6684c67cc1bb47697152f3e82bb7ae416b26 + languageName: node + linkType: hard + +"@firebase/component@npm:0.6.7": + version: 0.6.7 + resolution: "@firebase/component@npm:0.6.7" + dependencies: + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + checksum: 10/b41a1c654c6da7ed2570c403dac118b3633d05dc8d9769057fdb66ec04f2f3a8dcb7cff4a87dd5a0b9fa614854586d4376b5aadee6876450c1ee851b59d5b0da + languageName: node + linkType: hard + +"@firebase/database-compat@npm:1.0.5": + version: 1.0.5 + resolution: "@firebase/database-compat@npm:1.0.5" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/database": "npm:1.0.5" + "@firebase/database-types": "npm:1.0.3" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + checksum: 10/e7256f2675860340d560143ef40af9d9e1d941798c166e97b95be3c278068524dfceac9a59fe689a8c6c3704a84faae674601f8504a481521dff43dbc3063b1f + languageName: node + linkType: hard + +"@firebase/database-types@npm:1.0.3": + version: 1.0.3 + resolution: "@firebase/database-types@npm:1.0.3" + dependencies: + "@firebase/app-types": "npm:0.9.2" + "@firebase/util": "npm:1.9.6" + checksum: 10/ecc36c54552b42b063011d8c58d1645ac3a09a7da36b2cebd25774a1be7d8aa79f752671f75025df44c079289d0c4f42de3cfcdb48cd7fc73539ba93fdcfb476 + languageName: node + linkType: hard + +"@firebase/database@npm:1.0.5": + version: 1.0.5 + resolution: "@firebase/database@npm:1.0.5" + dependencies: + "@firebase/app-check-interop-types": "npm:0.3.2" + "@firebase/auth-interop-types": "npm:0.2.3" + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + faye-websocket: "npm:0.11.4" + tslib: "npm:^2.1.0" + checksum: 10/1ea0bb014af9e1134f68868a2cfe97befae5c252cc728342d2f8b51ce06df2085dc87b443339eebd4a622c49abdd583bbe2ebf3bd3ac6cad0c148f4a61612c42 + languageName: node + linkType: hard + +"@firebase/firestore-compat@npm:0.3.32": + version: 0.3.32 + resolution: "@firebase/firestore-compat@npm:0.3.32" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/firestore": "npm:4.6.3" + "@firebase/firestore-types": "npm:3.0.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/65ff915c6c1bb3ccc580887c91d7bb6470fbbb4035f3db24dc91ce297391ceb3d12ce2e311b8c7bcac7976e625089e8871cfe0e6a3c22d85945bb34b3022dc0a + languageName: node + linkType: hard + +"@firebase/firestore-types@npm:3.0.2": + version: 3.0.2 + resolution: "@firebase/firestore-types@npm:3.0.2" + peerDependencies: + "@firebase/app-types": 0.x + "@firebase/util": 1.x + checksum: 10/81e91f836a026ecb70937407ca8699add7abb5b050d8815620cde97c3eec3f78f7dfbb366225758909f0df31d9f21e98a84ba62701bd27ee38b2609898c11acd + languageName: node + linkType: hard + +"@firebase/firestore@npm:4.6.3": + version: 4.6.3 + resolution: "@firebase/firestore@npm:4.6.3" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + "@firebase/webchannel-wrapper": "npm:1.0.0" + "@grpc/grpc-js": "npm:~1.9.0" + "@grpc/proto-loader": "npm:^0.7.8" + tslib: "npm:^2.1.0" + undici: "npm:5.28.4" + peerDependencies: + "@firebase/app": 0.x + checksum: 10/a2edab1abb1ff2abb891c490bb81aa698128493847256c3ae7f20eefa97b51442c2689c0659c8e1daeb7f054d06687b6ae3db0680396a338d3228e8865933bd2 + languageName: node + linkType: hard + +"@firebase/functions-compat@npm:0.3.11": + version: 0.3.11 + resolution: "@firebase/functions-compat@npm:0.3.11" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/functions": "npm:0.11.5" + "@firebase/functions-types": "npm:0.6.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/271609ea2d95a333b1fab1ed1d4dcec9b92299253f9a310519b1ee70d61be87029da75dd852ce736179c21886bd2f04bb7aee299e731ef88d9ebe3d00540b61a + languageName: node + linkType: hard + +"@firebase/functions-types@npm:0.6.2": + version: 0.6.2 + resolution: "@firebase/functions-types@npm:0.6.2" + checksum: 10/5b8733f9d4bd85a617d35dd10ce296d9ec0490494e584697c4eda8098ff1e865607d7880b84194e86c35d438bbcd714977c111180502d0d1b6b2da1cde1b37ca + languageName: node + linkType: hard + +"@firebase/functions@npm:0.11.5": + version: 0.11.5 + resolution: "@firebase/functions@npm:0.11.5" + dependencies: + "@firebase/app-check-interop-types": "npm:0.3.2" + "@firebase/auth-interop-types": "npm:0.2.3" + "@firebase/component": "npm:0.6.7" + "@firebase/messaging-interop-types": "npm:0.2.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + undici: "npm:5.28.4" + peerDependencies: + "@firebase/app": 0.x + checksum: 10/839dd2d9a4a9321bb53d210729246fe4db97e7c26dc562ee1b0c95579b111534fd4a27962e19d2a84cd35f62e0ef75d89c06ffa1177d0a178740cfdcf5325162 + languageName: node + linkType: hard + +"@firebase/installations-compat@npm:0.2.7": + version: 0.2.7 + resolution: "@firebase/installations-compat@npm:0.2.7" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/installations": "npm:0.6.7" + "@firebase/installations-types": "npm:0.5.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/b32f9ba4b7d6cdb6c932da28ebbd29a7c398065ec4bf353ac40cb2fc8b2f5a77608759635ade41b6d5e2c8f2047e1710beeb3babdfe116d8fc9c6b693ce922aa + languageName: node + linkType: hard + +"@firebase/installations-types@npm:0.5.2": + version: 0.5.2 + resolution: "@firebase/installations-types@npm:0.5.2" + peerDependencies: + "@firebase/app-types": 0.x + checksum: 10/2e795280c299d644b8c8e3fdfa5c6f20cb367dd3b7df32317211f84393fa169b33dee0cbed28de407f3b22dc8f1fb2f7a11ae5a373f8082cc570ef61ef6b91ba + languageName: node + linkType: hard + +"@firebase/installations@npm:0.6.7": + version: 0.6.7 + resolution: "@firebase/installations@npm:0.6.7" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/util": "npm:1.9.6" + idb: "npm:7.1.1" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app": 0.x + checksum: 10/643bc8f1e908b2edadbda422e86753c8599e94744f1e2c4e4ae60059d32c4001c76d3fd63377a53bc49e31e991fbfa2f0c31a46dd69ed3151cc7c96316184b6c + languageName: node + linkType: hard + +"@firebase/logger@npm:0.4.2": + version: 0.4.2 + resolution: "@firebase/logger@npm:0.4.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/961b4605220c0a56c5f3ccf4e6049e44c27303c1ca998c6fa1d19de785c76d93e3b1a3da455e9f40655711345d8d779912366e4f369d93eda8d08c407cc5b140 + languageName: node + linkType: hard + +"@firebase/messaging-compat@npm:0.2.9": + version: 0.2.9 + resolution: "@firebase/messaging-compat@npm:0.2.9" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/messaging": "npm:0.12.9" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/15140fc298bdd28b28fd052eb630770c4388887a9a1d29afa11d4e74cccd3238826fae97e7beb4196a286fe0555023f836442508f13776c6e371bf610819aa5d + languageName: node + linkType: hard + +"@firebase/messaging-interop-types@npm:0.2.2": + version: 0.2.2 + resolution: "@firebase/messaging-interop-types@npm:0.2.2" + checksum: 10/547f8ebf2c5a8dcbc484991b97d76bd3dc3eb4bd9d4e6ea2ffc652097c7065d92dc68d389ddb19fba41e0ce3b5f4cd757ed22f96b4744801149b0f8dbf323af7 + languageName: node + linkType: hard + +"@firebase/messaging@npm:0.12.9": + version: 0.12.9 + resolution: "@firebase/messaging@npm:0.12.9" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/installations": "npm:0.6.7" + "@firebase/messaging-interop-types": "npm:0.2.2" + "@firebase/util": "npm:1.9.6" + idb: "npm:7.1.1" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app": 0.x + checksum: 10/67ace395630a858f990513398ea2055cbbf6c167f47068c62cc9eb342a6ab459a3fcb0f2f7b26c1c7027cb79a5e67e4124602723ed9e2ac483153981b91206e0 + languageName: node + linkType: hard + +"@firebase/performance-compat@npm:0.2.7": + version: 0.2.7 + resolution: "@firebase/performance-compat@npm:0.2.7" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/performance": "npm:0.6.7" + "@firebase/performance-types": "npm:0.2.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/346cc57378384377c4001dfc84322ece4bcebfbcfb7547e9629576ee527c89b257f763afd2e72640c32732006360976abd26c3e17734dd23398b1165bd3e0b9a + languageName: node + linkType: hard + +"@firebase/performance-types@npm:0.2.2": + version: 0.2.2 + resolution: "@firebase/performance-types@npm:0.2.2" + checksum: 10/d25ae06cb75ab6b44ffacf7affadc1f651881f283e58381c444eb63b62dfb74c33c544ab89843518ec1d15367ba7c4343b4d6b22de1f1df35126a1283baa578d + languageName: node + linkType: hard + +"@firebase/performance@npm:0.6.7": + version: 0.6.7 + resolution: "@firebase/performance@npm:0.6.7" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/installations": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app": 0.x + checksum: 10/760b942ad58d73fc6a6b28ff90d59c67fbf5405dc99e2c0fbaa604eecb1fb82419992763c0e59a92147e6cf7d43c1e32ee7cccd39d0706416263bf9684b19a27 + languageName: node + linkType: hard + +"@firebase/remote-config-compat@npm:0.2.7": + version: 0.2.7 + resolution: "@firebase/remote-config-compat@npm:0.2.7" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/remote-config": "npm:0.4.7" + "@firebase/remote-config-types": "npm:0.3.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/d8e729bb1e5da61a6d0c23dc410a43c404acdd2794cc880a311571354727299ec64936f3dc31b7081786a18cb4e39e12299d8ac4d264af6527fdaecbc5bf0fb3 + languageName: node + linkType: hard + +"@firebase/remote-config-types@npm:0.3.2": + version: 0.3.2 + resolution: "@firebase/remote-config-types@npm:0.3.2" + checksum: 10/6c91599c653825708aba9fe9e4562997f108c3e4f3eaf5d188f31c859a6ad013414aa7a213b6b021b68049dd0dd57158546dbc9fb64384652274ef7f57ce7d7d + languageName: node + linkType: hard + +"@firebase/remote-config@npm:0.4.7": + version: 0.4.7 + resolution: "@firebase/remote-config@npm:0.4.7" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/installations": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app": 0.x + checksum: 10/9fc4699921d20fbd030e1349fd53baaf06faf67c097158993249be295e51fc93117b8efd6c8f13d07353b72a1dd282cbf5ddd12f7bdcaa8f2ea35e3769210be1 + languageName: node + linkType: hard + +"@firebase/storage-compat@npm:0.3.8": + version: 0.3.8 + resolution: "@firebase/storage-compat@npm:0.3.8" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/storage": "npm:0.12.5" + "@firebase/storage-types": "npm:0.8.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app-compat": 0.x + checksum: 10/9c92e3f8e72946cc926c8ceb4098589df72d41e677dd7177a27517f98a3d6bda094b96c3370eca1c21bcb59166fbc4480d053255ae81649b7c12b10bed9fdb7b + languageName: node + linkType: hard + +"@firebase/storage-types@npm:0.8.2": + version: 0.8.2 + resolution: "@firebase/storage-types@npm:0.8.2" + peerDependencies: + "@firebase/app-types": 0.x + "@firebase/util": 1.x + checksum: 10/e00716932370d2004dc9f7ef6d7e3aff72305b91569fa6ec15e8bc2ec784b03a150391e8be2c063234edbbfda7796da915d48e26ce2f1f7c5d3343acd39afd99 + languageName: node + linkType: hard + +"@firebase/storage@npm:0.12.5": + version: 0.12.5 + resolution: "@firebase/storage@npm:0.12.5" + dependencies: + "@firebase/component": "npm:0.6.7" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + undici: "npm:5.28.4" + peerDependencies: + "@firebase/app": 0.x + checksum: 10/e8d3241f1851338f33ab954eb636b464fdd75f2800bdba56d52e807c99f5c6b4ac3f59ff35a836377fb28f72709310af7ba5daba9b8138f9173a621fc2231ca0 + languageName: node + linkType: hard + +"@firebase/util@npm:1.9.6": + version: 1.9.6 + resolution: "@firebase/util@npm:1.9.6" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/4037241991fefd28df19a38a638b8befb01e3d23b111623986256113604c485c3cca4c761de9888f6271da736dc10f0e8311b47f693d574ea46323c5bfd9abdb + languageName: node + linkType: hard + +"@firebase/vertexai-preview@npm:0.0.2": + version: 0.0.2 + resolution: "@firebase/vertexai-preview@npm:0.0.2" + dependencies: + "@firebase/app-check-interop-types": "npm:0.3.2" + "@firebase/component": "npm:0.6.7" + "@firebase/logger": "npm:0.4.2" + "@firebase/util": "npm:1.9.6" + tslib: "npm:^2.1.0" + peerDependencies: + "@firebase/app": 0.x + "@firebase/app-types": 0.x + checksum: 10/00ebde87389d85023101b727f2a54af0263a1ab2ad238a9d395672aeb135a023c980a862713b7df6343669be8a10cf8560a1f77da8ceddb0aef2703918c90973 + languageName: node + linkType: hard + +"@firebase/webchannel-wrapper@npm:1.0.0": + version: 1.0.0 + resolution: "@firebase/webchannel-wrapper@npm:1.0.0" + checksum: 10/007307141753c87d62f94ebe3365bc6d610ccbd1ac13b1b4584ed74452f874a830db69b781e4decc509c1327133df1169eecd39bdccb3c7b3b34ea30f2d884a5 + languageName: node + linkType: hard + "@floating-ui/core@npm:^1.0.0, @floating-ui/core@npm:^1.5.3": version: 1.6.3 resolution: "@floating-ui/core@npm:1.6.3" @@ -2416,6 +2944,30 @@ __metadata: languageName: node linkType: hard +"@grpc/grpc-js@npm:~1.9.0": + version: 1.9.15 + resolution: "@grpc/grpc-js@npm:1.9.15" + dependencies: + "@grpc/proto-loader": "npm:^0.7.8" + "@types/node": "npm:>=12.12.47" + checksum: 10/edd45c5970046ebb1bb54856f22a41186742c77dfb7e5182ca615f690f1a320af3abeef553d8924812d56911157a04882c7d264c2de64f326f8df7d473c47b2a + languageName: node + linkType: hard + +"@grpc/proto-loader@npm:^0.7.8": + version: 0.7.13 + resolution: "@grpc/proto-loader@npm:0.7.13" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.2.5" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/7e2d842c2061cbaf6450c71da0077263be3bab165454d5c8a3e1ae4d3c6d2915f02fd27da63ff01f05e127b1221acd40705273f5d29303901e60514e852992f4 + languageName: node + linkType: hard + "@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" @@ -6351,7 +6903,7 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:*, @types/hoist-non-react-statics@npm:^3.3.0": +"@types/hoist-non-react-statics@npm:*, @types/hoist-non-react-statics@npm:^3.3.0, @types/hoist-non-react-statics@npm:^3.3.1": version: 3.3.5 resolution: "@types/hoist-non-react-statics@npm:3.3.5" dependencies: @@ -6447,7 +6999,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=13.7.0": +"@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0": version: 20.14.9 resolution: "@types/node@npm:20.14.9" dependencies: @@ -6527,6 +7079,15 @@ __metadata: languageName: node linkType: hard +"@types/react-infinite-scroller@npm:^1": + version: 1.2.5 + resolution: "@types/react-infinite-scroller@npm:1.2.5" + dependencies: + "@types/react": "npm:*" + checksum: 10/8b56f63c35802d2593cc25b3ab03bfbeceb2a5379997364fb5cea414cc8690341fbf3902d8fc7a6db43f87c144276009cb4a7f3388f1a52091ac3a4fd7de7d33 + languageName: node + linkType: hard + "@types/react-redux@npm:^7.1.20": version: 7.1.33 resolution: "@types/react-redux@npm:7.1.33" @@ -8883,6 +9444,15 @@ __metadata: languageName: node linkType: hard +"blockies-react-svg@npm:^0.0.13": + version: 0.0.13 + resolution: "blockies-react-svg@npm:0.0.13" + peerDependencies: + react: ">=16.8.0" + checksum: 10/1618aaf4186afe4cfec9680e831cdc03ea2fb911964a71e5392a64487c0a5ccddb0f37bc933e9680f76b552fa20f1e85b0a6072cfa9445df8b5d5877e2b0360f + languageName: node + linkType: hard + "blockies@npm:0.0.2": version: 0.0.2 resolution: "blockies@npm:0.0.2" @@ -9718,6 +10288,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10/eaa5561aeb3135c2cddf7a3b3f562fc4238ff3b3fc666869ef2adf264be0f372136702f16add9299087fb1907c2e4ec5dbfe83bd24bce815c70a80c6c1a2e950 + languageName: node + linkType: hard + "clone-regexp@npm:^3.0.0": version: 3.0.0 resolution: "clone-regexp@npm:3.0.0" @@ -10496,6 +11077,13 @@ __metadata: languageName: node linkType: hard +"deepmerge@npm:^2.1.1": + version: 2.2.1 + resolution: "deepmerge@npm:2.2.1" + checksum: 10/a3da411cd3d471a8ae86ff7fd5e19abb648377b3f8c42a9e4c822406c2960a391cb829e4cca53819b73715e68f56b06f53c643ca7bba21cab569fecc9a723de1 + languageName: node + linkType: hard + "deepmerge@npm:^4.0.0, deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" @@ -12383,6 +12971,15 @@ __metadata: languageName: node linkType: hard +"faye-websocket@npm:0.11.4": + version: 0.11.4 + resolution: "faye-websocket@npm:0.11.4" + dependencies: + websocket-driver: "npm:>=0.5.1" + checksum: 10/22433c14c60925e424332d2794463a8da1c04848539b5f8db5fced62a7a7c71a25335a4a8b37334e3a32318835e2b87b1733d008561964121c4a0bd55f0878c3 + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -12468,6 +13065,41 @@ __metadata: languageName: node linkType: hard +"firebase@npm:^10.12.2": + version: 10.12.2 + resolution: "firebase@npm:10.12.2" + dependencies: + "@firebase/analytics": "npm:0.10.4" + "@firebase/analytics-compat": "npm:0.2.10" + "@firebase/app": "npm:0.10.5" + "@firebase/app-check": "npm:0.8.4" + "@firebase/app-check-compat": "npm:0.3.11" + "@firebase/app-compat": "npm:0.2.35" + "@firebase/app-types": "npm:0.9.2" + "@firebase/auth": "npm:1.7.4" + "@firebase/auth-compat": "npm:0.5.9" + "@firebase/database": "npm:1.0.5" + "@firebase/database-compat": "npm:1.0.5" + "@firebase/firestore": "npm:4.6.3" + "@firebase/firestore-compat": "npm:0.3.32" + "@firebase/functions": "npm:0.11.5" + "@firebase/functions-compat": "npm:0.3.11" + "@firebase/installations": "npm:0.6.7" + "@firebase/installations-compat": "npm:0.2.7" + "@firebase/messaging": "npm:0.12.9" + "@firebase/messaging-compat": "npm:0.2.9" + "@firebase/performance": "npm:0.6.7" + "@firebase/performance-compat": "npm:0.2.7" + "@firebase/remote-config": "npm:0.4.7" + "@firebase/remote-config-compat": "npm:0.2.7" + "@firebase/storage": "npm:0.12.5" + "@firebase/storage-compat": "npm:0.3.8" + "@firebase/util": "npm:1.9.6" + "@firebase/vertexai-preview": "npm:0.0.2" + checksum: 10/427d404993a86832ee528a9bb68c12c1f1e81b07b6d61fd721351e64c8ba3025a91d4464361cff9160ca0ef994b2f9d409ecbe0f685430fbe4ce2606822e96d6 + languageName: node + linkType: hard + "flairup@npm:0.0.38": version: 0.0.38 resolution: "flairup@npm:0.0.38" @@ -12583,6 +13215,24 @@ __metadata: languageName: node linkType: hard +"formik@npm:^2.4.6": + version: 2.4.6 + resolution: "formik@npm:2.4.6" + dependencies: + "@types/hoist-non-react-statics": "npm:^3.3.1" + deepmerge: "npm:^2.1.1" + hoist-non-react-statics: "npm:^3.3.0" + lodash: "npm:^4.17.21" + lodash-es: "npm:^4.17.21" + react-fast-compare: "npm:^2.0.1" + tiny-warning: "npm:^1.0.2" + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10/65d6845d913cfceebdbb1e34d498725965e07abd4c17f3ea9eeba77d9fab7d3b0f726fdfcae73f002b660ba56b236abc8d8aa6670a9c7cc0db27afebf6e48f4b + languageName: node + linkType: hard + "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -12708,7 +13358,7 @@ __metadata: languageName: node linkType: hard -"get-caller-file@npm:^2.0.1": +"get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" checksum: 10/b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 @@ -13346,6 +13996,13 @@ __metadata: languageName: node linkType: hard +"http-parser-js@npm:>=0.5.1": + version: 0.5.8 + resolution: "http-parser-js@npm:0.5.8" + checksum: 10/2a78a567ee6366dae0129d819b799dce1f95ec9732c5ab164a78ee69804ffb984abfa0660274e94e890fc54af93546eb9f12b6d10edbaed017e2d41c29b7cf29 + languageName: node + linkType: hard + "http-proxy-agent@npm:^7.0.0": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" @@ -13450,6 +14107,13 @@ __metadata: languageName: node linkType: hard +"idb@npm:7.1.1": + version: 7.1.1 + resolution: "idb@npm:7.1.1" + checksum: 10/8e33eaebf21055129864acb89932e0739b8c96788e559df24c253ce114d8c6deb977a3b30ea47a9bb8a2ae8a55964861c3df65f360d95745e341cee40d5c17f4 + languageName: node + linkType: hard + "idna-uts46-hx@npm:^2.3.1": version: 2.3.1 resolution: "idna-uts46-hx@npm:2.3.1" @@ -15364,6 +16028,13 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 10/03f39878ea1e42b3199bd3f478150ab723f93cc8730ad86fec1f2804f4a07c6e30deaac73cad53a88e9c3db33348bb8ceeb274552390e7a75d7849021c02df43 + languageName: node + linkType: hard + "lodash._baseiteratee@npm:~4.7.0": version: 4.7.0 resolution: "lodash._baseiteratee@npm:4.7.0" @@ -15413,6 +16084,13 @@ __metadata: languageName: node linkType: hard +"lodash.camelcase@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.camelcase@npm:4.3.0" + checksum: 10/c301cc379310441dc73cd6cebeb91fb254bea74e6ad3027f9346fc43b4174385153df420ffa521654e502fd34c40ef69ca4e7d40ee7129a99e06f306032bfc65 + languageName: node + linkType: hard + "lodash.clonedeep@npm:^4.5.0": version: 4.5.0 resolution: "lodash.clonedeep@npm:4.5.0" @@ -17686,7 +18364,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.0.0, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.0.0, prop-types@npm:^15.5.8, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -17708,6 +18386,13 @@ __metadata: languageName: node linkType: hard +"property-expr@npm:^2.0.5": + version: 2.0.6 + resolution: "property-expr@npm:2.0.6" + checksum: 10/89977f4bb230736c1876f460dd7ca9328034502fd92e738deb40516d16564b850c0bbc4e052c3df88b5b8cd58e51c93b46a94bea049a3f23f4a022c038864cab + languageName: node + linkType: hard + "property-information@npm:^5.0.0": version: 5.6.0 resolution: "property-information@npm:5.6.0" @@ -17741,7 +18426,7 @@ __metadata: languageName: node linkType: hard -"protobufjs@npm:^7.2.6": +"protobufjs@npm:^7.2.5, protobufjs@npm:^7.2.6": version: 7.3.2 resolution: "protobufjs@npm:7.3.2" dependencies: @@ -17908,6 +18593,7 @@ __metadata: "@types/openpgp": "npm:^4.4.18" "@types/react": "npm:^18.2.66" "@types/react-dom": "npm:^18.2.22" + "@types/react-infinite-scroller": "npm:^1" "@types/styled-components": "npm:5.1.26" "@typescript-eslint/eslint-plugin": "npm:^7.2.0" "@typescript-eslint/parser": "npm:^7.2.0" @@ -17924,6 +18610,7 @@ __metadata: assert: "npm:2.0.0" babel-plugin-styled-components: "npm:1.10.7" blockies: "npm:0.0.2" + blockies-react-svg: "npm:^0.0.13" browserify-zlib: "npm:^0.2.0" browserslist: "npm:4.14.6" buffer: "npm:6.0.3" @@ -17943,6 +18630,8 @@ __metadata: eth-crypto: "npm:1.6.0" eth-sig-util: "npm:^3.0.1" ethers: "npm:^5.7.2" + firebase: "npm:^10.12.2" + formik: "npm:^2.4.6" https-browserify: "npm:1.0.0" image-size: "npm:0.9.3" immer: "npm:^10.0.2" @@ -17966,6 +18655,7 @@ __metadata: react-ga: "npm:2.7.0" react-icons: "npm:4.12.0" react-image-file-resizer: "npm:^0.4.7" + react-infinite-scroller: "npm:^1.2.6" react-joyride: "npm:^2.4.0" react-multi-select-component: "npm:^4.2.3" react-player: "npm:^2.16.0" @@ -17992,6 +18682,7 @@ __metadata: vite-tsconfig-paths: "npm:^4.3.2" web3: "npm:^1.8.2" xss: "npm:^1.0.14" + yup: "npm:^1.4.0" languageName: unknown linkType: soft @@ -18316,6 +19007,13 @@ __metadata: languageName: node linkType: hard +"react-fast-compare@npm:^2.0.1": + version: 2.0.4 + resolution: "react-fast-compare@npm:2.0.4" + checksum: 10/e4e3218c0f5c29b88e9f184a12adb77b0a93a803dbd45cb98bbb754c8310dc74e6266c53dd70b90ba4d0939e0e1b8a182cb05d081bcab22507a0390fbcd768ac + languageName: node + linkType: hard + "react-fast-compare@npm:^3.0.1": version: 3.2.2 resolution: "react-fast-compare@npm:3.2.2" @@ -18397,6 +19095,17 @@ __metadata: languageName: node linkType: hard +"react-infinite-scroller@npm:^1.2.6": + version: 1.2.6 + resolution: "react-infinite-scroller@npm:1.2.6" + dependencies: + prop-types: "npm:^15.5.8" + peerDependencies: + react: ^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 10/d37d3dfc99029e203631b155cc92181453f14cef1cfbbf9bb05504a67b97eedc0771ca6848d110d846c26e3001981702baefd951bcdcdf9fbec15b5dfb420cad + languageName: node + linkType: hard + "react-innertext@npm:^1.1.5": version: 1.1.5 resolution: "react-innertext@npm:1.1.5" @@ -19395,7 +20104,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 @@ -20175,7 +20884,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -20646,6 +21355,13 @@ __metadata: languageName: node linkType: hard +"tiny-case@npm:^1.0.3": + version: 1.0.3 + resolution: "tiny-case@npm:1.0.3" + checksum: 10/3f7a30c39d5b0e1bc097b0b271bec14eb5b836093db034f35a0de26c14422380b50dc12bfd37498cf35b192f5df06f28a710712c87ead68872a9e37ad6f6049d + languageName: node + linkType: hard + "tiny-glob@npm:^0.2.9": version: 0.2.9 resolution: "tiny-glob@npm:0.2.9" @@ -20724,6 +21440,13 @@ __metadata: languageName: node linkType: hard +"toposort@npm:^2.0.2": + version: 2.0.2 + resolution: "toposort@npm:2.0.2" + checksum: 10/6f128353e4ed9739e49a28fb756b0a00f3752b29fc9b862ff781446598ee3b486cd229697feebc4eabd916eac5de219f3dae450c585bf13673f6b133a7226e06 + languageName: node + linkType: hard + "tough-cookie@npm:~2.5.0": version: 2.5.0 resolution: "tough-cookie@npm:2.5.0" @@ -20908,6 +21631,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^2.19.0": + version: 2.19.0 + resolution: "type-fest@npm:2.19.0" + checksum: 10/7bf9e8fdf34f92c8bb364c0af14ca875fac7e0183f2985498b77be129dc1b3b1ad0a6b3281580f19e48c6105c037fb966ad9934520c69c6434d17fd0af4eed78 + languageName: node + linkType: hard + "type-fest@npm:^4.18.2": version: 4.20.1 resolution: "type-fest@npm:4.20.1" @@ -21093,7 +21823,7 @@ __metadata: languageName: node linkType: hard -"undici@npm:^5.12.0": +"undici@npm:5.28.4, undici@npm:^5.12.0": version: 5.28.4 resolution: "undici@npm:5.28.4" dependencies: @@ -22102,6 +22832,24 @@ __metadata: languageName: node linkType: hard +"websocket-driver@npm:>=0.5.1": + version: 0.7.4 + resolution: "websocket-driver@npm:0.7.4" + dependencies: + http-parser-js: "npm:>=0.5.1" + safe-buffer: "npm:>=5.1.0" + websocket-extensions: "npm:>=0.1.1" + checksum: 10/17197d265d5812b96c728e70fd6fe7d067471e121669768fe0c7100c939d997ddfc807d371a728556e24fc7238aa9d58e630ea4ff5fd4cfbb40f3d0a240ef32d + languageName: node + linkType: hard + +"websocket-extensions@npm:>=0.1.1": + version: 0.1.4 + resolution: "websocket-extensions@npm:0.1.4" + checksum: 10/b5399b487d277c78cdd2aef63764b67764aa9899431e3a2fa272c6ad7236a0fb4549b411d89afa76d5afd664c39d62fc19118582dc937e5bb17deb694f42a0d1 + languageName: node + linkType: hard + "websocket@npm:^1.0.32": version: 1.0.35 resolution: "websocket@npm:1.0.35" @@ -22214,7 +22962,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -22434,6 +23182,13 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10/5f1b5f95e3775de4514edbb142398a2c37849ccfaf04a015be5d75521e9629d3be29bd4432d23c57f37e5b61ade592fb0197022e9993f81a06a5afbdcda9346d + languageName: node + linkType: hard + "yaeti@npm:^0.0.6": version: 0.0.6 resolution: "yaeti@npm:0.0.6" @@ -22472,6 +23227,13 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10/9dc2c217ea3bf8d858041252d43e074f7166b53f3d010a8c711275e09cd3d62a002969a39858b92bbda2a6a63a585c7127014534a560b9c69ed2d923d113406e + languageName: node + linkType: hard + "yargs@npm:^15.3.1": version: 15.4.1 resolution: "yargs@npm:15.4.1" @@ -22491,6 +23253,21 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10/abb3e37678d6e38ea85485ed86ebe0d1e3464c640d7d9069805ea0da12f69d5a32df8e5625e370f9c96dd1c2dc088ab2d0a4dd32af18222ef3c4224a19471576 + languageName: node + linkType: hard + "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0" @@ -22498,6 +23275,18 @@ __metadata: languageName: node linkType: hard +"yup@npm:^1.4.0": + version: 1.4.0 + resolution: "yup@npm:1.4.0" + dependencies: + property-expr: "npm:^2.0.5" + tiny-case: "npm:^1.0.3" + toposort: "npm:^2.0.2" + type-fest: "npm:^2.19.0" + checksum: 10/3d1277e5e1fff4d8130e525c7361f54874ca848ebd427a0aa66606952e3370b9947d84a1ea0b943f389649e886d26b1349930889727489460d6f2f86c2a26e77 + languageName: node + linkType: hard + "zustand@npm:4.4.0": version: 4.4.0 resolution: "zustand@npm:4.4.0"