Skip to content

Commit

Permalink
prize wall: faster interval, fix background, update text, public anno…
Browse files Browse the repository at this point in the history
…uncement
  • Loading branch information
GGonryun committed Aug 23, 2024
1 parent 6ae6d8e commit 0b759e6
Show file tree
Hide file tree
Showing 15 changed files with 137 additions and 47 deletions.
3 changes: 3 additions & 0 deletions libs/api/discord/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export type DiscordMessageInput = {
description?: string;
color?: number;
url?: string;
image?: {
url: string;
};
fields?: {
name: string;
value: string;
Expand Down
15 changes: 11 additions & 4 deletions libs/services/prizes/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import {
import { InventoryService } from '@worksheets/services/inventory';
import { shuffle } from '@worksheets/util/arrays';
import { randomFloatBetween } from '@worksheets/util/numbers';
import {
MAX_DAILY_PRIZES,
MAX_PRIZE_DISCOUNT,
MIN_PRIZE_DISCOUNT,
} from '@worksheets/util/settings';
import { calculatePrizePrice, UserSchema } from '@worksheets/util/types';

export class PrizeService {
Expand Down Expand Up @@ -139,14 +144,16 @@ export class PrizeService {
code: prize.code.content,
type: prize.code.type,
url: prize.code.sourceUrl,
userId: user.id,
imageUrl: prize.code.imageUrl,
user: {
id: user.id,
username: user.username,
},
cost,
};
}

async shuffle() {
const MAX_DAILY_PRIZES = 5;

const prizes = await this.db.prize.findMany({
where: {
status: {
Expand Down Expand Up @@ -174,7 +181,7 @@ export class PrizeService {
id: prize.id,
},
data: {
discount: randomFloatBetween(0.1, 0.5),
discount: randomFloatBetween(MIN_PRIZE_DISCOUNT, MAX_PRIZE_DISCOUNT),
status: PrizeStatus.ACTIVE,
},
});
Expand Down
17 changes: 12 additions & 5 deletions libs/services/templates/src/lib/discord-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,22 @@ export class DiscordTemplates {
opts: ExtractTemplatePayload<'prize-purchased'>
): DiscordMessageInput {
return {
content: `A user has purchased a prize: ${opts.prizeId}`,
content: `🔑🎁 PRIZE UNLOCKED 🎁🔑`,
embeds: [
{
title: `User ID: ${opts.userId}`,
description: `The user
(${opts.userId}) has purchased the prize ${opts.name} for ${opts.cost} tokens.`,
title: `${opts.name}${opts.cost} tokens`,
url: routes.prizes.url(),
description: `The user ${opts.user.username} has purchased a ${
opts.name
} ${opts.type === 'STEAM' ? 'Steam Key' : 'Unknown'} for ${
opts.cost
} tokens!`,
image: {
url: opts.imageUrl,
},
},
],
channel: 'admin',
channel: 'notification',
};
}
static userReport(
Expand Down
2 changes: 1 addition & 1 deletion libs/services/templates/src/lib/push-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class PushTemplates {
opts: ExtractTemplatePayload<'prize-purchased'>
): PushNotifyInput {
return {
userIds: [opts.userId],
userIds: [opts.user.id],
type: 'PRIZE',
text: `You have unlocked the prize <a href="${opts.url}">${opts.name} ${
opts.type === 'STEAM' ? 'Steam Key' : ''
Expand Down
12 changes: 10 additions & 2 deletions libs/ui/components/help/src/lib/help-prize-wall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { Box, Link, Typography } from '@mui/material';
import { routes } from '@worksheets/routes';
import { ListItem, OrderedList } from '@worksheets/ui-core';
import { InventoryPanels } from '@worksheets/util/enums';
import { toPercentage } from '@worksheets/util/numbers';
import {
MAX_PRIZE_DISCOUNT,
MIN_PRIZE_DISCOUNT,
PRIZE_WALL_INTERVAL,
} from '@worksheets/util/settings';
import { QuestionAnswer } from '@worksheets/util/types';

import { HelpfulLinks } from './helpful-links';
Expand All @@ -19,8 +25,10 @@ export const helpPrizeWall: QuestionAnswer[] = [
prize.
<br />
<br />
Every 24 hours, a new set of prizes will be available for redemption
with varying discounts. Be sure to check back daily to see what's new!
Every {PRIZE_WALL_INTERVAL} hours, a new set of prizes will be available
for redemption with varying discounts. Discounts range from{' '}
{toPercentage(MIN_PRIZE_DISCOUNT)} to {toPercentage(MAX_PRIZE_DISCOUNT)}
. Be sure to check back often to see what's new!
<br />
<br />
Players are only allowed to redeem one prize per day. If you have any
Expand Down
2 changes: 1 addition & 1 deletion libs/ui/components/modals/src/lib/info-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const InfoModal = forwardRef<
<Modal
open={open}
onClose={onClose}
background={background}
sx={{
background,
maxHeight: '90vh',
maxWidth: `min(${maxWidth}px, 95vw)`,
}}
Expand Down
8 changes: 6 additions & 2 deletions libs/ui/components/modals/src/lib/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ import {
ModalProps as MuiModalProps,
Paper,
} from '@mui/material';
import { Theme } from '@worksheets/ui/theme';
import { forwardRef } from 'react';

export type ModalProps = Pick<
MuiModalProps,
'open' | 'onClose' | 'children' | 'sx'
>;
> & {
background?: (theme: Theme) => string;
};

export type OnClose = MuiModalProps['onClose'];

// eslint-disable-next-line @typescript-eslint/ban-types
export type ModalWrapper<T = {}> = Omit<ModalProps, 'children'> & T;

export const Modal = forwardRef<HTMLDivElement, ModalProps>(
({ children, sx, ...props }, ref) => {
({ children, sx, background, ...props }, ref) => {
return (
<MuiModal {...props}>
<Box
Expand All @@ -38,6 +41,7 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
<Paper
elevation={6}
sx={{
background,
borderRadius: (theme) => theme.shape.borderRadius,
display: 'flex',
position: 'relative',
Expand Down
4 changes: 2 additions & 2 deletions libs/ui/pages/prizes/src/lib/deal-countdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { Box, LinearProgress, Typography } from '@mui/material';
import { Row } from '@worksheets/ui/components/flex';
import { printShortDateTime } from '@worksheets/util/time';

import { useTimeToUtcMidnight } from './hooks/use-time-to-midnight';
import { useNextPrizeWallInterval } from './hooks/use-time-to-interval';

export const DealCountdown = () => {
const timeRemaining = useTimeToUtcMidnight();
const timeRemaining = useNextPrizeWallInterval();
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
return (
<Box>
Expand Down
32 changes: 32 additions & 0 deletions libs/ui/pages/prizes/src/lib/hooks/use-time-to-interval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useInterval } from '@worksheets/ui-core';
import { PRIZE_WALL_INTERVAL } from '@worksheets/util/settings';
import {
durationToString,
getNextIntervalAligned,
millisecondsAsDuration,
printShortDateTime,
timeUntil,
} from '@worksheets/util/time';
import { useEffect, useState } from 'react';

export const useNextPrizeWallInterval = () => {
const [timeRemaining, setTimeRemaining] = useState<number>(0);

useEffect(() => {
const next = getNextIntervalAligned(PRIZE_WALL_INTERVAL);
setTimeRemaining(timeUntil(next.getTime()));
}, []);

useInterval(() => {
const next = getNextIntervalAligned(PRIZE_WALL_INTERVAL);
setTimeRemaining(timeUntil(next.getTime()));
}, 1000);

const string = durationToString(millisecondsAsDuration(timeRemaining));

return {
string,
number: timeRemaining,
utc: printShortDateTime(new Date(Date.now() + timeRemaining)),
};
};
25 changes: 0 additions & 25 deletions libs/ui/pages/prizes/src/lib/hooks/use-time-to-midnight.ts

This file was deleted.

10 changes: 7 additions & 3 deletions libs/ui/pages/prizes/src/lib/reset-notice.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { Alarm } from '@mui/icons-material';
import { Typography } from '@mui/material';
import { Column, Row } from '@worksheets/ui/components/flex';
import { PRIZE_WALL_INTERVAL } from '@worksheets/util/settings';
import React from 'react';

import { useTimeToUtcMidnight } from './hooks/use-time-to-midnight';
import { useNextPrizeWallInterval } from './hooks/use-time-to-interval';

export const ResetNotice: React.FC = () => {
const timeRemaining = useTimeToUtcMidnight();
const timeRemaining = useNextPrizeWallInterval();

return (
<Row gap={1.5}>
<Alarm fontSize="large" />
<Column>
<Typography typography="body1" fontWeight={700}>
New Deals Every Day!
New Deals Every {PRIZE_WALL_INTERVAL} Hours!
</Typography>
<Typography typography="body3" fontWeight={500}>
Next Update: {timeRemaining.string}
</Typography>
<Typography typography="body3" fontWeight={500}>
@ {timeRemaining.utc}
</Typography>
</Column>
</Row>
);
Expand Down
5 changes: 5 additions & 0 deletions libs/util/settings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ export const SESSION_AGE_MINUTES = 30;

export const DAMAGE_PER_ATTACK = 1;
export const STRIKES_PER_ATTACK = 1;

export const PRIZE_WALL_INTERVAL = 6; // TODO: load from vercel cron job json file.
export const MAX_DAILY_PRIZES = 6;
export const MAX_PRIZE_DISCOUNT = 0.5;
export const MIN_PRIZE_DISCOUNT = 0.1;
41 changes: 41 additions & 0 deletions libs/util/time/src/lib/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,47 @@ export const nextUtcMidnight = (now = new Date()): Date => {
return nextMidnight;
};

/**
* Calculates the next interval time based on a specified interval in hours,
* starting from 12 AM UTC.
*
* @param currentTime - The current Date and time.
* @param intervalHours - The interval duration in hours (e.g., 12 for 12-hour intervals).
* @returns A Date object representing the next interval start time in UTC.
*/
export function getNextIntervalAligned(
intervalHours: number,
currentTime: Date = new Date()
): Date {
// Calculate the total milliseconds since the Unix epoch
const currentTimeMs: number = currentTime.getTime();

// Calculate the interval duration in milliseconds
const intervalMs: number = intervalHours * 60 * 60 * 1000;

// Calculate the Unix timestamp for 12 AM UTC of the current day
const startOfDay: Date = new Date(
Date.UTC(
currentTime.getUTCFullYear(),
currentTime.getUTCMonth(),
currentTime.getUTCDate()
)
);
const startOfDayMs: number = startOfDay.getTime();

// Calculate the elapsed time since the start of the day
const elapsedMs: number = currentTimeMs - startOfDayMs;

// Calculate the number of complete intervals that have passed
const intervalsPassed: number = Math.floor(elapsedMs / intervalMs);

// Calculate the timestamp for the next interval
const nextIntervalMs: number =
startOfDayMs + (intervalsPassed + 1) * intervalMs;

return new Date(nextIntervalMs);
}

export const lastUtcMidnight = (now = new Date()): Date => {
const lastMidnight = new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
Expand Down
6 changes: 5 additions & 1 deletion libs/util/types/src/lib/prizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ export const purchaseResultSchema = z.object({
type: z.nativeEnum(ActivationCodeType),
url: z.string(),
code: z.string(),
userId: z.string(),
imageUrl: z.string(),
cost: z.number(),
user: z.object({
id: z.string(),
username: z.string(),
}),
});

export type PurchaseResultSchema = z.infer<typeof purchaseResultSchema>;
Expand Down
2 changes: 1 addition & 1 deletion vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
{
"path": "/api/cron/shuffle-prizes",
"schedule": "0 0 * * *"
"schedule": "0 */6 * * *"
},
{
"path": "/api/cron/spawn-raffle",
Expand Down

0 comments on commit 0b759e6

Please sign in to comment.