Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 6 additions & 6 deletions packages/cypress/src/integration/profile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ const eventReader = MOCK_DATA.users.event_reader;
const workspacePopulated = MOCK_DATA.users.settings_workplace_new;
const workspaceEmpty = MOCK_DATA.users.settings_workplace_empty;

const betaTester = MOCK_DATA.users['beta-tester'];

describe('[Profile]', () => {
beforeEach(() => {
cy.visit('/');
Expand Down Expand Up @@ -188,10 +186,12 @@ describe('[Profile]', () => {
cy.get('[data-cy=ImpactTab]').should('not.exist');
});

it('[Cannot see profile views]', () => {
cy.signIn(subscriber.email, subscriber.password);
it('[Cannot see profile views or member history without premium tier]', () => {
const user = generateNewUserDetails();
cy.signUpNewUser(user);
cy.visit(`/u/${profile_views.username}`);
cy.get('[data-testid=profile-views-stat]').should('not.exist');
cy.get('[data-cy=MemberHistory]').should('not.exist');
});

it('should display questions count on profile tab', () => {
Expand Down Expand Up @@ -335,9 +335,9 @@ describe('[Profile]', () => {
});
});

describe('[By Beta Tester]', () => {
describe('[By Premium Tier User]', () => {
it('[Displays other information]', () => {
cy.signIn(betaTester.email, betaTester.password);
cy.signIn(subscriber.email, subscriber.password);
cy.visit(`/u/${profile_views.username}`);
cy.step('Displays view count for profile with views');
cy.get('[data-testid=profile-views-stat]').contains(/Views: \d+/);
Expand Down
18 changes: 14 additions & 4 deletions packages/cypress/src/integration/profileList.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { MOCK_DATA } from '../data';
import { generateNewUserDetails } from '../utils/TestUtils';

const subscriber = MOCK_DATA.users.subscriber;

describe('[ProfileList Modal]', () => {
const question = MOCK_DATA.questions[0];
const { slug } = question;

it('[Opens the useful voters modal]', () => {
const user = generateNewUserDetails();
cy.signUpNewUser(user);
cy.signIn(subscriber.email, subscriber.password);
cy.visit(`/questions/${slug}`);
cy.wait(1000);
cy.get('[data-cy=ContentStatistics-useful]').should('be.visible').click();
Expand All @@ -20,12 +21,21 @@ describe('[ProfileList Modal]', () => {
cy.get('[data-cy=profile-list-modal] button[icon="close"]').click();
});

it('[Cannot open the useful voters modal without premium tier]', () => {
const user = generateNewUserDetails();
cy.signUpNewUser(user);
cy.visit(`/questions/${slug}`);
cy.wait(1000);
cy.get('[data-cy=ContentStatistics-useful]').should('be.visible').click();
cy.wait(500);
cy.get('[data-cy=profile-list-modal]').should('not.exist');
});

const questionWithNoUsefulVotes = MOCK_DATA.questions[1];
const { slug: slugNoVotes } = questionWithNoUsefulVotes;

it('[Cannot open the useful voters modal when there are no useful votes]', () => {
const user = generateNewUserDetails();
cy.signUpNewUser(user);
cy.signIn(subscriber.email, subscriber.password);
cy.visit(`/questions/${slugNoVotes}`);
cy.wait(1000);
cy.get('[data-cy=ContentStatistics-useful]').should('be.visible').click();
Expand Down
5 changes: 5 additions & 0 deletions shared/mocks/data/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export const users: Users = {
displayName: 'PRO',
name: 'pro',
imageUrl: 'svg',
premiumTier: 1,
},
],
createdAt: new Date('2020-01-07T12:14:50.354Z'),
Expand All @@ -132,6 +133,7 @@ export const users: Users = {
displayName: 'PRO',
name: 'pro',
imageUrl: 'svg',
premiumTier: 1,
},
],
createdAt: new Date('2020-01-07T12:14:30.030Z'),
Expand All @@ -151,6 +153,7 @@ export const users: Users = {
displayName: 'PRO',
name: 'pro',
imageUrl: 'svg',
premiumTier: 1,
},
],
createdAt: new Date('2020-01-07T12:15:42.218Z'),
Expand All @@ -173,6 +176,7 @@ export const users: Users = {
displayName: 'PRO',
name: 'pro',
imageUrl: 'svg',
premiumTier: 1,
},
],
createdAt: new Date('2020-01-07T12:14:15.081Z'),
Expand Down Expand Up @@ -227,6 +231,7 @@ export const users: Users = {
displayName: 'PRO',
name: 'pro',
imageUrl: 'svg',
premiumTier: 1,
},
],
createdAt: new Date('2020-01-07T12:14:15.081Z'),
Expand Down
4 changes: 4 additions & 0 deletions shared/models/profileBadge.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export enum PremiumTier {
ONE = 1,
}

export class DBProfileBadge {
id: number;
name: string;
Expand Down
10 changes: 9 additions & 1 deletion src/common/PremiumTierWrapper.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { render } from '@testing-library/react';
import { type Profile, UserRole } from 'oa-shared';
import { ProfileStoreProvider } from 'src/stores/Profile/profile.store';
import { FactoryUser } from 'src/test/factories/User';
import { describe, expect, it, vi } from 'vitest';

import { PremiumTierWrapper } from './PremiumTierWrapper';
import { PremiumTierWrapper, userHasPremiumTier } from './PremiumTierWrapper';

vi.mock('src/stores/Profile/profile.store', () => ({
useProfileStore: () => ({
Expand Down Expand Up @@ -62,3 +63,10 @@ describe('PremiumTierWrapper', () => {
expect(getByText('Test Content')).toBeTruthy();
});
});

describe('userHasPremiumTier', () => {
it('returns true for admin users regardless of tier', () => {
const adminUser = FactoryUser({ roles: [UserRole.ADMIN], badges: [] }) as Profile;
expect(userHasPremiumTier(adminUser, 1)).toBe(true);
});
});
15 changes: 12 additions & 3 deletions src/common/PremiumTierWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { observer } from 'mobx-react';
import { UserRole } from 'oa-shared';
import { useProfileStore } from 'src/stores/Profile/profile.store';

import type { Profile } from 'oa-shared';
Expand All @@ -19,14 +20,22 @@ export const PremiumTierWrapper = observer((props: IProps) => {
return <>{hasRequiredTier ? children : fallback || null}</>;
});

export const userHasPremiumTier = (user?: Profile | null, tierRequired?: number): boolean => {
export const userHasPremiumTier = (profile?: Profile | null, tierRequired?: number): boolean => {
if (!tierRequired || tierRequired <= 0) {
return true;
}

if (!user?.badges || user.badges.length === 0) {
if (!profile) {
return false;
}

return user.badges.some((badge) => badge.premiumTier === tierRequired);
if (profile.roles?.includes(UserRole.ADMIN)) {
return true;
}

if (!profile.badges || profile.badges.length === 0) {
return false;
}

return profile.badges.some((badge) => badge.premiumTier === tierRequired);
};
10 changes: 8 additions & 2 deletions src/pages/Library/Content/Page/LibraryDescription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import {
TagList,
UsefulStatsButton,
} from 'oa-components';
import { DifficultyLevelRecord } from 'oa-shared';
import { DifficultyLevelRecord, PremiumTier } from 'oa-shared';
import { ClientOnly } from 'remix-utils/client-only';
import DifficultyLevel from 'src/assets/icons/icon-difficulty-level.svg';
import TimeNeeded from 'src/assets/icons/icon-time-needed.svg';
import { DownloadWrapper } from 'src/common/DownloadWrapper';
import { userHasPremiumTier } from 'src/common/PremiumTierWrapper';
import { buildStatisticsLabel, capitalizeFirstLetter, hasAdminRights } from 'src/utils/helpers';
import { createUsefulStatistic } from 'src/utils/statistics';
import { Alert, Box, Card, Divider, Flex, Heading, Image, Text } from 'theme-ui';
Expand Down Expand Up @@ -243,7 +244,12 @@ export const LibraryDescription = ({
}),
stat: item.totalViews,
},
createUsefulStatistic('projects', item.id, votedUsefulCount || 0),
createUsefulStatistic(
'projects',
item.id,
votedUsefulCount || 0,
userHasPremiumTier(loggedInUser, PremiumTier.ONE),
),
{
icon: 'comment-outline',
label: buildStatisticsLabel({
Expand Down
9 changes: 8 additions & 1 deletion src/pages/Question/QuestionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
TagList,
UsefulStatsButton,
} from 'oa-components';
import { PremiumTier } from 'oa-shared';
import { ClientOnly } from 'remix-utils/client-only';
import { userHasPremiumTier } from 'src/common/PremiumTierWrapper';
import { Breadcrumbs } from 'src/pages/common/Breadcrumbs/Breadcrumbs';
import { usefulService } from 'src/services/usefulService';
import { useProfileStore } from 'src/stores/Profile/profile.store';
Expand Down Expand Up @@ -165,7 +167,12 @@ export const QuestionPage = observer(({ question }: IProps) => {
}),
stat: subscribersCount,
},
createUsefulStatistic('questions', question.id, usefulCount),
createUsefulStatistic(
'questions',
question.id,
usefulCount,
userHasPremiumTier(activeUser, PremiumTier.ONE),
),
{
icon: 'comment-outline',
label: buildStatisticsLabel({
Expand Down
10 changes: 8 additions & 2 deletions src/pages/Research/Content/ResearchDescription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import {
UsefulStatsButton,
Username,
} from 'oa-components';
import { type Profile, type ResearchItem, ResearchStatusRecord } from 'oa-shared';
import { PremiumTier, type Profile, type ResearchItem, ResearchStatusRecord } from 'oa-shared';
// eslint-disable-next-line import/no-unresolved
import { ClientOnly } from 'remix-utils/client-only';
import { userHasPremiumTier } from 'src/common/PremiumTierWrapper';
import { DraftTag } from 'src/pages/common/Drafts/DraftTag';
import { buildStatisticsLabel } from 'src/utils/helpers';
import { createUsefulStatistic } from 'src/utils/statistics';
Expand Down Expand Up @@ -191,7 +192,12 @@ const ResearchDescription = (props: IProps) => {
}),
stat: subscribersCount || 0,
},
createUsefulStatistic('research', research.id, votedUsefulCount || 0),
createUsefulStatistic(
'research',
research.id,
votedUsefulCount || 0,
userHasPremiumTier(props.activeUser, PremiumTier.ONE),
),
{
icon: 'comment-outline',
label: buildStatisticsLabel({
Expand Down
10 changes: 5 additions & 5 deletions src/pages/User/content/ProfileDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useEffect, useMemo, useState } from 'react';
import { Button, UserStatistics, VisitorModal } from 'oa-components';
import { UserRole } from 'oa-shared';
import { PremiumTier } from 'oa-shared';
import { trackEvent } from 'src/common/Analytics';
import { AuthWrapper } from 'src/common/AuthWrapper';
import { DonationRequestModalContainer } from 'src/common/DonationRequestModalContainer';
import { PremiumTierWrapper } from 'src/common/PremiumTierWrapper';
import { ProfileTags } from 'src/pages/common/ProfileTags';
import { mapPinService } from 'src/pages/Maps/map.service';
import { Box, Divider, Flex, Paragraph } from 'theme-ui';
Expand Down Expand Up @@ -128,8 +128,8 @@ export const ProfileDetails = ({ docs, profile, selectTab }: IProps) => {
</Button>
</>
)}
<AuthWrapper
roleRequired={UserRole.BETA_TESTER}
<PremiumTierWrapper
tierRequired={PremiumTier.ONE}
fallback={
<UserStatistics
profile={profile}
Expand All @@ -151,7 +151,7 @@ export const ProfileDetails = ({ docs, profile, selectTab }: IProps) => {
questionCount={docs?.questions.length || 0}
showViews
/>
</AuthWrapper>
</PremiumTierWrapper>
</Flex>
</Flex>
</Box>
Expand Down
8 changes: 4 additions & 4 deletions src/pages/User/content/UserProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState } from 'react';
import { useLocation } from 'react-router';
import { MemberBadge, MemberHistory, Tab, TabPanel, Tabs, TabsList } from 'oa-components';
import { UserRole } from 'oa-shared';
import { AuthWrapper } from 'src/common/AuthWrapper';
import { PremiumTier } from 'oa-shared';
import { PremiumTierWrapper } from 'src/common/PremiumTierWrapper';
import { isPreciousPlastic } from 'src/config/config';
import { isContactable } from 'src/utils/helpers';
import { isProfileComplete } from 'src/utils/isProfileComplete';
Expand Down Expand Up @@ -123,9 +123,9 @@ export const UserProfile = ({ docs, isViewingOwnProfile, user }: IProps) => {
)}
</Tabs>
</Box>
<AuthWrapper roleRequired={UserRole.BETA_TESTER}>
<PremiumTierWrapper tierRequired={PremiumTier.ONE}>
<MemberHistory memberSince={user.createdAt} lastActive={user.lastActive} />
</AuthWrapper>
</PremiumTierWrapper>
</Flex>
</Card>
</Flex>
Expand Down
3 changes: 2 additions & 1 deletion src/routes/api.profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const loader = async ({ request }) => {
name,
display_name,
image_url,
action_url
action_url,
premium_tier
)
),
type:profile_types(
Expand Down
15 changes: 8 additions & 7 deletions src/services/profileService.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type { DBAuthorVotes, DBMedia, DBProfile, ProfileFormData, ProfileType }
export class ProfileServiceServer {
constructor(private client: SupabaseClient) {}

// TODO: add premium_tier to profile_badges selection once migration is applied
async getByAuthId(id: string): Promise<DBProfile | null> {
const { data } = await this.client
.from('profiles')
Expand All @@ -21,7 +20,8 @@ export class ProfileServiceServer {
name,
display_name,
image_url,
action_url
action_url,
premium_tier
)
),
type:profile_types(
Expand Down Expand Up @@ -75,7 +75,6 @@ export class ProfileServiceServer {
return data as DBProfile;
}

// TODO: add premium_tier to profile_badges selection once migration is applied
async getUsersByUsername(usernames: string[]): Promise<DBProfile[] | null> {
const { data } = await this.client
.from('profiles')
Expand All @@ -98,7 +97,8 @@ export class ProfileServiceServer {
name,
display_name,
image_url,
action_url
action_url,
premium_tier
)
),
type:profile_types(
Expand Down Expand Up @@ -132,7 +132,8 @@ export class ProfileServiceServer {
name,
display_name,
image_url,
action_url
action_url,
premium_tier
)
),
tags:profile_tags_relations(
Expand Down Expand Up @@ -274,10 +275,10 @@ export class ProfileServiceServer {
name,
display_name,
image_url,
action_url
action_url,
premium_tier
)
)`,
// TODO: add premium_tier to profile_badges selection once migration is applied to CI test database
)
.single();
if (error) {
Expand Down
Loading