diff --git a/packages/atlas/atlas.config.yml b/packages/atlas/atlas.config.yml index 62a6878ace..9ed1885645 100644 --- a/packages/atlas/atlas.config.yml +++ b/packages/atlas/atlas.config.yml @@ -119,11 +119,11 @@ features: linkText: Go to Notion # Used only on YPP Dashboard - if empty defaults to "Go to {title}" label: Notion # Used for YPP Dashboard to inform user which vendor given feature uses - if empty defaults to title icon: info # Optional icon to be displayed. Possible icons: message, info, tokenStack - - title: Payments - link: /studio/payments - linkText: Go to Payments - label: Studio - icon: tokenStack + # - title: Payments + # link: /studio/payments + # linkText: Go to Payments + # label: Studio + # icon: tokenStack - title: Support link: https://discord.com/channels/811216481340751934/1053294778529353788 linkText: Go to Discord diff --git a/packages/atlas/src/.env b/packages/atlas/src/.env index 92f8c807b9..70e8dd4f58 100644 --- a/packages/atlas/src/.env +++ b/packages/atlas/src/.env @@ -43,7 +43,7 @@ VITE_DEVELOPMENT_ORION_URL=https://atlas-dev.joystream.org/orion-api/graphql VITE_DEVELOPMENT_QUERY_NODE_SUBSCRIPTION_URL=wss://atlas-dev.joystream.org/orion-v2/graphql VITE_DEVELOPMENT_NODE_URL=wss://atlas-dev.joystream.org/ws-rpc VITE_DEVELOPMENT_FAUCET_URL=https://atlas-dev.joystream.org/member-faucet/register -VITE_DEVELOPMENT_YPP_FAUCET_URL=https://52.204.147.11.nip.io/membership +VITE_DEVELOPMENT_YPP_FAUCET_URL=https://50.19.175.219.nip.io/memberships # Experimental env URLs VITE_NEXT_ORION_AUTH_URL= diff --git a/packages/atlas/src/components/CopyButton/CopyButton.tsx b/packages/atlas/src/components/CopyButton/CopyButton.tsx new file mode 100644 index 0000000000..3a6b2fc9ce --- /dev/null +++ b/packages/atlas/src/components/CopyButton/CopyButton.tsx @@ -0,0 +1,59 @@ +import styled from '@emotion/styled' +import { useRef, useState } from 'react' + +import { Text } from '@/components/Text' +import { Button, ButtonProps } from '@/components/_buttons/Button' +import { Popover, PopoverImperativeHandle } from '@/components/_overlays/Popover' +import { useClipboard } from '@/hooks/useClipboard' +import { cVar, sizes } from '@/styles' + +export type CopyButtonProps = { + textToCopy: string + copySuccessText?: string + className?: string + onClick?: () => void +} & Omit +export const CopyButton = ({ + textToCopy, + copySuccessText = 'Copied', + className, + onClick, + ...buttonProps +}: CopyButtonProps) => { + const { copyToClipboard } = useClipboard() + const popoverRef = useRef(null) + const [copyButtonClicked, setCopyButtonClicked] = useState(false) + const handleCopy = () => { + if (!textToCopy || copyButtonClicked) { + return + } + copyToClipboard(textToCopy) + setCopyButtonClicked(true) + onClick?.() + popoverRef.current?.show() + setTimeout(() => { + setCopyButtonClicked(false) + popoverRef.current?.hide() + }, 3_000) + } + + return ( + + }> + + + {copySuccessText} + + + + + - . - - } - /> - )} - {currentChannel?.yppStatus === 'Unverified' && ( - } - description={ - - Your channel needs to get verified before content syncing starts. It normally takes 12-48 hours for - channels to get verified. -
- Once verified, you will qualify for the rewards. Payouts are made on a weekly basis, every Friday, for the - previous calendar week. Your first payment will involve the reward for the sign up of{' '} - {' '} - USD paid out in ${atlasConfig.joystream.tokenTicker} tokens based on the market rate. -
- } - /> - )} - {atlasConfig.features.ypp.widgets && ( - - {atlasConfig.features.ypp.widgets.map((widget) => ( - , - to: widget.link, - iconPlacement: 'right', - }} - /> + + + + + + + + + + + + + {formatDate(nextPayoutDate)} + + } + iconPlacement="right" + > + Go to Airtable + + + } + /> + + {atlasConfig.features.ypp.widgets && + atlasConfig.features.ypp.widgets.map((widget) => ( + + + + {widget.icon ? configYppIconMapper[widget.icon] : null} + + {widget.title} + + + } iconPlacement="right"> + {widget.linkText ?? `Go to ${widget.title}`} + + + } + /> + ))} - - )} - - {REWARDS?.map((reward) => { - const customMultiplier = reward.customMultiplier?.[currentTier] - const rewardAmount = calculateReward(reward.joyAmount, customMultiplier || multiplier, currentTier) - const rewardAmountUsd = calculateReward(reward.usdAmount, customMultiplier || multiplier, currentTier) - return ( + {!hasDismissedSignupMessage && !currentChannel?.yppStatus.startsWith('Suspended') && ( + { - if ( - reward.actionButton && - 'copyReferral' in reward.actionButton && - reward.actionButton.copyReferral - ) { - trackReferralLinkGenerated(channelId) - copyToClipboard( - `${window.location.host}/ypp?referrerId=${channelId}`, - 'Referral link copied to clipboard' - ) - } - }, - } - : undefined + title={ + currentChannel?.yppStatus.startsWith('Verified') + ? 'Thank you for signing up!' + : `Sign up to ${atlasConfig.general.appName}` + } + description={ + currentChannel?.yppStatus.startsWith('Verified') + ? `You will receive sign up bonus on (Friday) ${formatDate(nextPayoutDate)}` + : 'Connect you YouTube channels via a step-by-step flow and get your first reward. You can sign up with multiple channels!' + } + dollarAmount={ + !currentChannel || !currentChannel.yppStatus.startsWith('Verified') + ? 100 + : getTierRewards(currentChannel.yppStatus.split('::')[1].toLowerCase())?.[0] + } + isRangeAmount={!currentChannel || !currentChannel.yppStatus.startsWith('Verified')} + amountTooltip="Ranks are assigned at discretion of Joystream team based on such factors as content quality and channel popularity" + actionNode={ + !currentChannel || !currentChannel.yppStatus.startsWith('Verified') ? ( + + ) : ( + updateDismissedMessages(getMessageIdForChannel(channelId as string))} + icon={smMatch && } + > + {!smMatch ? 'Close' : ''} + + ) } - joyAmount={rewardAmount} - dollarAmount={rewardAmountUsd} /> - ) - })} - - } - title="Have more than one YouTube channel?" - description={`You can apply to the YouTube Partner Program with as many YouTube & ${APP_NAME} channels as you want. Each YouTube channel can be assigned to only one ${APP_NAME} channel. \nYou can create a new channel from the top right menu.`} - /> + + )} + + + + + Suspended + + + + {syncStatusContent} + + ) + } + /> + + + trackReferralLinkGenerated(channelId)} + > + Copy referral link + + } + /> + + ) } diff --git a/packages/atlas/src/views/studio/YppDashboard/tabs/YppDashboardReferralsTab/YppDashboardReferralsTab.tsx b/packages/atlas/src/views/studio/YppDashboard/tabs/YppDashboardReferralsTab/YppDashboardReferralsTab.tsx index 87b755f16c..05128f00ad 100644 --- a/packages/atlas/src/views/studio/YppDashboard/tabs/YppDashboardReferralsTab/YppDashboardReferralsTab.tsx +++ b/packages/atlas/src/views/studio/YppDashboard/tabs/YppDashboardReferralsTab/YppDashboardReferralsTab.tsx @@ -4,12 +4,15 @@ import { SvgActionLinkUrl } from '@/assets/icons' import { EmptyFallback } from '@/components/EmptyFallback' import { YppReferral, YppReferralTable } from '@/components/YppReferralTable/YppReferralTable' import { Button } from '@/components/_buttons/Button' +import { atlasConfig } from '@/config' import { useClipboard } from '@/hooks/useClipboard' import { useUser } from '@/providers/user/user.hooks' import { useGetYppSyncedChannels } from '@/views/global/YppLandingView/useGetYppSyncedChannels' import { FallbackContainer } from '../YppDashboardTabs.styles' +const BASE_REFERRAL_REWARD = atlasConfig.features.ypp.referralBaseReward ?? 0 + export const YppDashboardReferralsTab = () => { const { currentChannel, isLoading } = useGetYppSyncedChannels() const { copyToClipboard } = useClipboard() @@ -27,7 +30,7 @@ export const YppDashboardReferralsTab = () => { return { date: new Date(channelData.createdAt), channel: String(channelData.joystreamChannelId), - rewardUsd: 1, // TIERS[tier].multiplier * BASE_REFERRAL_REWARD, todo + rewardUsd: 1 * BASE_REFERRAL_REWARD, status: channelData.yppStatus, tier: 1, } diff --git a/packages/atlas/src/views/studio/YppDashboard/tabs/YppDashboardTabs.styles.ts b/packages/atlas/src/views/studio/YppDashboard/tabs/YppDashboardTabs.styles.ts index 502fac3bef..de0da4bb41 100644 --- a/packages/atlas/src/views/studio/YppDashboard/tabs/YppDashboardTabs.styles.ts +++ b/packages/atlas/src/views/studio/YppDashboard/tabs/YppDashboardTabs.styles.ts @@ -1,37 +1,15 @@ +import { keyframes } from '@emotion/react' import styled from '@emotion/styled' import { SvgActionArrowRight, SvgAlertsInformative24, SvgAlertsWarning32 } from '@/assets/icons' import { ActionBar } from '@/components/ActionBar' import { Banner } from '@/components/Banner' -import { cVar, media, sizes, zIndex } from '@/styles' +import { CopyButton } from '@/components/CopyButton/CopyButton' +import { Button } from '@/components/_buttons/Button' +import { cVar, media, sizes, square, zIndex } from '@/styles' export { Divider } from '../YppDashboard.styles' -export const RewardsWrapper = styled.div` - display: grid; - gap: ${sizes(4)}; - margin-bottom: ${sizes(4)}; - - ${media.md} { - gap: ${sizes(6)}; - margin-bottom: ${sizes(6)}; - } -` - -export const WidgetsWrapper = styled.section` - display: grid; - gap: ${sizes(4)}; - margin-bottom: ${sizes(4)}; - - ${media.sm} { - grid-template-columns: repeat(3, 1fr); - } - - ${media.md} { - margin-bottom: ${sizes(6)}; - } -` - export const StyledSvgAlertsInformative24 = styled(SvgAlertsInformative24)` path { fill: ${cVar('colorTextStrong')}; @@ -73,3 +51,72 @@ export const StyledActionBar = styled(ActionBar)` export const FallbackContainer = styled.div` margin-top: 128px; ` +const dotPulse = ({ isOn }: { isOn: boolean }) => keyframes` + 0% { + box-shadow: none; + } + + 10% { + box-shadow: 0 0 0 3px ${isOn ? '#0c984680' : '#ff695f80'}; + } + + 20%, 100% { + box-shadow: none; + } + + +` +export const YppSyncStatus = styled.div` + display: flex; + align-items: center; + justify-content: center; + gap: ${sizes(2)}; + background-color: ${cVar('colorCoreNeutral800Lighten')}; + padding: ${sizes(2.5)} ${sizes(4)}; + border-radius: 99px; + width: 100%; + + ${media.sm} { + width: fit-content; + } + + p { + white-space: nowrap; + } +` +export const StatusDotWrapper = styled.div` + ${square(20)}; + + display: grid; + place-items: center; +` +export const StatusDot = styled.div<{ isOn: boolean }>` + width: 10px; + height: 10px; + border-radius: 50%; + background: ${(props) => (props.isOn ? 'linear-gradient(#0ebe57, #096c34)' : 'linear-gradient(#ff695f, #bf0c00)')}; + box-shadow: 0 0 0 5px ${(props) => (props.isOn ? '#0c984680' : '#ff695f80')}; + animation: 10s ease-out ${(props) => dotPulse(props)} infinite; +` + +export const StyledCloseButton = styled(Button)` + position: static; + + ${media.sm} { + position: absolute; + top: ${sizes(6)}; + right: ${sizes(6)}; + } + + ${media.lg} { + position: static; + } +` + +export const StyledCopyButton = styled(CopyButton)` + width: 100%; + + ${media.sm} { + width: auto; + } +`