Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

quest list is in 2 columns and added useMemo for fakeQuests loading #248

Merged
merged 7 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@uniswap/sdk-core": "^3.0.1",
"@uniswap/v2-sdk": "^3.0.1",
"@urql/devtools": "^2.0.2",
"browserlist": "^1.0.1",
"chalk": "^4.1.2",
"clipboard-polyfill": "^3.0.0-pre5",
"customize-cra": "^1.0.0",
Expand Down
76 changes: 40 additions & 36 deletions packages/react-app/src/collapsable-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,18 @@ type Props = {
children: any;
label?: string;
visible?: boolean;
collapsed?: boolean;
type?: 'image' | 'code' | 'default';
alt?: string;
};

export function CollapsableBlock(props: Props) {
const theme = useTheme();
const [isVisible, setVisible] = useState(props.visible);
const [collapsed, setCollapsed] = useState(props.collapsed);
const [content, setContent] = useState<ReactNode | undefined>();
const copyCode = useCopyToClipboard();

useEffect(() => setVisible(props.visible), [props.visible]);

const [content, setContent] = useState<ReactNode | undefined>();
useEffect(() => setCollapsed(props.collapsed), [props.collapsed]);

useEffect(() => {
// eslint-disable-next-line jsx-a11y/alt-text
Expand All @@ -72,38 +72,42 @@ export function CollapsableBlock(props: Props) {
}, [props, props.children]);

return (
<WrapperStyled theme={theme}>
<LineStyled>
<CollapseButtonStyled onClick={() => setVisible(!isVisible)}>
<IconColumnStyled>
{isVisible ? (
<>
<IconDown size="tiny" />
<IconUp size="tiny" />
</>
) : (
<>
<IconUp size="tiny" />
<IconDown size="tiny" />
</>
<>
{props.visible && (
<WrapperStyled theme={theme}>
<LineStyled>
<CollapseButtonStyled onClick={() => setCollapsed(!collapsed)}>
<IconColumnStyled>
{collapsed ? (
<>
<IconUp size="tiny" />
<IconDown size="tiny" />
</>
) : (
<>
<IconDown size="tiny" />
<IconUp size="tiny" />
</>
)}
</IconColumnStyled>
<LabelStyled theme={theme}>
{collapsed ? 'Show ' : 'Hide '}
{props.label}
</LabelStyled>
</CollapseButtonStyled>
{!collapsed && (
<CopyButtonStyled
onClick={() => copyCode(content)}
icon={<IconCopy />}
size="small"
label="Copy"
display="icon"
/>
)}
</IconColumnStyled>
<LabelStyled theme={theme}>
{isVisible ? 'Hide ' : 'Show '}
{props.label}
</LabelStyled>
</CollapseButtonStyled>
{isVisible && (
<CopyButtonStyled
onClick={() => copyCode(content)}
icon={<IconCopy />}
size="small"
label="Copy"
display="icon"
/>
)}
</LineStyled>
{isVisible && content ? <ContentWrapperStyled>{content}</ContentWrapperStyled> : <></>}
</WrapperStyled>
</LineStyled>
{!collapsed && content ? <ContentWrapperStyled>{content}</ContentWrapperStyled> : <></>}
</WrapperStyled>
)}
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ const InputStyled = styled.input<{
}
`;

const DateStyled = styled.div`
font-family: Ubuntu Mono;
font-size: 1.1rem;
`;

// #endregion

type Props = {
Expand Down Expand Up @@ -100,7 +105,7 @@ function DateFieldInput({
isDarkTheme={isDarkTheme(theme)}
/>
) : (
<span>{value ? new Date(value).toDateString() : 'Not set'}</span>
<DateStyled>{value ? new Date(value).toDateString() : 'Not set'}</DateStyled>
);

return (
Expand Down
21 changes: 15 additions & 6 deletions packages/react-app/src/components/field-input/text-field-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const BlockStyled = styled.div<{ wide?: boolean }>`
${({ wide }) => wide && 'width: 100%;'}
`;

const TextFieldWrapperStyled = styled.div<{ maxLine?: number }>`
${({ maxLine }) => (maxLine ? `height: ${maxLine * 24}px;` : '')};
overflow: hidden;
`;

// #endregion

type Props = {
Expand All @@ -46,6 +51,8 @@ type Props = {
isMarkDown?: boolean;
ellipsis?: ReactNode;
tooltip?: React.ReactNode;
disableLinks?: boolean;
showBlocks?: boolean;

onBlur?: Function;
error?: string | false;
Expand All @@ -70,6 +77,8 @@ export default function TextFieldInput({
tooltip,
onBlur = noop,
error,
disableLinks = false,
showBlocks = false,
}: Props) {
const [isEditState, setIsEdit] = useState(isEdit);

Expand All @@ -82,7 +91,7 @@ export default function TextFieldInput({
};

const readOnlyContent = (
<>
<TextFieldWrapperStyled maxLine={maxLine}>
{isMarkDown ? (
<Markdown
normalized
Expand All @@ -99,27 +108,27 @@ export default function TextFieldInput({
component: CollapsableBlock,
props: {
label: 'block',
visible: !maxLine,
visible: showBlocks,
},
},
code: {
component: CollapsableBlock,
props: {
label: 'code block',
type: 'code',
visible: !maxLine,
visible: showBlocks,
},
},
img: {
component: CollapsableBlock,
props: {
label: 'image',
type: 'image',
visible: !maxLine,
visible: showBlocks,
},
},
a: {
component: 'a',
component: disableLinks ? 'span' : 'a',
props: {
target: '_blank',
tabIndex: '-1',
Expand All @@ -131,7 +140,7 @@ export default function TextFieldInput({
) : (
value
)}
</>
</TextFieldWrapperStyled>
);
const loadableContent = isEditState ? (
<BlockStyled wide={wide}>
Expand Down
5 changes: 5 additions & 0 deletions packages/react-app/src/components/modals/quest-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ENUM_QUEST_STATE,
ENUM_QUEST_VIEW_MODE,
ENUM_TRANSACTION_STATUS,
MAX_LINE_DESCRIPTION,
} from 'src/constants';
import { QuestModel } from 'src/models/quest.model';
import styled from 'styled-components';
Expand Down Expand Up @@ -344,8 +345,12 @@ export default function QuestModal({
<br />- The payout amount. This could be a constant amount for quests that
payout multiple times, a range with reference to what determines what
amount, the contracts balance at time of claim.
<br />- The first {MAX_LINE_DESCRIPTION} lines only will be displayed in
main page. This is supposed to be an overview of the Quest. Try to stick
with normal text to prevent any overflow cropping.
<br />
⚠️<i>The description should not include any sensitive information.</i>
<br />
</>
}
onChange={handleChange}
Expand Down
27 changes: 14 additions & 13 deletions packages/react-app/src/components/quest.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Card, useViewport } from '@1hive/1hive-ui';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { ENUM_PAGES, ENUM_QUEST_STATE } from 'src/constants';
import { ENUM_PAGES, ENUM_QUEST_STATE, MAX_LINE_DESCRIPTION } from 'src/constants';
import { QuestModel } from 'src/models/quest.model';
import { TokenAmountModel } from 'src/models/token-amount.model';
import * as QuestService from 'src/services/quest.service';
Expand All @@ -26,6 +26,15 @@ import { AddressFieldInput } from './field-input/address-field-input';

const TitleLinkStyled = styled(Link)`
font-weight: 100;
width: 100%;

div {
text-overflow: ellipsis;
overflow: hidden;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
`;

const LinkStyled = styled(Link)`
Expand Down Expand Up @@ -240,24 +249,16 @@ export default function Quest({
</RowStyled>

{!isSummary && fieldsRow}

<TextFieldInput
id="description"
value={questData?.description}
isLoading={isLoading || !questData}
tooltip={
<>
<b>The quest description should include:</b>
<br />- Details about what the quest entails. <br />- What evidence must be
submitted by users claiming a reward for completing the quest. <br />- The payout
amount. This could be a constant amount for quests that payout multiple times, a
range with reference to what determines what amount, the contracts balance at time
of claim. <br />
⚠️<i>The description should not include any sensitive information.</i>
</>
}
multiline
isMarkDown
maxLine={isSummary ? 5 : undefined}
disableLinks={isSummary}
showBlocks={!isSummary}
maxLine={isSummary ? MAX_LINE_DESCRIPTION : undefined}
ellipsis={
<LinkStyled to={`/${ENUM_PAGES.Detail}?id=${questData?.address}`}>
Read more
Expand Down
42 changes: 34 additions & 8 deletions packages/react-app/src/components/views/quest-list.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EmptyStateCard, Button, useViewport } from '@1hive/1hive-ui';
import { debounce } from 'lodash-es';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { isMobile } from 'react-device-detect';
import InfiniteScroll from 'react-infinite-scroll-component';
import Quest from 'src/components/quest';
Expand Down Expand Up @@ -49,10 +49,17 @@ const LineStyled = styled.div`
align-items: center;
`;

const skeletonQuests: any[] = [];
for (let i = 0; i < QUESTS_PAGE_SIZE; i += 1) {
skeletonQuests.push(<Quest key={`${i}`} isLoading isSummary />);
}
const FlexContainerStyled = styled.div`
display: flex;
flex-direction: row;
flex-wrap: wrap;
`;

const QuestWrapperStyled = styled.div<{
singleColumn: boolean;
}>`
width: ${({ singleColumn }) => (singleColumn ? '100%' : '50%')};
`;

export default function QuestList() {
const [quests, setQuests] = useState<QuestModel[]>([]);
Expand All @@ -65,6 +72,18 @@ export default function QuestList() {

const { setPage } = usePageContext();

const skeletonQuests: any[] = useMemo(() => {
const fakeQuests = [];
for (let i = 0; i < QUESTS_PAGE_SIZE; i += 1) {
fakeQuests.push(
<QuestWrapperStyled singleColumn={below('medium')}>
<Quest key={`${i}`} isLoading isSummary />
</QuestWrapperStyled>,
);
}
return fakeQuests;
}, []);

useEffect(() => setPage(ENUM_PAGES.List), [setPage]);

useEffect(() => {
Expand Down Expand Up @@ -147,12 +166,19 @@ export default function QuestList() {
scrollableTarget="scroll-view"
scrollThreshold="120px"
>
<div>
<FlexContainerStyled>
{quests.map((questData: QuestModel) => (
<Quest key={questData.address} isSummary questData={questData} />
<QuestWrapperStyled singleColumn={below('medium')}>
<Quest
key={questData.address}
isSummary
questData={questData}
isLoading={!questData.address}
/>
</QuestWrapperStyled>
))}
{isLoading && skeletonQuests}
</div>
</FlexContainerStyled>
</InfiniteScroll>
</MainView>
);
Expand Down
2 changes: 2 additions & 0 deletions packages/react-app/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,5 @@ export const DEFAULT_FILTER = Object.freeze({
} as FilterModel);

export const DEFAULT_CLAIM_EXECUTION_DELAY_MS = 60 * 15 * 1000; // Add 15 minutes by default

export const MAX_LINE_DESCRIPTION = 5;
2 changes: 2 additions & 0 deletions packages/react-app/src/styles/style.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Ubuntu+Mono&display=swap');

body {
font-family: 'Ubuntu', sans-serif !important;
}
Expand Down
Loading