Skip to content

Commit

Permalink
prizes: implement the prize-wall
Browse files Browse the repository at this point in the history
  • Loading branch information
GGonryun committed Aug 11, 2024
1 parent c0b34e6 commit 89d7dde
Show file tree
Hide file tree
Showing 99 changed files with 2,466 additions and 321 deletions.
2 changes: 0 additions & 2 deletions apps/blog/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import './styles.css';

import * as FullStory from '@fullstory/browser';
import { CssBaseline, ThemeProvider } from '@mui/material';
import { AdSenseScript } from '@worksheets/ui/components/advertisements';
import { COOKIE_DOMAIN, IS_PRODUCTION } from '@worksheets/ui/env';
import { BlogLayoutContainer } from '@worksheets/ui/layout';
import theme from '@worksheets/ui/theme';
Expand All @@ -27,7 +26,6 @@ function CustomApp({
<>
<DefaultSeo {...defaultSeo} />
<CssBaseline />
<AdSenseScript />
<Head>
<meta
name="viewport"
Expand Down
2 changes: 2 additions & 0 deletions apps/blog/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AdSenseScript } from '@worksheets/ui/components/advertisements';
import {
DocumentInitialProps,
Head,
Expand Down Expand Up @@ -55,6 +56,7 @@ export default function MyDocument(props: DocumentProps) {
></script>
</Head>
<body>
<AdSenseScript />
<Main />
<NextScript />
</body>
Expand Down
4 changes: 4 additions & 0 deletions apps/charity/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ const nextConfig = {
protocol: 'https',
hostname: 'picsum.photos',
},
{
protocol: 'https',
hostname: 'shared.akamai.steamstatic.com',
},
],
},
nx: {
Expand Down
9 changes: 9 additions & 0 deletions apps/charity/pages/api/cron/shuffle-prizes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { prisma } from '@worksheets/prisma';
import { PrizeService } from '@worksheets/services/prizes';
import { createCronJob } from '@worksheets/util/cron';
const shufflePrizes = async () => {
const service = new PrizeService(prisma);
await service.shuffle();
};

export default createCronJob(shufflePrizes);
29 changes: 3 additions & 26 deletions apps/charity/pages/api/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
monstersSeo,
playSeo,
privacySeo,
prizesSeo,
rafflesSeo,
signUpSeo,
termsSeo,
Expand Down Expand Up @@ -58,6 +59,7 @@ const addBasicPages = () => {
rafflesSeo.canonical,
vipSeo.canonical,
bossBattlesSeo.canonical,
prizesSeo.canonical,
helpCenterSeo.canonical,
helpAccountsSeo.canonical,
helpFaqSeo.canonical,
Expand Down Expand Up @@ -147,41 +149,17 @@ const getDevelopers = async () => {
.join('');
};

const getMonsters = async () => {
const monsters = await prisma.mob.findMany({
select: {
id: true,
updatedAt: true,
},
});

return monsters
.map(
(monster) =>
`<url><loc>${routes.monster.url({
params: {
monsterId: monster.id,
},
})}</loc><lastmod>${
// use w3c date format yyyy-mm-dd
printShortDate(monster.updatedAt, 'fr-CA')
}</lastmod><priority>0.3</priority></url>`
)
.join('');
};

const handler: NextApiHandler = async (req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/xml');

// Instructing the Vercel edge to cache the file for 7 days in seconds
res.setHeader('Cache-control', 'stale-while-revalidate, s-maxage=604800');

const [games, tags, developers, monsters] = await Promise.all([
const [games, tags, developers] = await Promise.all([
getGames(),
getTags(),
getDevelopers(),
getMonsters(),
]);

// generate sitemap here
Expand All @@ -192,7 +170,6 @@ const handler: NextApiHandler = async (req, res) => {
${games}
${tags}
${developers}
${monsters}
</urlset>`;

res.end(xml);
Expand Down
28 changes: 28 additions & 0 deletions apps/charity/pages/prizes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { AppLayoutContainer } from '@worksheets/ui/layout';
import { DynamicPrizesPage } from '@worksheets/ui/pages/prizes';
import { NextPageWithLayout } from '@worksheets/util-next';
import { GetStaticProps } from 'next';
import { NextSeo, NextSeoProps } from 'next-seo';

import { prizesSeo } from '../../util/seo';

type Props = {
seo: NextSeoProps;
};

const Page: NextPageWithLayout<Props> = ({ seo }) => (
<>
<NextSeo {...seo} />
<DynamicPrizesPage />
</>
);

export const getStaticProps = (async () => ({
props: { seo: prizesSeo },
})) satisfies GetStaticProps<Props>;

Page.getLayout = (page) => {
return <AppLayoutContainer>{page}</AppLayoutContainer>;
};

export default Page;
25 changes: 20 additions & 5 deletions apps/charity/util/seo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DeveloperSchema,
ItemSchema,
MonsterSchema,
PrizeSchema,
RaffleSchema,
SerializableGameSchema,
TagSchema,
Expand Down Expand Up @@ -475,35 +476,35 @@ export const auctionsSeo = createSeo({
path: routes.auctions.path(),
title: 'Auctions',
description:
'Bid on digital prizes and win free games. Every token you spend is a donation towards charity. Win tokens by playing browser games and completing tasks.',
'Bid on digital prizes and win free games. Win tokens by playing browser games and completing tasks.',
});

export const monstersSeo = createSeo({
path: routes.monsters.path(),
title: 'Monsters',
description:
'Defeat monsters and earn rewards. Every token you spend is a donation towards charity. Win tokens by playing browser games.',
'Defeat monsters and earn rewards. Win tokens by playing browser games.',
});

export const monsterSeo = (monster: MonsterSchema): NextSeoProps =>
createSeo({
path: routes.monster.path({ params: { monsterId: monster.id } }),
title: `${monster.name} | Monster`,
description: `Defeat ${monster.name} and earn rewards. Every token you spend is a donation towards charity. Win prizes by playing browser games.`,
description: `Defeat ${monster.name} and earn rewards. Win prizes by playing browser games.`,
});

export const itemsSeo = createSeo({
path: routes.items.path(),
title: 'Items',
description:
'View all items available on Charity Games. Every token you spend is a donation towards charity. Win tokens by playing browser games.',
'View all items available on Charity Games. Win tokens by playing browser games.',
});

export const itemSeo = (item: ItemSchema): NextSeoProps =>
createSeo({
path: routes.item.path({ params: { itemId: item.id } }),
title: `${item.name} | Item`,
description: `View ${item.name} and learn how to use it. Every token you spend is a donation towards charity. Win prizes by playing browser games.`,
description: `View ${item.name} and learn how to use it. Win prizes by playing browser games.`,
});

export const userSeo = (userId: string): NextSeoProps => {
Expand All @@ -521,3 +522,17 @@ export const connectSeo = (providerId: string): NextSeoProps => {
description: `Connect an external account to Charity Games. Turn your games into donations. Help us make a difference.`,
});
};

export const prizesSeo = createSeo({
path: routes.prizes.path(),
title: 'Prizes',
description:
'Trade your tokens for digital and physical prizes. Win prizes by playing browser games.',
});

export const prizeSeo = (item: PrizeSchema): NextSeoProps =>
createSeo({
path: routes.prizes.prize.path({ params: { prizeId: item.id } }),
title: `${item.name} | Prize`,
description: `Trade your tokens for ${item.name}. Win prizes by playing browser games.`,
});
50 changes: 40 additions & 10 deletions libs/prisma/src/schemas/main.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ model User {
gameScores GameScore[]
experience UserExperience?
achievement PlayerAchievement[]
prizes Prize[]
}

model Account {
Expand Down Expand Up @@ -542,22 +543,31 @@ model RaffleParticipation {
@@unique([userId, raffleId])
}

enum ActivationCodeType {
STEAM
}

model ActivationCode {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// the redemption/secret code for the prize.
content String
accessedAt DateTime?
// more data about the code
sourceUrl String
type ActivationCodeType @default(STEAM)
// ** Code Overrides **
// even though the item has a name, the code itself can have a different name.
// for example the source item could be "Random Steam Key" and the name would be "Steam Key for Game X"
name String
itemId String
item Item @relation(fields: [itemId], references: [id])
userId String?
user User? @relation(fields: [userId], references: [id])
name String
imageUrl String @default("")
sourceUrl String
// ** Relationships **
itemId String?
item Item? @relation(fields: [itemId], references: [id])
prize Prize?
userId String?
user User? @relation(fields: [userId], references: [id])
}

// these codes are used to redeem prizes on the site as opposed to activation codes which are used to redeem external prizes.
Expand Down Expand Up @@ -601,6 +611,7 @@ model ScheduledEmail {
enum NotificationType {
SYSTEM
FRIEND
PRIZE
RAFFLE
QUEST
INVENTORY
Expand Down Expand Up @@ -1063,3 +1074,22 @@ model PlayerAchievement {
@@unique([achievementId, userId])
}

enum PrizeStatus {
PENDING
ACTIVE
REDEEMED
EXPIRED
}

model Prize {
id Int @id @default(autoincrement())
status PrizeStatus @default(PENDING)
discount Float @default(0) // percentage discount
purchasedAt DateTime?
userId String?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
value Int
codeId String @unique
code ActivationCode @relation(fields: [codeId], references: [id])
}
8 changes: 8 additions & 0 deletions libs/routes/src/lib/routes/app-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ export const routes = {
}),
},
}),
prizes: createRoute({
path: '/prizes',
routes: {
prize: createRoute({
path: '/prizes/[prizeId]',
}),
},
}),
connect: createRoute({
path: '/connect/[providerId]',
query: ConnectIntegrationQueryParams,
Expand Down
8 changes: 8 additions & 0 deletions libs/services/notifications/src/destinations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ const achievementUnlockedTemplates: TemplateBuilder<'achievement-unlocked'> = (
push: PushTemplates.achievementUnlocked(payload),
});

const prizePurchasedTemplates: TemplateBuilder<'prize-purchased'> = (
payload
) => ({
discord: DiscordTemplates.prizePurchased(payload),
push: PushTemplates.prizePurchased(payload),
});

export const destinations: Record<NotificationTemplateType, TemplateBuilder> = {
'new-game': newGameTemplates,
'new-raffle': newRaffleTemplates,
Expand All @@ -176,4 +183,5 @@ export const destinations: Record<NotificationTemplateType, TemplateBuilder> = {
'share-gift': shareGiftTemplates,
'won-leaderboard': wonLeaderboardTemplates,
'achievement-unlocked': achievementUnlockedTemplates,
'prize-purchased': prizePurchasedTemplates,
};
25 changes: 25 additions & 0 deletions libs/services/prizes/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}
11 changes: 11 additions & 0 deletions libs/services/prizes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# services-prizes

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build services-prizes` to build the library.

## Running unit tests

Run `nx test services-prizes` to execute the unit tests via [Jest](https://jestjs.io).
11 changes: 11 additions & 0 deletions libs/services/prizes/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'services-prizes',
preset: '../../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../../coverage/libs/services/prizes',
};
11 changes: 11 additions & 0 deletions libs/services/prizes/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "services/prizes",
"version": "0.0.1",
"dependencies": {
"tslib": "^2.3.0"
},
"type": "commonjs",
"main": "./src/index.js",
"typings": "./src/index.d.ts",
"private": true
}
Loading

0 comments on commit 89d7dde

Please sign in to comment.