Browse Source

Merge pull request #2509 from DzhideX/add-founding-member-program-info

Implement FM reminder banners
Bedeho Mender 3 years ago
parent
commit
6d2a5e0e5a

+ 2 - 0
pioneer/packages/apps/src/SideBar/index.tsx

@@ -14,6 +14,7 @@ import NetworkModal from '../modals/Network';
 import { useTranslation } from '../translate';
 import ChainInfo from './ChainInfo';
 import Item from './Item';
+import SidebarBanner from '../SidebarBanner';
 
 interface Props {
   className?: string;
@@ -100,6 +101,7 @@ function SideBar ({ className = '', collapse, handleResize, isCollapsed, isMenuO
                 )
             ))}
             <Menu.Divider hidden />
+            <SidebarBanner isSidebarCollapsed={isCollapsed}/>
           </div>
           <div className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}>
             <Button

+ 325 - 0
pioneer/packages/apps/src/SidebarBanner.tsx

@@ -0,0 +1,325 @@
+import React, { useState, useEffect } from 'react';
+import usePromise from '@polkadot/joy-utils/react/hooks/usePromise';
+import styled from 'styled-components';
+import { Segment, Loader, Button } from 'semantic-ui-react';
+
+const COUNTER_BORDER_RADIUS_VALUE = 2;
+
+const BannerContainer = styled.div<{ isCollapsed?: boolean }>`
+  ${({ isCollapsed }) => isCollapsed ? `
+    min-height: 222px;
+    max-height: 222px;
+  ` : `
+    min-height: 322px;
+    max-height: 322px;
+    padding: 16px;
+  `}
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  background-color: #4038FF;
+`;
+
+const BannerTitle = styled.h1`
+  padding-right: 1px;
+  font-family: Lato;
+  font-size: 16px;
+  font-weight: 800;
+  line-height: 20px;
+  letter-spacing: 0em;
+  color: white;
+`;
+
+const BannerSubtitle = styled.h2`
+  margin-top: 16px;
+  font-family: Lato;
+  font-size: 14px;
+  font-weight: 400;
+  line-height: 18px;
+  letter-spacing: 0em;
+  color: #E0E1FF;
+`;
+
+const BannerLink = styled.a`
+  margin-top: 8px;
+  font-size: 12px;
+  font-weight: 600;
+  line-height: 16px;
+  letter-spacing: 0em;
+  text-align: center;
+  text-decoration: underline;
+  color: #B4BBFF !important;
+`;
+
+const BannerButton = styled(Button)`
+  width: 100% !important;
+  margin-top: 8px !important;
+`;
+
+const ProgressContainer = styled.div<{ isCollapsed ?: boolean }>`
+  width: 100%;
+  ${({ isCollapsed }) => isCollapsed ? `
+    margin-top: 3px;
+  ` : `
+    margin-top: 8px;
+  `}
+`;
+
+const CounterContainer = styled.div<{ isCollapsed ?: boolean }>`
+  width: 100%;
+  ${({ isCollapsed }) => isCollapsed ? `
+    height: 120px;
+    flex-direction: column;
+  ` : `
+    height: 64px;
+  `}
+  padding: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: ${({ children }) => children && children > 1 ? 'space-between' : 'center'};
+  background-color: #261EE4;
+  border-top-left-radius: ${COUNTER_BORDER_RADIUS_VALUE}px;
+  border-top-right-radius: ${COUNTER_BORDER_RADIUS_VALUE}px;
+`;
+
+const CounterItem = styled.div<{ isCollapsed ?: boolean }>`
+  ${({ isCollapsed }) => isCollapsed ? `
+    width: 43px;
+  ` : `
+    width: 56px;
+  `}
+  height: 48px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+`;
+
+const CounterItemNumber = styled.p`
+  margin: 0;
+  font-size: 32px;
+  font-weight: 700;
+  line-height: 32px;
+  letter-spacing: 0em;
+  color: white;
+`;
+
+const CounterItemText = styled.p`
+  margin: 0;
+  font-size: 10px;
+  font-weight: 600;
+  line-height: 16px;
+  letter-spacing: 0em;
+  color: white;
+`;
+
+const Progress = styled.div<{ isCollapsed?: boolean }>`
+  width: 100%;
+  height: 6px;
+  background-color: #5252FF;
+  ${({ isCollapsed }) => !isCollapsed && `
+    border-bottom-left-radius: ${COUNTER_BORDER_RADIUS_VALUE}px;
+    border-bottom-right-radius: ${COUNTER_BORDER_RADIUS_VALUE}px;
+  `}
+`;
+
+const ProgressBar = styled.div<{ isCollapsed?: boolean }>`
+  width: 0%;
+  height: 100%;
+  background-color: white;
+  ${({ isCollapsed }) => !isCollapsed && `
+    border-bottom-left-radius: ${COUNTER_BORDER_RADIUS_VALUE}px;
+    border-bottom-right-radius: ${COUNTER_BORDER_RADIUS_VALUE}px;
+  `}
+`;
+
+const ErrorText = styled.h1`
+  font-size: 14px;
+  letter-spacing: 0em;
+  font-weight: 600;
+  color: white;
+`;
+
+const DatesContainer = styled.div`
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  margin-top: 8px;
+`;
+
+const DateText = styled.p<{ isCollapsed?: boolean }>`
+  font-size: 12px;
+  line-height: 16px;
+  letter-spacing: 0em;
+  color: #E0E1FF;
+  ${({ isCollapsed }) => isCollapsed && `
+    margin-top: 8px;
+  `}
+`;
+
+const StyledLoader = styled(Loader)`
+  ::before {
+    border-color: rgba(255,255,255,.15) !important;
+  }
+
+  ::after {
+    border-color: white transparent transparent !important;
+  }
+`;
+
+const FM_DATA_URL = 'https://raw.githubusercontent.com/Joystream/founding-members/main/data/fm-info.json';
+const MILLISECONDS_TO_DAYS = 1000 * 60 * 60 * 24;
+
+type FoundingMembersData = {
+  scoringPeriodsFull: {
+    currentScoringPeriod: {
+      started: string;
+      ends: string;
+    }
+  }
+}
+
+const numberToDateString = (number: number) => {
+  const remainingTime: Array<[number, string]> = [];
+
+  const weeks = Math.floor(number / 7);
+  const days = Math.floor(number - (weeks * 7));
+  const hours = Math.floor((number - ((weeks * 7) + days)) * 24);
+
+  if (weeks) {
+    remainingTime.push([weeks, weeks === 1 ? 'WEEK' : 'WEEKS']);
+
+    if (days) {
+      remainingTime.push([days, days === 1 ? 'DAY' : 'DAYS']);
+    }
+
+    return remainingTime;
+  }
+
+  if (days) {
+    remainingTime.push([days, days === 1 ? 'DAY' : 'DAYS']);
+  }
+
+  if (hours) {
+    remainingTime.push([hours, hours === 1 ? 'HOUR' : 'HOURS']);
+  }
+
+  return remainingTime;
+};
+
+const SidebarBanner = ({ isSidebarCollapsed } : { isSidebarCollapsed: boolean}) => {
+  const [foundingMembersData, foundingMembersDataError] = usePromise<FoundingMembersData | undefined>(
+    () => fetch(FM_DATA_URL).then((res) => res.json().then((data) => data as FoundingMembersData)), undefined, []
+  );
+  const [dates, setDates] = useState<{ started: Date, ends: Date }>();
+  const [progress, setProgress] = useState<number>(0);
+  const [remainingTime, setRemainingTime] = useState<number>();
+
+  useEffect(() => {
+    if (foundingMembersData && !foundingMembersDataError) {
+      const scoringPeriodStartedDate = new Date(foundingMembersData.scoringPeriodsFull.currentScoringPeriod.started);
+      const scoringPeriodEndedDate = new Date(foundingMembersData.scoringPeriodsFull.currentScoringPeriod.ends);
+      const now = new Date();
+
+      // calculate the elapsed time from start of scoring period until now
+      const timeDifferenceBetweenDates = Math.abs(scoringPeriodEndedDate.getTime() - scoringPeriodStartedDate.getTime()) / MILLISECONDS_TO_DAYS;
+      const timePassedUntilNow = Math.abs(now.getTime() - scoringPeriodStartedDate.getTime()) / MILLISECONDS_TO_DAYS;
+      const progressPercentage = (timePassedUntilNow / timeDifferenceBetweenDates) * 100;
+
+      // calculate the amount of days remaining until the end of the scoring period
+      const remainingTime = Math.abs(scoringPeriodEndedDate.getTime() - now.getTime()) / MILLISECONDS_TO_DAYS;
+
+      setRemainingTime(remainingTime);
+
+      setDates({
+        started: scoringPeriodStartedDate,
+        ends: scoringPeriodEndedDate
+      });
+
+      setProgress(progressPercentage > 100 ? 100 : progressPercentage);
+    }
+  }, [foundingMembersData]);
+
+  const Loading = ({ isCollapsed } : { isCollapsed ?: boolean}) => (
+    <Segment>
+      <StyledLoader active size={isCollapsed ? 'small' : 'medium'} />
+    </Segment>
+  );
+
+  const Error = () => (
+    <ErrorText> Error.. </ErrorText>
+  );
+
+  if (isSidebarCollapsed) {
+    return (
+      <BannerContainer isCollapsed={true}>
+        <BannerSubtitle>Scoring period ends in:</BannerSubtitle>
+        <ProgressContainer isCollapsed={true}>
+          <CounterContainer isCollapsed={true}>
+            {remainingTime
+              ? numberToDateString(remainingTime).map(([amountOfTime, timePeriodString], index) => (
+                <CounterItem key={`${index}-${amountOfTime}-${timePeriodString}`}>
+                  <CounterItemNumber>{amountOfTime}</CounterItemNumber>
+                  <CounterItemText>{timePeriodString}</CounterItemText>
+                </CounterItem>
+              ))
+              : <Loading isCollapsed={true}/>
+            }
+            {!remainingTime && foundingMembersDataError ? <Error /> : null}
+          </CounterContainer>
+          <Progress isCollapsed={true}>
+            <ProgressBar isCollapsed={true} style={{ width: `${progress}%` }}/>
+          </Progress>
+        </ProgressContainer>
+        <DateText isCollapsed={true} >{dates?.ends.toLocaleString('default', { month: 'short' })} {dates?.ends.getDate()}</DateText>
+      </BannerContainer>
+    );
+  }
+
+  return (
+    <BannerContainer>
+      <BannerTitle>Report your activity to earn FM points</BannerTitle>
+      <BannerSubtitle>Current scoring period ends in:</BannerSubtitle>
+      <ProgressContainer>
+        <CounterContainer>
+          {remainingTime
+            ? numberToDateString(remainingTime).map(([amountOfTime, timePeriodString], index) => (
+              <CounterItem key={`${index}-${amountOfTime}-${timePeriodString}`}>
+                <CounterItemNumber>{amountOfTime}</CounterItemNumber>
+                <CounterItemText>{timePeriodString}</CounterItemText>
+              </CounterItem>
+            ))
+            : <Loading />
+          }
+          {!remainingTime && foundingMembersDataError ? <Error /> : null}
+        </CounterContainer>
+        <Progress>
+          <ProgressBar style={{ width: `${progress}%` }}/>
+        </Progress>
+        <DatesContainer>
+          <DateText>{dates?.started.toLocaleString('default', { month: 'short' })} {dates?.started.getDate()}</DateText>
+          <DateText>{dates?.ends.toLocaleString('default', { month: 'short' })} {dates?.ends.getDate()}</DateText>
+        </DatesContainer>
+      </ProgressContainer>
+      <BannerButton
+        color='black'
+        href='https://www.joystream.org/founding-members/form/'
+        target='_blank'
+        rel='noopener noreferrer'
+      >
+        Report now
+      </BannerButton>
+      <BannerLink
+        href='https://github.com/Joystream/founding-members/blob/main/SUBMISSION-GUIDELINES.md'
+        target='_blank'
+        rel='noopener noreferrer'
+      >
+        Learn more...
+      </BannerLink>
+    </BannerContainer>
+  );
+};
+
+export default SidebarBanner;

