Skip to content

Commit

Permalink
Integrate starter pack (#5092)
Browse files Browse the repository at this point in the history
* Scout game referral

* Add baseUrl

* Revert info page icon

* Missing telegram API

* Update catching errors in the page

* Update session in welcome

* Starterpack

* WIP connect to new contract

* Add starter pack overlay

* Add onboarding workflows

* Cleanup registration and loading

* Update fetcher methods

* Fix type errors

* Contract address

* Working display for starter pack

* Use nft type and fix type errors

* Cleanup form

* Fix NFT purchase

* Add builder scouted events from starter pack to auto-resolution

* Hide starter pack screen if user has already bought the max

* Include starter pack in calculation

* Add test coverage for scout point division

* Fix tests

* Delete stubs

* Cleanup code

* Cleanup

* Ensure bio displays

* Fix onboarding page

* Cleanup starterpack UI

* Fix builders carousel and UI

* Cleanup tests

* Fix the scout info page

* Fix unit tests

* Fix E2E test

* More logging

* Bump core

* Move to starter pack logic

* Fix seeder script

* Fix types

* Bump core

* Fix types

* Fix types

* Bump core

* Fix the types

* fix image hostname used for background images in nfts

* fix permissions tag

* Fix key for environment value

* Dont return NFT with 0 purchases

* Cleanup PR review

* Cleanup carousel

* Fix up carousel formatting

* Fix the paths for AWS

* Cleanup

* Bump core

---------

Co-authored-by: Valentin L <[email protected]>
Co-authored-by: mattcasey <[email protected]>
  • Loading branch information
3 people authored Dec 6, 2024
1 parent f2773e1 commit 36c999e
Show file tree
Hide file tree
Showing 86 changed files with 2,340 additions and 232 deletions.
1 change: 1 addition & 0 deletions .ebstalk.apps.env/scoutgame.env
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ S3_UPLOAD_KEY="{{pull:secretsmanager:/io.cv.app/prd/aws/s3:SecretString:access_k
NFT_ARTWORK_S3_PATH="prd"
REACT_APP_SCOUTGAME_PLATFORM=webapp
REACT_APP_BUILDER_NFT_CONTRACT_ADDRESS="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:builder_smart_contract_address}}"
REACT_APP_BUILDER_NFT_STARTER_PACK_CONTRACT_ADDRESS="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:builder_smart_contract_starter_pack_address}}"

# Scout protocol season 2
SCOUTGAME_METADATA_PATH_PREFIX="prd"
Expand Down
1 change: 1 addition & 0 deletions .ebstalk.apps.env/scoutgamecron.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ DD_API_KEY="{{pull:secretsmanager:/io.cv.app/shared/datadog:SecretString:dd_api_
BUILDER_SMART_CONTRACT_MINTER_PRIVKEY="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:builder_smart_contract_minter_privkey}}"
MIXPANEL_API_KEY="{{pull:secretsmanager:/io.cv.app/prd/mixpanel:SecretString:scoutgame_api_key}}"
REACT_APP_BUILDER_NFT_CONTRACT_ADDRESS="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:builder_smart_contract_address}}"
REACT_APP_BUILDER_NFT_STARTER_PACK_CONTRACT_ADDRESS="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:builder_smart_contract_starter_pack_address}}"
NFT_ASSETS_FOLDER="/app/public/assets"
IMAGE_HOSTING_DOMAIN="https://scoutgame.xyz" # necesssary for rendering nft share images. TODO: grab images from disk

Expand Down
5 changes: 2 additions & 3 deletions apps/scoutgame/__e2e__/onboarding.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { prisma } from '@charmverse/core/prisma-client';
import { mockScout } from '@packages/scoutgame/testing/database';
import { prettyPrint } from '@packages/utils/strings';
import { randomIntFromInterval } from '@root/lib/utils/random';

import { expect, test } from './test';
Expand Down Expand Up @@ -41,7 +42,7 @@ test.describe('Onboarding flow', () => {

await welcomePage.submitExtraDetails.click();

await page.waitForURL('**/welcome/builder');
await page.waitForURL('**/welcome/how-it-works', { waitUntil: 'load' });

// make sure we saved onboarding preferences
const user = await prisma.scout.findFirstOrThrow({
Expand All @@ -60,8 +61,6 @@ test.describe('Onboarding flow', () => {
expect(user.agreedToTermsAt).not.toBeNull();
expect(user.email).toBe(email);

await Promise.all([page.waitForURL('**/welcome/how-it-works'), welcomePage.continueButton.click()]);

await Promise.all([page.waitForURL('**/scout', { waitUntil: 'load' }), welcomePage.continueButton.click()]);

await expect(scoutPage.container).toBeVisible();
Expand Down
32 changes: 32 additions & 0 deletions apps/scoutgame/app/(general)/scout/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { prisma } from '@charmverse/core/prisma-client';
import { MAX_STARTER_PACK_PURCHASES } from '@packages/scoutgame/builderNfts/constants';
import { getStarterpackBuilders } from '@packages/scoutgame/builders/getStarterPackBuilders';
import type { BuilderInfo } from '@packages/scoutgame/builders/interfaces';
import { currentSeason } from '@packages/scoutgame/dates';
import { getUserFromSession } from '@packages/scoutgame/session/getUserFromSession';
import { safeAwaitSSRData } from '@packages/scoutgame/utils/async';

import { ScoutPage } from 'components/scout/ScoutPage';

export default async function Scout({
Expand All @@ -13,6 +21,28 @@ export default async function Scout({
const buildersLayout = (searchParams.buildersLayout as string) || 'table';
const tab = (searchParams.tab as string) || 'scouts';

const user = await getUserFromSession();

let builders: BuilderInfo[] = [];

let remainingStarterCards = MAX_STARTER_PACK_PURCHASES;

if (user?.id) {
const purchases = await prisma.nFTPurchaseEvent
.aggregate({
where: { builderNft: { nftType: 'starter_pack', season: currentSeason }, scoutId: user.id },
_sum: { tokensPurchased: true }
})
.then((res) => res._sum.tokensPurchased || 0);

remainingStarterCards = MAX_STARTER_PACK_PURCHASES - purchases;

if (purchases < MAX_STARTER_PACK_PURCHASES) {
const [_, starterPackBuilders] = await safeAwaitSSRData(getStarterpackBuilders({ season: currentSeason }));
builders = starterPackBuilders ?? [];
}
}

return (
<ScoutPage
scoutSort={scoutSort}
Expand All @@ -22,6 +52,8 @@ export default async function Scout({
scoutTab={scoutTab}
buildersLayout={buildersLayout}
tab={tab}
starterpackBuilders={builders}
remainingStarterCards={remainingStarterCards}
/>
);
}
5 changes: 0 additions & 5 deletions apps/scoutgame/app/(info)/welcome/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ export default async function Welcome() {
const user = await getUserFromSession();

if (user?.onboardedAt && user?.agreedToTermsAt && !user?.builderStatus) {
log.debug('Redirect user to github connect page from Welcome page', { userId: user?.id });
redirect('/welcome/builder');
}

if (user?.agreedToTermsAt && user?.onboardedAt) {
log.debug('Redirect user to home page from Welcome page', { userId: user.id });
redirect('/');
}
Expand Down
34 changes: 34 additions & 0 deletions apps/scoutgame/app/(info)/welcome/scout-info/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { log } from '@charmverse/core/log';
import { BuilderNftType } from '@charmverse/core/prisma-client';
import { currentSeason } from '@packages/scoutgame/dates';
import { getBuildersByFid } from '@packages/scoutgame/social/getBuildersByFid';
import type { Metadata } from 'next';
import { redirect } from 'next/navigation';

import { ScoutInfoPage } from 'components/welcome/scout-info/ScoutInfoPage';

export const dynamic = 'force-dynamic';

export const metadata: Metadata = {
other: {
robots: 'noindex'
},
title: 'Welcome'
};

export default async function ScoutInfo() {
const starterPackBuilder = await getBuildersByFid({
// piesrtasty
fids: [547807],
season: currentSeason,
nftType: BuilderNftType.starter_pack,
limit: 1
});

if (!starterPackBuilder.builders[0]) {
log.warn('No starter pack builder found, redirecting to builders you know');
redirect('/builders-you-know');
}

return <ScoutInfoPage builder={starterPackBuilder.builders[0]} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export function PartnerRewardsCarousel() {
},
'& img.swiper-pagination-bullet-active': {
opacity: 1
},
'& .swiper-pagination': {
position: 'relative',
bottom: 5
}
}}
>
Expand All @@ -55,6 +59,7 @@ export function PartnerRewardsCarousel() {
renderBullet={(index, className) =>
`<img src="/images/crypto/${partnerInfos[index].logo}" class="${className}"/>`
}
boxProps={{ width: { xs: '100%', md: '95%' } }}
>
{partnerInfos.map((partner) => (
<Link href={`/info/partner-rewards/${partner.infoPath}`} key={partner.name}>
Expand Down
2 changes: 1 addition & 1 deletion apps/scoutgame/components/layout/StickyFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SiteNavigation } from 'components/common/SiteNavigation';

export function StickyFooter() {
return (
<Box component='footer' position='sticky' bottom={0}>
<Box component='footer' position='sticky' zIndex={1} bottom={0}>
<Hidden mdUp>
<SiteNavigation />
</Hidden>
Expand Down
41 changes: 27 additions & 14 deletions apps/scoutgame/components/scout/ScoutPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import AppsIcon from '@mui/icons-material/Apps';
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
import { Box, Grid2 as Grid, Stack, Typography } from '@mui/material';
import type { BuilderInfo } from '@packages/scoutgame/builders/interfaces';
import { HeaderMessage } from '@packages/scoutgame-ui/components/common/Header/HeaderMessage';
import { TabsMenu, type TabItem } from '@packages/scoutgame-ui/components/common/Tabs/TabsMenu';
import { InfoModal } from '@packages/scoutgame-ui/components/scout/InfoModal';
import { ScoutPageTable } from '@packages/scoutgame-ui/components/scout/ScoutPageTable/ScoutPageTable';
import { StarterPackCarousel } from '@packages/scoutgame-ui/components/scout/StarterPackCarousel/StarterPackCarousel';
import { TodaysHotBuildersCarousel } from '@packages/scoutgame-ui/components/scout/TodaysHotBuildersCarousel/TodaysHotBuildersCarousel';
import { isTruthy } from '@packages/utils/types';
import Link from 'next/link';
Expand All @@ -30,7 +32,9 @@ export function ScoutPage({
builderOrder,
scoutTab,
buildersLayout,
tab
tab,
starterpackBuilders,
remainingStarterCards
}: {
scoutSort: string;
builderSort: string;
Expand All @@ -39,6 +43,8 @@ export function ScoutPage({
scoutTab: string;
buildersLayout: string;
tab: string;
starterpackBuilders: BuilderInfo[];
remainingStarterCards?: number;
}) {
const urlString = Object.entries({ tab, scoutSort, builderSort, scoutOrder, builderOrder })
.filter(([, value]) => isTruthy(value))
Expand Down Expand Up @@ -66,19 +72,26 @@ export function ScoutPage({
gap: 2
}}
>
<Typography variant='h5' color='secondary' textAlign='center' fontWeight='bold' mb={2}>
Scout today's HOT Builders!
</Typography>
<Box
sx={{
height: {
xs: 250,
md: 325
}
}}
>
<TodaysHotBuildersCarousel showPromoCards />
</Box>
{/* {starterpackBuilders.length ? (
<StarterPackCarousel builders={starterpackBuilders} remainingStarterCards={remainingStarterCards} />
) : ( */}
<>
<Typography variant='h5' color='secondary' textAlign='center' fontWeight='bold' mb={2} mt={2}>
Scout today's HOT Builders!
</Typography>
<Box
sx={{
height: {
xs: 250,
md: 325
}
}}
>
<TodaysHotBuildersCarousel showPromoCards />
</Box>
</>
{/* )} */}

<Stack
position='sticky'
top={0}
Expand Down
16 changes: 16 additions & 0 deletions apps/scoutgame/components/scout/components/StarterPackInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Box, Typography } from '@mui/material';

export function StarterPackInfo({ builders = 3 }: { builders?: number }) {
return (
<Box>
<Typography variant='h4' color='secondary' fontWeight={600} textAlign='center'>
Scout your Starter Pack
</Typography>
<Typography variant='h5' textAlign='center'>
Scout up to {builders} Builders in this Starter Set <br />
Starter Cards at 20 point (up to 95% off)
</Typography>
<Typography>* Starter Cards earn 1/10th the points of Season Cards.</Typography>
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { log } from '@charmverse/core/log';
import { yupResolver } from '@hookform/resolvers/yup';
import {
Box,
Button,
Checkbox,
FormControl,
Expand All @@ -11,8 +12,7 @@ import {
Link,
Stack,
TextField,
Typography,
Box
Typography
} from '@mui/material';
import type { SessionUser } from '@packages/scoutgame/session/interfaces';
import { FormErrors } from '@packages/scoutgame-ui/components/common/FormErrors';
Expand All @@ -28,8 +28,8 @@ import type { FieldErrors } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';

import { saveOnboardingDetailsAction } from 'lib/users/saveOnboardingDetailsAction';
import { saveOnboardingDetailsSchema } from 'lib/users/saveOnboardingDetailsSchema';
import type { SaveOnboardingDetailsFormValues } from 'lib/users/saveOnboardingDetailsSchema';
import { saveOnboardingDetailsSchema } from 'lib/users/saveOnboardingDetailsSchema';

export function ExtraDetailsForm({ user }: { user: SessionUser }) {
const router = useRouter();
Expand All @@ -41,11 +41,11 @@ export function ExtraDetailsForm({ user }: { user: SessionUser }) {
// update the user object with the new terms of service agreement
await refreshUser();
// If builder already has a status, show the spam policy page
// Otherwise show the github connection page
// Otherwise show the scout info page
if (user.builderStatus) {
router.push('/welcome/spam-policy');
} else {
router.push('/welcome/builder');
router.push('/welcome/how-it-works');
}
},
onError(err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ export function HowItWorksContent({ onClickContinue }: { onClickContinue?: React
LinkComponent={Link}
variant='contained'
onClick={onClickContinue}
href='/builders-you-know'
href='/welcome/scout-info'
data-test='continue-button'
sx={{ margin: '0 auto', display: 'flex', width: 'fit-content' }}
>
Start Playing
Next
</Button>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function HowItWorksPage() {
return (
<SinglePageLayout>
<InfoBackgroundImage />
<SinglePageWrapper bgcolor='background.default'>
<SinglePageWrapper bgcolor='background.default' height='initial'>
<HowItWorksContent />
</SinglePageWrapper>
</SinglePageLayout>
Expand Down
38 changes: 38 additions & 0 deletions apps/scoutgame/components/welcome/scout-info/ScoutInfoContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import { Box, Button, Typography } from '@mui/material';
import type { BuilderInfo } from '@packages/scoutgame/builders/interfaces';
import { BuilderCard } from '@packages/scoutgame-ui/components/common/Card/BuilderCard/BuilderCard';
import { useMdScreen } from '@packages/scoutgame-ui/hooks/useMediaScreens';
import Link from 'next/link';
import React from 'react';

export function ScoutInfoContent({ builder }: { builder: BuilderInfo }) {
const isMdScreen = useMdScreen();
const iconSize = isMdScreen ? 24 : 18;
return (
<>
<Typography color='secondary' textAlign='center' width='100%' fontWeight={700} variant='h5'>
Last Step!
</Typography>
<Box>
<Box my={2}>
<BuilderCard builder={builder} disableProfileUrl />
</Box>
<Typography my={2}>
You score points by collecting the NFTs of Builders. You can Scout your first 3 builders for 95% off their
normal price.
</Typography>
</Box>
<Button
LinkComponent={Link}
variant='contained'
href='/builders-you-know'
data-test='start-scouting-button'
sx={{ margin: '8px auto', display: 'flex', width: 'fit-content' }}
>
Start Scouting
</Button>
</>
);
}
18 changes: 18 additions & 0 deletions apps/scoutgame/components/welcome/scout-info/ScoutInfoPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { BuilderInfo } from '@packages/scoutgame/builders/interfaces';

import { SinglePageLayout } from 'components/common/Layout';
import { SinglePageWrapper } from 'components/common/SinglePageWrapper';
import { InfoBackgroundImage } from 'components/layout/InfoBackgroundImage';

import { ScoutInfoContent } from './ScoutInfoContent';

export function ScoutInfoPage({ builder }: { builder: BuilderInfo }) {
return (
<SinglePageLayout>
<InfoBackgroundImage />
<SinglePageWrapper bgcolor='background.default' maxWidth='350px' height='initial'>
<ScoutInfoContent builder={builder} />
</SinglePageWrapper>
</SinglePageLayout>
);
}
Loading

0 comments on commit 36c999e

Please sign in to comment.