From c639c9dc97878a7166c82d841b8fce1611cb67e0 Mon Sep 17 00:00:00 2001 From: Rassl Date: Mon, 18 Sep 2023 18:02:12 +0300 Subject: [PATCH] Feature/tweet view (#411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: fix typo * feat: update tweet item, change pagination, fix missing image placehoders --------- Co-authored-by: Расул --- public/person_placeholder.svg | 16 +++ public/verified_twitter.svg | 3 + .../AddNodeModal/SourceUrl/index.tsx | 6 +- src/components/App/Providers/MuiButton.ts | 2 +- src/components/App/Providers/index.tsx | 84 +------------ .../Relevance/Episode/TypePerson/index.tsx | 38 ++++++ .../Relevance/Episode/TypeTweet/index.tsx | 91 ++++++++++++++ .../App/SideBar/Relevance/Episode/index.tsx | 69 +++++++---- .../Relevance/EpisodeSkeleton/index.tsx | 116 ++++++++++++++++++ .../App/SideBar/Relevance/index.tsx | 42 ++++--- .../App/SideBar/SelectedNodeView/index.tsx | 2 +- src/components/App/SideBar/TwitData/index.tsx | 2 +- .../SideBar/{Relevance => }/YouTube/index.tsx | 4 +- src/components/App/SideBar/index.tsx | 50 ++------ src/components/common/Avatar/index.tsx | 8 +- src/components/common/TypeBadge/index.tsx | 3 +- src/types/index.ts | 1 + 17 files changed, 369 insertions(+), 168 deletions(-) create mode 100644 public/person_placeholder.svg create mode 100644 public/verified_twitter.svg create mode 100644 src/components/App/SideBar/Relevance/Episode/TypePerson/index.tsx create mode 100644 src/components/App/SideBar/Relevance/Episode/TypeTweet/index.tsx create mode 100644 src/components/App/SideBar/Relevance/EpisodeSkeleton/index.tsx rename src/components/App/SideBar/{Relevance => }/YouTube/index.tsx (95%) diff --git a/public/person_placeholder.svg b/public/person_placeholder.svg new file mode 100644 index 000000000..bedd0694b --- /dev/null +++ b/public/person_placeholder.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/verified_twitter.svg b/public/verified_twitter.svg new file mode 100644 index 000000000..d5fcfc837 --- /dev/null +++ b/public/verified_twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/AddNodeModal/SourceUrl/index.tsx b/src/components/AddNodeModal/SourceUrl/index.tsx index 9b27c8093..334c5125a 100644 --- a/src/components/AddNodeModal/SourceUrl/index.tsx +++ b/src/components/AddNodeModal/SourceUrl/index.tsx @@ -1,12 +1,12 @@ -import React, { useState, FC } from 'react' +import { FC, useState } from 'react' +import { FaCheck } from 'react-icons/fa' import styled from 'styled-components' import { Flex } from '~/components/common/Flex' import { colors } from '~/utils/colors' -import { requiredRule } from '../index' import { TagInput } from '../TagInput' import { TextArea } from '../TextArea' import { TextInput } from '../TextInput' -import { FaCheck } from 'react-icons/fa' +import { requiredRule } from '../index' type Props = { startTime?: string diff --git a/src/components/App/Providers/MuiButton.ts b/src/components/App/Providers/MuiButton.ts index a2b781737..8aa7ea275 100644 --- a/src/components/App/Providers/MuiButton.ts +++ b/src/components/App/Providers/MuiButton.ts @@ -19,7 +19,7 @@ export const MuiButton = { fontSize: '12px', fontStyle: 'normal', fontWeight: '400', - lineHeight: 'normal', + lineHeight: '8px', cursor: 'pointer', columnGap: '6px', '&:hover': { diff --git a/src/components/App/Providers/index.tsx b/src/components/App/Providers/index.tsx index d08a9aa16..b83a63ed3 100644 --- a/src/components/App/Providers/index.tsx +++ b/src/components/App/Providers/index.tsx @@ -8,6 +8,7 @@ import { FC, PropsWithChildren } from 'react' import { ThemeProvider as StyleThemeProvider } from 'styled-components' import { colors } from '~/utils/colors' import { breakpoints } from '~/utils/media' +import { MuiButton } from './MuiButton' const palette = createPalette({ mode: 'dark', @@ -16,89 +17,16 @@ const palette = createPalette({ }, }) -const MuiButton = { - defaultProps: { - disableElevation: true, - disableRipple: true, - }, - styleOverrides: { - root: { - display: 'inline-flex', - padding: '12px 20px', - justifyContent: 'center', - alignItems: 'center', - gap: '10px', - borderRadius: '200px', - background: colors.BUTTON1, - color: 'var(--Primary-Text, #fff)', - fontFamily: 'Barlow', - fontSize: '12px', - fontStyle: 'normal', - fontWeight: '400', - lineHeight: 'normal', - cursor: 'pointer', - columnGap: '6px', - '&:hover': { - background: colors.BUTTON1_HOVER, - color: colors.GRAY3, - outline: 'none', - boxShadow: 'none', - }, - '&:focus': { - outline: 'none', - boxShadow: 'none', - background: colors.BUTTON1_PRESS, - color: colors.GRAY6, - }, - '&:active': { - outline: 'none', - boxShadow: 'none', - background: colors.BUTTON1_PRESS, - color: colors.GRAY6, - }, - '&.MuiButton-sizeSmall': { - padding: '7px 16px', - fontSize: '11px', - fontWeight: 500, - }, - '&.MuiButton-sizeLarge': { - padding: '12px 24px', - fontSize: '1.2rem', - }, - - '&.MuiButton-outlined': {}, - }, - outlined: { - // Add your custom styles here for the outlined variant - borderColor: colors.BUTTON1, - borderWidth: '1px', - backgroundColor: 'transparent', - '&:hover': { - borderColor: colors.BUTTON1_HOVER, - backgroundColor: 'transparent', - color: colors.GRAY3, - }, - '&:active': { - backgroundColor: colors.BUTTON1_PRESS, - color: colors.GRAY6, - }, - }, - startIcon: { - // Define the size of the icon (adjust as needed) - fontSize: '1em', - }, - endIcon: { - // Define the size of the icon (adjust as needed) - fontSize: '1em', - }, - }, -} - export const appTheme = createTheme({ palette, components: { MuiButton, }, + typography: { + button: { + textTransform: 'none', + }, + }, breakpoints: { values: { xs: breakpoints.small, diff --git a/src/components/App/SideBar/Relevance/Episode/TypePerson/index.tsx b/src/components/App/SideBar/Relevance/Episode/TypePerson/index.tsx new file mode 100644 index 000000000..82fa4a5b6 --- /dev/null +++ b/src/components/App/SideBar/Relevance/Episode/TypePerson/index.tsx @@ -0,0 +1,38 @@ +import styled from 'styled-components' +import { Avatar } from '~/components/common/Avatar' +import { Flex } from '~/components/common/Flex' +import { colors } from '~/utils/colors' + +type Props = { + title: string + imageUrl?: string + name: string +} + +export const TypePerson = ({ title, imageUrl, name }: Props) => ( + + + + + {(title || name) && {title || name}} + +) + +const PictureWrapper = styled(Flex)` + img { + width: 64px; + height: 64px + border-radius: 50%; + object-fit: cover; + } + margin-right: 16px; +` + +const Name = styled(Flex)` + color: ${colors.white}; + font-family: Barlow; + font-size: 13px; + font-style: normal; + font-weight: 600; + line-height: 17px; +` diff --git a/src/components/App/SideBar/Relevance/Episode/TypeTweet/index.tsx b/src/components/App/SideBar/Relevance/Episode/TypeTweet/index.tsx new file mode 100644 index 000000000..80ad068fc --- /dev/null +++ b/src/components/App/SideBar/Relevance/Episode/TypeTweet/index.tsx @@ -0,0 +1,91 @@ +import moment from 'moment' +import styled from 'styled-components' +import { Avatar } from '~/components/common/Avatar' +import { Flex } from '~/components/common/Flex' +import { colors } from '~/utils/colors' +import { Date } from '..' + +type Props = { + text: string + imageUrl?: string + twitterHandle?: string + date: number + name: string + verified: boolean +} + +export const TypeTweet = ({ text, imageUrl, date, twitterHandle, name, verified }: Props) => ( + + + + + + + + {name} + {verified && ( +
+ verified +
+ )} +
+ {twitterHandle && @{twitterHandle}} +
+
+ + + {text} + + {Boolean(date) && {moment.unix(date).format('ll')}} + + +
+) + +const PictureWrapper = styled(Flex)` + img { + width: 64px; + height: 64px + border-radius: 50%; + object-fit: cover; + } + margin-right: 16px; +` + +const Name = styled(Flex)` + color: ${colors.white}; + font-family: Barlow; + font-size: 11px; + font-style: normal; + font-weight: 600; + line-height: normal; + letter-spacing: -0.22px; + .verification { + margin-left: 4px; + } +` + +const TwitterHandle = styled(Flex)` + color: ${colors.GRAY7}; + font-family: Barlow; + font-size: 9px; + font-style: normal; + font-weight: 400; + line-height: normal; +` + +const TwitText = styled(Flex)` + color: ${colors.white}; + font-family: Barlow; + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 130%; + letter-spacing: -0.39px; + margin: 8px 0; + display: -webkit-box; + -webkit-line-clamp: 2; /* Limit to two lines */ + -webkit-box-orient: vertical; + overflow: hidden; + white-space: normal; +` diff --git a/src/components/App/SideBar/Relevance/Episode/index.tsx b/src/components/App/SideBar/Relevance/Episode/index.tsx index 4573740eb..d3cbc8b03 100644 --- a/src/components/App/SideBar/Relevance/Episode/index.tsx +++ b/src/components/App/SideBar/Relevance/Episode/index.tsx @@ -8,6 +8,8 @@ import { Text } from '~/components/common/Text' import { TypeBadge } from '~/components/common/TypeBadge' import { useDataStore } from '~/stores/useDataStore' import { colors } from '~/utils/colors' +import { TypePerson } from './TypePerson' +import { TypeTweet } from './TypeTweet' type EpisodeWrapperProps = FlexboxProps & { isSelected?: boolean @@ -45,7 +47,12 @@ type Props = { id?: string imageUrl: string title?: string + text?: string type?: string + name?: string + verified?: boolean + twitterHandle?: string + profilePicture?: string className?: string onClick: () => void } @@ -59,6 +66,11 @@ export const Episode = ({ imageUrl, title, type, + text, + name, + profilePicture, + verified = false, + twitterHandle, className = 'episode-wrapper', onClick, }: Props) => { @@ -67,35 +79,50 @@ export const Episode = ({ return ( - - {!isSelectedView && ( - - + {type !== 'tweet' && type !== 'person' && type !== 'guest' && ( + + {!isSelectedView && ( + + - {false && } - - )} + {false && } + + )} - - - - {type && } + + + + {type && } + - - {description} - - {Boolean(date) && {moment.unix(date).format('ll')}} - {Boolean(title) && {title}} - {false && } + {description} + + {Boolean(date) && {moment.unix(date).format('ll')}} + {Boolean(title) && {title}} + {false && } + - + )} + {['person', 'guest'].includes(type as string) && ( + + )} + {type === 'tweet' && ( + + )} ) } -const Description = styled(Flex)` +export const Description = styled(Flex)` font-family: Barlow; font-size: 13px; font-style: normal; @@ -110,7 +137,7 @@ const Description = styled(Flex)` white-space: normal; ` -const Date = styled(Text)` +export const Date = styled(Text)` overflow: hidden; color: ${colors.GRAY6}; text-overflow: ellipsis; @@ -123,7 +150,7 @@ const Date = styled(Text)` flex-shrink: 0; ` -const Title = styled(Date)` +export const Title = styled(Date)` display: flex; flex-direction: row; align-items: center; diff --git a/src/components/App/SideBar/Relevance/EpisodeSkeleton/index.tsx b/src/components/App/SideBar/Relevance/EpisodeSkeleton/index.tsx new file mode 100644 index 000000000..12783b864 --- /dev/null +++ b/src/components/App/SideBar/Relevance/EpisodeSkeleton/index.tsx @@ -0,0 +1,116 @@ +import { Skeleton } from '@mui/material' +import styled from 'styled-components' +import { Flex } from '~/components/common/Flex' +import { FlexboxProps } from '~/components/common/Flex/flexbox' +import { Text } from '~/components/common/Text' +import { colors } from '~/utils/colors' + +type EpisodeWrapperProps = FlexboxProps & { + isSelected?: boolean +} + +const EpisodeWrapper = styled(Flex).attrs({ + direction: 'column', +})` + padding: 24px; + cursor: pointer; + border-top: 1px solid #101317; + background: ${colors.BG1}; + + .type-image { + width: 20px; + height: 20px; + border-radius: 50%; + margin-right: 8px; + } + + .booster__pill { + margin-right: 0; + margin-top: 8px; + } + .player-controls { + margin-left: 4px; + } + + .title { + margin: 20px 0 8px; + } +` + +const StyledSkeleton = styled(Skeleton)` + && { + background: rgba(0, 0, 0, 0.15); + } +` + +export const EpisodeSkeleton = () => ( + <> + {Array(7) + .fill(null) + .map((val, index) => ( + // eslint-disable-next-line react/no-array-index-key + + + + + + + + + + + + + + + ))} + +) + +export const Description = styled(Flex)` + font-family: Barlow; + font-size: 13px; + font-style: normal; + font-weight: 500; + line-height: 17px; + color: ${colors.white}; + margin: 16px 0; + display: -webkit-box; + -webkit-line-clamp: 2; /* Limit to two lines */ + -webkit-box-orient: vertical; + overflow: hidden; + white-space: normal; +` + +export const Date = styled(Text)` + overflow: hidden; + color: ${colors.GRAY6}; + text-overflow: ellipsis; + font-family: Barlow; + font-size: 11px; + font-style: normal; + font-weight: 400; + line-height: 18px; + margin-right: 8px; + flex-shrink: 0; +` + +export const Title = styled(Date)` + display: flex; + flex-direction: row; + align-items: center; + flex-shrink: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + &:before { + content: ''; + display: block; + border-radius: 2px; + margin-right: 8px; + width: 4px; + flex-shrink: 0; + height: 4px; + background: ${colors.GRAY6}; + } +` diff --git a/src/components/App/SideBar/Relevance/index.tsx b/src/components/App/SideBar/Relevance/index.tsx index c371b7465..1bc683709 100644 --- a/src/components/App/SideBar/Relevance/index.tsx +++ b/src/components/App/SideBar/Relevance/index.tsx @@ -1,8 +1,9 @@ +import { Button } from '@mui/material' import { ReactNode, useCallback, useMemo, useRef, useState } from 'react' +import styled from 'styled-components' import { useGraphData } from '~/components/DataRetriever' import { ScrollView } from '~/components/ScrollView' import { Flex } from '~/components/common/Flex' -import { Pill } from '~/components/common/Pill' import { useAppStore } from '~/stores/useAppStore' import { useDataStore } from '~/stores/useDataStore' import { NodeExtended } from '~/types' @@ -36,11 +37,10 @@ export const Relevance = ({ header = null }: Props) => { const endSlice = startSlice + pageSize const hasNext = data.nodes.length - 1 > endSlice - const hasPrevious = startSlice > 0 const isMobile = useIsMatchBreakpoint('sm', 'down') - const currentNodes = useMemo(() => data.nodes.slice(startSlice, endSlice), [data.nodes, endSlice, startSlice]) + const currentNodes = useMemo(() => data.nodes.slice(0, endSlice), [data.nodes, endSlice]) const handleNodeClick = useCallback( (node: NodeExtended) => { @@ -70,6 +70,11 @@ export const Relevance = ({ header = null }: Props) => { id, episode_title: episodeTitle, node_type: nodeType, + text, + name, + profile_picture: profilePicture, + verified = false, + twitter_handle: twitterHandle, } = n || {} return ( @@ -81,26 +86,20 @@ export const Relevance = ({ header = null }: Props) => { description={formatDescription(description)} id={id} imageUrl={imageUrl || 'audio_default.svg'} + name={name || ''} onClick={() => handleNodeClick(n)} + profilePicture={profilePicture} + text={text || ''} title={episodeTitle} + twitterHandle={twitterHandle} type={type || nodeType} + verified={verified} /> ) })} - - { - if (hasPrevious) { - setCurrentPage(currentPage - 1) - scrollViewRef.current?.scrollTo(0, 0) - } - }} - > - Previous - - + + ) } + +const LoadMoreWrapper = styled(Flex)` + flex: 0 0 86px; +` diff --git a/src/components/App/SideBar/SelectedNodeView/index.tsx b/src/components/App/SideBar/SelectedNodeView/index.tsx index fb1951291..6f0130a16 100644 --- a/src/components/App/SideBar/SelectedNodeView/index.tsx +++ b/src/components/App/SideBar/SelectedNodeView/index.tsx @@ -6,11 +6,11 @@ import { Creator } from '../Creator' import { Data } from '../Data' import { Messages } from '../Messages' import { Person } from '../Person' -import { YouTube } from '../Relevance/YouTube' import { Show } from '../Show' import { Topic } from '../Topic' import { TwitData } from '../TwitData' import { Twitter } from '../Twitter' +import { YouTube } from '../YouTube' // eslint-disable-next-line no-underscore-dangle const _View = () => { diff --git a/src/components/App/SideBar/TwitData/index.tsx b/src/components/App/SideBar/TwitData/index.tsx index 07907bba2..497d4ccbd 100644 --- a/src/components/App/SideBar/TwitData/index.tsx +++ b/src/components/App/SideBar/TwitData/index.tsx @@ -19,7 +19,7 @@ export const TwitData = () => { - Profile + Profile diff --git a/src/components/App/SideBar/Relevance/YouTube/index.tsx b/src/components/App/SideBar/YouTube/index.tsx similarity index 95% rename from src/components/App/SideBar/Relevance/YouTube/index.tsx rename to src/components/App/SideBar/YouTube/index.tsx index c7320cb14..31c8a27fc 100644 --- a/src/components/App/SideBar/Relevance/YouTube/index.tsx +++ b/src/components/App/SideBar/YouTube/index.tsx @@ -5,8 +5,8 @@ import { Flex } from '~/components/common/Flex' import { useSelectedNode } from '~/stores/useDataStore' import { formatDescription } from '~/utils/formatDescription' import { videoTimetoSeconds } from '~/utils/videoTimetoSeconds' -import { Transcript } from '../../Transcript' -import { Episode } from '../Episode' +import { Episode } from '../Relevance/Episode' +import { Transcript } from '../Transcript' export const YouTube = () => { const selectedNode = useSelectedNode() diff --git a/src/components/App/SideBar/index.tsx b/src/components/App/SideBar/index.tsx index 46861e1a1..7c1c9f582 100644 --- a/src/components/App/SideBar/index.tsx +++ b/src/components/App/SideBar/index.tsx @@ -1,20 +1,21 @@ -import { Slide } from '@mui/material' +import { Button, Slide } from '@mui/material' import { forwardRef, useEffect, useRef, useState } from 'react' import { useFormContext } from 'react-hook-form' import styled from 'styled-components' import { SearchBar } from '~/components/SearchBar' import { Flex } from '~/components/common/Flex' -import { Loader } from '~/components/common/Loader' import { useAppStore } from '~/stores/useAppStore' import { useDataStore, useSelectedNode } from '~/stores/useDataStore' import { colors } from '~/utils/colors' import clsx from 'clsx' +import { ClipLoader } from 'react-spinners' import { useGraphData } from '~/components/DataRetriever' import ChevronLeftIcon from '~/components/Icons/ChevronLeftIcon' import ClearIcon from '~/components/Icons/ClearIcon' import SearchIcon from '~/components/Icons/SearchIcon' import { LatestView } from './Latest' +import { EpisodeSkeleton } from './Relevance/EpisodeSkeleton' import { SideBarSubView } from './SidebarSubView' import { Tab } from './Tab' import { Trending } from './Trending' @@ -65,14 +66,17 @@ const Content = forwardRef(({ onSubmit, subViewOpen - { setValue('search', '') clearSearch() }} > - {searchTerm ? : } + {!isLoading ? ( + <>{searchTerm ? : } + ) : ( + + )} {searchTerm && ( @@ -82,7 +86,7 @@ const Content = forwardRef(({ onSubmit, subViewOpen results
- Teach me +
)} @@ -102,7 +106,7 @@ const Content = forwardRef(({ onSubmit, subViewOpen )} - {isLoading ? : } + {isLoading ? : } ) @@ -182,38 +186,6 @@ const SearchDetails = styled(Flex).attrs({ } ` -const ActionButton = styled(Flex)` - display: inline-flex; - padding: 12px 20px; - justify-content: center; - align-items: center; - gap: 10px; - border-radius: 200px; - background: ${colors.BUTTON1}; - color: var(--Primary-Text, #fff); - text-align: center; - font-family: Barlow; - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: normal; - cursor: pointer; - - & + & { - margin-left: 8px; - } - - &:hover { - background: ${colors.BUTTON1_HOVER}; - color: ${colors.GRAY3}; - } - - &:active { - background: ${colors.BUTTON1_PRESS}; - color: ${colors.GRAY6}; - } -` - const InputButton = styled(Flex).attrs({ align: 'center', justify: 'center', @@ -268,7 +240,7 @@ const CollapseButton = styled(Flex).attrs({ const ScrollWrapper = styled(Flex)(() => ({ overflow: 'auto', - height: 'calc(100% - 158px)', + flex: 1, width: '100%', })) diff --git a/src/components/common/Avatar/index.tsx b/src/components/common/Avatar/index.tsx index 2f321a629..1f29f84e0 100644 --- a/src/components/common/Avatar/index.tsx +++ b/src/components/common/Avatar/index.tsx @@ -1,9 +1,10 @@ import styled from 'styled-components' type Props = { - size?: 45 | 80 | 130 | 188 + size?: 45 | 80 | 130 | 188 | 64 | 27 src: string type: string + rounded?: boolean } type TTypeMapper = { @@ -13,7 +14,10 @@ type TTypeMapper = { const TypesMapper: TTypeMapper = { youtube: 'video', podcast: 'audio', + clip: 'audio', tweet: 'tweet', + person: 'person', + twitter_space: 'audio', } export const Avatar = styled.div` @@ -22,5 +26,5 @@ export const Avatar = styled.div` background-repeat: no-repeat; width: ${({ size = 45 }) => size}px; height: ${({ size = 45 }) => size}px; - border-radius: 2px; + border-radius: ${({ rounded }) => (rounded ? '50%' : '2px')}; ` diff --git a/src/components/common/TypeBadge/index.tsx b/src/components/common/TypeBadge/index.tsx index 42535d451..a9b5acaf7 100644 --- a/src/components/common/TypeBadge/index.tsx +++ b/src/components/common/TypeBadge/index.tsx @@ -12,8 +12,9 @@ type EpisodeTypeImage = { } const EpisodeTypeImages: EpisodeTypeImage = { - podcast: { img: 'audio_badge.svg', label: 'clip' }, + podcast: { img: 'audio_badge.svg', label: 'podcast' }, clip: { img: 'audio_badge.svg', label: 'clip' }, + show: { img: 'audio_badge.svg', label: 'show' }, tweet: { img: 'twitter_badge.svg', label: 'tweet' }, twitter_space: { img: 'audio_badge.svg', label: 'twitter_space' }, youtube: { img: 'video_badge.svg', label: 'episode' }, diff --git a/src/types/index.ts b/src/types/index.ts index bd32d546d..40cc8014a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -52,6 +52,7 @@ export type Node = { weight?: number tweet_id?: string twitter_handle?: string + profile_picture?: string verified?: boolean unique_id?: string }