+ 2 - 0
pioneer/packages/joy-election/src/index.tsx

@@ -22,6 +22,7 @@ import Reveals from './Reveals';
 import { queryToProp } from '@polkadot/joy-utils/functions/misc';
 import { Seat } from '@joystream/types/council';
 import { ApiProps } from '@polkadot/react-api/types';
+import FMReminderBanner from '@polkadot/joy-utils/react/components/FMReminderBanner';
 
 const ElectionMain = styled.main`${style}`;
 
@@ -67,6 +68,7 @@ class App extends React.PureComponent<Props, State> {
 
     return (
       <ElectionMain className='election--App'>
+        <FMReminderBanner contextualTitle='Council'/>
         <header>
           <Tabs basePath={basePath} items={tabs} />
         </header>

+ 2 - 0
pioneer/packages/joy-forum/src/index.tsx

@@ -16,6 +16,7 @@ import { CategoryList, ViewCategoryById } from './CategoryList';
 import { ViewThreadById } from './ViewThread';
 import { LegacyPagingRedirect } from './LegacyPagingRedirect';
 import ForumRoot from './ForumRoot';
+import FMReminderBanner from '@polkadot/joy-utils/react/components/FMReminderBanner';
 
 const ForumMain = styled.main`${style}`;
 
@@ -29,6 +30,7 @@ class App extends React.PureComponent<Props> {
       <ForumProvider>
         <ForumSudoProvider>
           <ForumMain className='forum--App'>
+            <FMReminderBanner contextualTitle='Forum'/>
             <Switch>
               <Route path={`${basePath}/categories/new`} component={NewCategory} />
               {/* routes for handling legacy format of forum paging within the routing path */}

+ 4 - 2
pioneer/packages/joy-forum/src/style.ts

@@ -1,12 +1,14 @@
 import { css } from 'styled-components';
 
 export default css`
-  padding-top: 1.5rem;
-
   .ui.segment {
     background-color: #fff;
   }
 
+  .ui.breadcrumb {
+    margin-top: 2rem;
+  }
+
   .ForumPageTitle {
     display: flex;
     margin-top: 1rem;

+ 2 - 0
pioneer/packages/joy-proposals/src/index.tsx

@@ -27,6 +27,7 @@ import { SignalForm,
 import { RouteProps as AppMainRouteProps } from '@polkadot/apps-routing/types';
 import style from './style';
 import { HistoricalProposalFromId } from './Proposal/ProposalFromId';
+import FMReminderBanner from '@polkadot/joy-utils/react/components/FMReminderBanner';
 
 const ProposalsMain = styled.main`${style}`;
 
@@ -58,6 +59,7 @@ function App (props: Props): React.ReactElement<Props> {
 
   return (
     <ProposalsMain className='proposal--App'>
+      <FMReminderBanner contextualTitle='Proposals'/>
       <StyledHeader>
         <Tabs
           basePath={basePath}

+ 2 - 0
pioneer/packages/joy-roles/src/index.tsx

@@ -17,6 +17,7 @@ import { OpportunityController, OpportunityView } from './tabs/Opportunity.contr
 import { OpportunitiesController, OpportunitiesView } from './tabs/Opportunities.controller';
 import { ApplyController, ApplyView } from './flows/apply.controller';
 import { MyRolesController, MyRolesView } from './tabs/MyRoles.controller';
+import FMReminderBanner from '@polkadot/joy-utils/react/components/FMReminderBanner';
 
 import './index.sass';
 
@@ -74,6 +75,7 @@ export const App: React.FC<Props> = (props: Props) => {
 
   return (
     <main className='roles--App'>
+      <FMReminderBanner contextualTitle='Working Groups'/>
       <header>
         <Tabs
           basePath={basePath}

BIN
pioneer/packages/joy-utils/src/assets/coin-illustration.png


BIN
pioneer/packages/joy-utils/src/assets/coin-illustration1.png


+ 138 - 0
pioneer/packages/joy-utils/src/react/components/FMReminderBanner.tsx

@@ -0,0 +1,138 @@
+import React from 'react';
+import styled from 'styled-components';
+import { Button, Icon } from 'semantic-ui-react';
+import CoinIllustration from '../../assets/coin-illustration.png';
+import CoinIllustrationSmall from '../../assets/coin-illustration1.png';
+
+const Container = styled.div`
+  height: auto;
+  margin: 2em 0 0 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+`;
+
+const Banner = styled.div`
+  height: 89px;
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 1.5em;
+  background-color: #262626;
+  box-shadow: inset 0px 0px 0px 1px rgba(34, 36, 38, 0.22);
+  border-radius: 4px;
+  background-image: url(${CoinIllustration});
+  background-position: 90% 0;
+  background-repeat: no-repeat;
+  background-size: contain;
+
+  @media(max-width: 1450px){
+    height: 109px;
+  }
+
+  @media(max-width: 1200px){
+    background-image: none;
+  }
+
+  @media(max-width: 800px){
+    flex-direction: column;
+    align-items: initial;
+    height: auto;
+  }
+
+  @media (max-width: 425px){
+    background-image: url(${CoinIllustrationSmall});
+    padding-top: 7em;
+    background-position: left 0;
+    background-size: 200px;
+  }
+`;
+
+const TextContainer = styled.div``;
+
+const BannerTitle = styled.h1`
+  font-family: Lato;
+  font-size: 16px;
+  font-style: normal;
+  font-weight: 900;
+  line-height: 20px;
+  letter-spacing: 0em;
+  color: white;
+  margin-bottom: 7px;
+`;
+
+const BannerText = styled.p`
+  font-size: 14px;
+  font-style: normal;
+  font-weight: 400;
+  line-height: 20px;
+  letter-spacing: 0.0033em;
+  color: #FFFFFFDE;
+
+  a {
+    text-decoration: underline;
+    color: inherit;
+  }
+`;
+
+const BannerButton = styled(Button)`
+  background-color: #4038FF !important;
+  color: white !important;
+  min-width: 155px !important;
+  width: 155px !important;
+  min-height: 36px !important;
+  height: 36px !important;
+
+  .icon {
+    background-color: #3D35F2 !important;
+  }
+
+  margin-left: 260px !important;
+
+  @media(max-width: 1200px){
+    margin-left: 30px !important;
+  }
+
+  @media(max-width: 800px){
+    margin: 20px 0 0 0 !important;
+  }
+`;
+
+interface Props {
+  contextualTitle: 'Council' | 'Working Groups' | 'Proposals' | 'Forum';
+}
+
+const FMReminderBanner = ({ contextualTitle } : Props) => {
+  return (
+    <Container>
+      <Banner>
+        <TextContainer>
+          <BannerTitle>Report your {contextualTitle} activity to earn Founding Members points!</BannerTitle>
+          <BannerText>
+            Only activity that&apos;s been reported is eligible for earning FM points.
+            <a
+              href='https://github.com/Joystream/founding-members/blob/main/SUBMISSION-GUIDELINES.md'
+              target='_blank'
+              rel='noopener noreferrer'
+            >
+              Learn more about reporting your activity...
+            </a>
+          </BannerText>
+        </TextContainer>
+        <BannerButton
+          icon
+          labelPosition='right'
+          href='https://www.joystream.org/founding-members/form/'
+          target='_blank'
+          rel='noopener noreferrer'
+        >
+            Report Now
+          <Icon name='arrow right' />
+        </BannerButton>
+      </Banner>
+    </Container>
+  );
+};
+
+export default FMReminderBanner;