diff --git a/packages/components/src/ArticleCallToAction/ArticleCallToAction.stories.tsx b/packages/components/src/ArticleCallToAction/ArticleCallToAction.stories.tsx index 6ab0c6e735..e8b3b32290 100644 --- a/packages/components/src/ArticleCallToAction/ArticleCallToAction.stories.tsx +++ b/packages/components/src/ArticleCallToAction/ArticleCallToAction.stories.tsx @@ -52,6 +52,7 @@ export const ArticleCallToActionSingleContributor: StoryFn< countryCode: faker.address.countryCode(), userName: faker.internet.userName(), isVerified: faker.datatype.boolean(), + isSupporter: faker.datatype.boolean(), }, ]} > @@ -63,6 +64,7 @@ const makeFakeUser = () => ({ countryCode: faker.address.countryCode(), userName: faker.internet.userName(), isVerified: faker.datatype.boolean(), + isSupporter: faker.datatype.boolean(), }) export const ArticleCallToActionMultipleContributors: StoryFn< diff --git a/packages/components/src/ArticleCallToAction/ArticleCallToAction.tsx b/packages/components/src/ArticleCallToAction/ArticleCallToAction.tsx index ffac32f9a3..c61371d18b 100644 --- a/packages/components/src/ArticleCallToAction/ArticleCallToAction.tsx +++ b/packages/components/src/ArticleCallToAction/ArticleCallToAction.tsx @@ -5,14 +5,16 @@ import { Username } from '../Username/Username' import type { User } from '../types/common' -export interface Props { - author: User & { isVerified: boolean } +export interface IProps { + author: User children: React.ReactNode - contributors?: (User & { isVerified: boolean })[] + contributors?: User[] } -export const ArticleCallToAction = (props: Props) => { +export const ArticleCallToAction = (props: IProps) => { + const { author, children, contributors } = props const theme = useTheme() as any + return ( { > Made by - + - {props.contributors && props?.contributors.length ? ( + {contributors && contributors.length ? ( With contributions from:{' '} - {props.contributors.map((contributor, key) => ( + {contributors.map((contributor, key) => ( { }, }} > - {props.children} + {children} ) diff --git a/packages/components/src/CommentItem/CommentItem.tsx b/packages/components/src/CommentItem/CommentItem.tsx index 50f24e794e..52ab347f43 100644 --- a/packages/components/src/CommentItem/CommentItem.tsx +++ b/packages/components/src/CommentItem/CommentItem.tsx @@ -40,6 +40,7 @@ export const CommentItem = (props: CommentItemProps) => { creatorName, creatorCountry, isUserVerified, + isUserSupporter, isEditable, _created, _edited, @@ -86,8 +87,9 @@ export const CommentItem = (props: CommentItemProps) => { user={{ userName: creatorName, countryCode: creatorCountry, + isVerified: !!isUserVerified, + isSupporter: !!isUserSupporter, }} - isVerified={!!isUserVerified} /> {_edited && ( (Edited) diff --git a/packages/components/src/CommentItem/types.ts b/packages/components/src/CommentItem/types.ts index 5c6dcc61eb..a4fb702bce 100644 --- a/packages/components/src/CommentItem/types.ts +++ b/packages/components/src/CommentItem/types.ts @@ -1,6 +1,7 @@ export interface IComment { text: string isUserVerified?: boolean + isUserSupporter?: boolean isEditable: boolean creatorCountry?: string | null creatorName: string diff --git a/packages/components/src/MapMemberCard/MapMemberCard.stories.tsx b/packages/components/src/MapMemberCard/MapMemberCard.stories.tsx index 0771cf8dce..fcff4e5b3d 100644 --- a/packages/components/src/MapMemberCard/MapMemberCard.stories.tsx +++ b/packages/components/src/MapMemberCard/MapMemberCard.stories.tsx @@ -14,9 +14,10 @@ export const Default = { imageUrl: 'https://placekitten.com/450/450', description: `${faker.lorem.sentence()}`, user: { - username: faker.internet.userName(), + countryCode: faker.address.countryCode('alpha-2'), + userName: faker.internet.userName(), + isSupporter: faker.datatype.boolean(), isVerified: faker.datatype.boolean(), - country: faker.address.countryCode('alpha-2'), }, heading: `${faker.lorem.word()}`, isEditable: false, @@ -30,9 +31,10 @@ export const LoadingState = { imageUrl: 'https://placekitten.com/450/450', description: `${faker.lorem.sentence()}`, user: { - username: faker.internet.userName(), + countryCode: faker.address.countryCode('alpha-2'), + userName: faker.internet.userName(), + isSupporter: faker.datatype.boolean(), isVerified: faker.datatype.boolean(), - country: faker.address.countryCode('alpha-2'), }, heading: `${faker.lorem.word()}`, isEditable: false, @@ -45,9 +47,10 @@ export const ModerationComments = { description: `${faker.lorem.sentence()}`, comments: `${faker.lorem.sentence()}`, user: { - username: faker.internet.userName(), + countryCode: faker.address.countryCode('alpha-2'), + userName: faker.internet.userName(), + isSupporter: faker.datatype.boolean(), isVerified: faker.datatype.boolean(), - country: faker.address.countryCode('alpha-2'), }, heading: `${faker.lorem.word()}`, isEditable: false, diff --git a/packages/components/src/MapMemberCard/MapMemberCard.tsx b/packages/components/src/MapMemberCard/MapMemberCard.tsx index 4a659c6ba5..846ecc1055 100644 --- a/packages/components/src/MapMemberCard/MapMemberCard.tsx +++ b/packages/components/src/MapMemberCard/MapMemberCard.tsx @@ -1,23 +1,20 @@ +import { keyframes } from '@emotion/react' import { Alert, AspectRatio, Box, Card, Image, Text } from 'theme-ui' import { Username } from '../Username/Username' +import type { User } from '../types/common' + export interface Props { loading?: boolean imageUrl: string description: string comments: string | null - user: { - isVerified: boolean - username: string - country: string | null - } + user: User heading: string isEditable: boolean } -import { keyframes } from '@emotion/react' - const wave = keyframes` from { background: lightgrey; @@ -29,6 +26,7 @@ const wave = keyframes` export const MapMemberCard = (props: Props) => { const { imageUrl, description, user, heading, comments } = props + return ( @@ -62,13 +60,7 @@ export const MapMemberCard = (props: Props) => { {heading}
- +
{ )} - {hasLocation ? ( - - - - {props.country || 'View on Map'} - - - ) : null} - {props?.isSupporter ? ( @@ -74,6 +61,19 @@ export const UserStatistics = (props: UserStatisticsProps) => { ) : null} + {hasLocation ? ( + + + + {props.country || 'View on Map'} + + + ) : null} + {props.usefulCount ? ( diff --git a/packages/components/src/Username/Username.stories.tsx b/packages/components/src/Username/Username.stories.tsx index 3e1778c637..d5614d883c 100644 --- a/packages/components/src/Username/Username.stories.tsx +++ b/packages/components/src/Username/Username.stories.tsx @@ -12,8 +12,9 @@ export const Verified = { user: { userName: 'a-username', countryCode: 'pt', + isSupporter: false, + isVerified: true, }, - isVerified: true, }, } @@ -22,8 +23,31 @@ export const Unverified = { user: { countryCode: 'pt', userName: 'a-username', + isVerified: false, + isSupporter: false, + }, + }, +} + +export const VerifiedSupporter = { + args: { + user: { + countryCode: 'pt', + userName: 'a-username', + isVerified: true, + isSupporter: true, + }, + }, +} + +export const UnverifiedSupporter = { + args: { + user: { + countryCode: 'pt', + userName: 'a-username', + isVerified: false, + isSupporter: true, }, - isVerified: false, }, } @@ -32,7 +56,6 @@ export const WithoutFlag = { user: { userName: 'a-username', }, - isVerified: false, }, } @@ -42,7 +65,6 @@ export const InvalidCountryCode = { userName: 'a-username', countryCode: 'zz', }, - isVerified: false, }, } @@ -54,6 +76,5 @@ export const InlineStyles = { sx: { outline: '10px solid red', }, - isVerified: false, }, } diff --git a/packages/components/src/Username/Username.test.tsx b/packages/components/src/Username/Username.test.tsx index e42fa7fede..3bb84101ab 100644 --- a/packages/components/src/Username/Username.test.tsx +++ b/packages/components/src/Username/Username.test.tsx @@ -1,6 +1,12 @@ import { render } from '../tests/utils' import { Username } from './Username' -import { InvalidCountryCode, WithoutFlag } from './Username.stories' +import { + InvalidCountryCode, + UnverifiedSupporter, + Verified, + VerifiedSupporter, + WithoutFlag, +} from './Username.stories' describe('Username', () => { it('shows an unknown flag for empty value', () => { @@ -14,4 +20,19 @@ describe('Username', () => { expect(getByTestId('Username: unknown flag')).toBeInTheDocument() }) + + it('shows a verified badge when the user is verified', () => { + const { getByTestId } = render() + expect(getByTestId('Username: verified badge')).toBeInTheDocument() + }) + + it('shows a supporter badge when the user is a supporter and also unverified', () => { + const { getByTestId } = render() + expect(getByTestId('Username: supporter badge')).toBeInTheDocument() + }) + + it('shows a verified badge when the user is verified and also a supporter', () => { + const { getByTestId } = render() + expect(getByTestId('Username: verified badge')).toBeInTheDocument() + }) }) diff --git a/packages/components/src/Username/Username.tsx b/packages/components/src/Username/Username.tsx index 870533a0de..f5da5d77ed 100644 --- a/packages/components/src/Username/Username.tsx +++ b/packages/components/src/Username/Username.tsx @@ -2,6 +2,7 @@ import { Flex, Image, Text } from 'theme-ui' import flagUnknownSVG from '../../assets/icons/flag-unknown.svg' import VerifiedBadgeIcon from '../../assets/icons/icon-verified-badge.svg' +import SupporterBadgeIcon from '../../assets/icons/supporter.svg' import { FlagIconHowTos } from '../FlagIcon/FlagIcon' import { InternalLink } from '../InternalLink/InternalLink' import { twoCharacterCountryCodes } from './TwoCharacterCountryCodes' @@ -9,8 +10,7 @@ import { twoCharacterCountryCodes } from './TwoCharacterCountryCodes' import type { ThemeUIStyleObject } from 'theme-ui' import type { User } from '../types/common' -export interface Props { - isVerified: boolean +export interface IProps { user: User sx?: ThemeUIStyleObject } @@ -18,11 +18,13 @@ export interface Props { const isValidCountryCode = (str: string) => str && twoCharacterCountryCodes.has(str.toUpperCase()) -export const Username = (props: Props) => { +export const Username = ({ user, sx }: IProps) => { + const { countryCode, userName, isSupporter, isVerified } = user + return ( { background: 'softblue', color: 'bluetag', }, - ...(props.sx || {}), + ...(sx || {}), }} > { }} > - {props.user.countryCode && - isValidCountryCode(props.user.countryCode) ? ( + {countryCode && isValidCountryCode(countryCode) ? ( - + ) : ( { > )} - {props.user.userName} - {props.isVerified && ( + {userName} + {isVerified && ( )} + {isSupporter && !isVerified && ( + + )} ) diff --git a/packages/components/src/types/common.ts b/packages/components/src/types/common.ts index 2b3535bab2..fa7c2b0866 100644 --- a/packages/components/src/types/common.ts +++ b/packages/components/src/types/common.ts @@ -1,4 +1,6 @@ export type User = { - countryCode?: string | null userName: string + countryCode?: string | null + isSupporter?: boolean + isVerified?: boolean } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index f7014c8e14..ca8314dc50 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -9,6 +9,7 @@ export const fakeComment = (commentOverloads: Partial = {}) => ({ _id: faker.database.mongodbObjectId(), creatorName: faker.internet.userName(), isUserVerified: faker.datatype.boolean(), + isUserSupporter: faker.datatype.boolean(), text: faker.lorem.text(), isEditable: faker.datatype.boolean(), ...commentOverloads, diff --git a/src/models/discussion.models.tsx b/src/models/discussion.models.tsx index d7cb8dcb8e..bfd68dc894 100644 --- a/src/models/discussion.models.tsx +++ b/src/models/discussion.models.tsx @@ -7,6 +7,7 @@ import type { IQuestion } from './question.models' */ export type IDiscussionComment = IComment & { parentCommentId: string | null + isUserSupporter?: boolean } export type IDiscussion = { diff --git a/src/pages/Howto/Content/Howto/Howto.tsx b/src/pages/Howto/Content/Howto/Howto.tsx index 1b2d4e4465..a2a48c3287 100644 --- a/src/pages/Howto/Content/Howto/Howto.tsx +++ b/src/pages/Howto/Content/Howto/Howto.tsx @@ -110,7 +110,10 @@ export const Howto = observer(() => { } const hasUserVotedUseful = howtoStore.userVotedActiveHowToUseful - + const isVerified = isUserVerifiedWithStore( + howto._createdBy, + aggregationsStore, + ) return ( <> { author={{ userName: howto._createdBy, countryCode: howto.creatorCountry, - isVerified: isUserVerifiedWithStore( - howto._createdBy, - aggregationsStore, - ), + isVerified, }} >