diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index ac48949..fe02681 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -2,13 +2,15 @@ /* tslint:disable */ /** - * Mock Service Worker (0.47.4). + * Mock Service Worker. * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. */ -const INTEGRITY_CHECKSUM = 'b3066ef78c2f9090b4ce87e874965995' +const PACKAGE_VERSION = '2.4.5' +const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() self.addEventListener('install', function () { @@ -47,7 +49,10 @@ self.addEventListener('message', async function (event) { case 'INTEGRITY_CHECK_REQUEST': { sendToClient(client, { type: 'INTEGRITY_CHECK_RESPONSE', - payload: INTEGRITY_CHECKSUM, + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, }) break } @@ -86,12 +91,6 @@ self.addEventListener('message', async function (event) { self.addEventListener('fetch', function (event) { const { request } = event - const accept = request.headers.get('accept') || '' - - // Bypass server-sent events. - if (accept.includes('text/event-stream')) { - return - } // Bypass navigation requests. if (request.mode === 'navigate') { @@ -100,10 +99,7 @@ self.addEventListener('fetch', function (event) { // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. - if ( - request.cache === 'only-if-cached' && - request.mode !== 'same-origin' - ) { + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { return } @@ -115,29 +111,8 @@ self.addEventListener('fetch', function (event) { } // Generate unique request ID. - const requestId = Math.random().toString(16).slice(2) - - event.respondWith( - handleRequest(event, requestId).catch((error) => { - if (error.name === 'NetworkError') { - console.warn( - '[MSW] Successfully emulated a network error for the "%s %s" request.', - request.method, - request.url, - ) - return - } - - // At this point, any exception indicates an issue with the original request/response. - console.error( - `\ -[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, - request.method, - request.url, - `${error.name}: ${error.message}`, - ) - }), - ) + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) }) async function handleRequest(event, requestId) { @@ -149,23 +124,24 @@ async function handleRequest(event, requestId) { // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { ;(async function () { - const clonedResponse = response.clone() - sendToClient(client, { - type: 'RESPONSE', - payload: { - requestId, - type: clonedResponse.type, - ok: clonedResponse.ok, - status: clonedResponse.status, - statusText: clonedResponse.statusText, - body: - clonedResponse.body === null - ? null - : await clonedResponse.text(), - headers: Object.fromEntries(clonedResponse.headers.entries()), - redirected: clonedResponse.redirected, + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, }, - }) + [responseClone.body], + ) })() } @@ -179,7 +155,7 @@ async function handleRequest(event, requestId) { async function resolveMainClient(event) { const client = await self.clients.get(event.clientId) - if (client.frameType === 'top-level') { + if (client?.frameType === 'top-level') { return client } @@ -201,20 +177,20 @@ async function resolveMainClient(event) { async function getResponse(event, client, requestId) { const { request } = event - const clonedRequest = request.clone() + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() function passthrough() { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const headers = Object.fromEntries(clonedRequest.headers.entries()) + const headers = Object.fromEntries(requestClone.headers.entries()) - // Remove MSW-specific request headers so the bypassed requests - // comply with the server's CORS preflight check. - // Operate with the headers as an object because request "Headers" - // are immutable. - delete headers['x-msw-bypass'] + // Remove internal MSW request header so the passthrough request + // complies with any potential CORS preflight checks on the server. + // Some servers forbid unknown request headers. + delete headers['x-msw-intention'] - return fetch(clonedRequest, { headers }) + return fetch(requestClone, { headers }) } // Bypass mocking when the client is not active. @@ -230,57 +206,46 @@ async function getResponse(event, client, requestId) { return passthrough() } - // Bypass requests with the explicit bypass header. - // Such requests can be issued by "ctx.fetch()". - if (request.headers.get('x-msw-bypass') === 'true') { - return passthrough() - } - // Notify the client that a request has been intercepted. - const clientMessage = await sendToClient(client, { - type: 'REQUEST', - payload: { - id: requestId, - url: request.url, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - mode: request.mode, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.text(), - bodyUsed: request.bodyUsed, - keepalive: request.keepalive, + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, }, - }) + [requestBuffer], + ) switch (clientMessage.type) { case 'MOCK_RESPONSE': { return respondWithMock(clientMessage.data) } - case 'MOCK_NOT_FOUND': { + case 'PASSTHROUGH': { return passthrough() } - - case 'NETWORK_ERROR': { - const { name, message } = clientMessage.data - const networkError = new Error(message) - networkError.name = name - - // Rejecting a "respondWith" promise emulates a network error. - throw networkError - } } return passthrough() } -function sendToClient(client, message) { +function sendToClient(client, message, transferrables = []) { return new Promise((resolve, reject) => { const channel = new MessageChannel() @@ -292,17 +257,28 @@ function sendToClient(client, message) { resolve(event.data) } - client.postMessage(message, [channel.port2]) + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) }) } -function sleep(timeMs) { - return new Promise((resolve) => { - setTimeout(resolve, timeMs) +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, }) -} -async function respondWithMock(response) { - await sleep(response.delay) - return new Response(response.body, response) + return mockedResponse } diff --git a/src/components/Comments/Comments.tsx b/src/components/Comments/Comments.tsx index ff1cd72..378363f 100644 --- a/src/components/Comments/Comments.tsx +++ b/src/components/Comments/Comments.tsx @@ -221,21 +221,23 @@ function Comments(props: CommentsProps) { - handleDelete(comment.id)} - trigger={ - - - - - - } - /> + {comment.by.id === user?.id && ( + handleDelete(comment.id)} + trigger={ + + + + + + } + /> + )} {comment.by.id === user?.id ? ( @@ -301,7 +303,7 @@ function Comments(props: CommentsProps) { - {reply.by.id !== user?.id && ( + {reply.by.id === user?.id && ( + handleDelete(reply.id)} + trigger={ + + + + + + } + /> + )} + {reply.by.id === user?.id ? ( + + ) : ( + className="dark:text-white text-gray-800 ml-2"> {reply.hearts_count} diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index 6373da1..34758f4 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -49,21 +49,24 @@ function Footer() { href="https://github.com/felipebrsk/" target="_blank" className="animate-pulse" - data-qa="github-link"> + data-qa="github-link" + rel="noopener noreferrer"> + data-qa="linkedin-link" + rel="noopener noreferrer"> + data-qa="whatsapp-link" + rel="noopener noreferrer"> diff --git a/src/components/Forms/Review/ReviewForm.test.tsx b/src/components/Forms/Review/ReviewForm.test.tsx index a015271..c6469b6 100644 --- a/src/components/Forms/Review/ReviewForm.test.tsx +++ b/src/components/Forms/Review/ReviewForm.test.tsx @@ -111,7 +111,7 @@ describe('ReviewForm Component', () => { await waitFor(() => { expect(consoleSpy).toHaveBeenCalledWith({ review: 'This is a valid review with more than 15 characters.', - played: true, + consumed: true, userId: 1, rate: 2.5, gameId: mockGame.id, @@ -119,7 +119,7 @@ describe('ReviewForm Component', () => { }) }) - it('toggles the played switch on and off', () => { + it('toggles the consumed switch on and off', () => { const { getByRole } = renderReviewForm() const switchElement = getByRole('checkbox') diff --git a/src/components/Forms/Review/ReviewForm.tsx b/src/components/Forms/Review/ReviewForm.tsx index 1888c5f..5c19e9f 100644 --- a/src/components/Forms/Review/ReviewForm.tsx +++ b/src/components/Forms/Review/ReviewForm.tsx @@ -21,7 +21,7 @@ export interface ReviewFormProps { function ReviewForm(props: ReviewFormProps) { const { game } = props - const [played, setPlayed] = useState(false) + const [consumed, setConsumed] = useState(false) const [rating, setRating] = useState(null) const { register, @@ -52,7 +52,7 @@ function ReviewForm(props: ReviewFormProps) { const payload: ReviewStore = { ...data, - played, + consumed, userId: 1, rate: rating, gameId: game.id, @@ -109,9 +109,9 @@ function ReviewForm(props: ReviewFormProps) { setPlayed(v)} + value={consumed} + checked={consumed} + onChange={(_, v) => setConsumed(v)} sx={{ '& .MuiSwitch-switchBase': { color: '#ff4d4d', diff --git a/src/components/Forms/Review/validations.ts b/src/components/Forms/Review/validations.ts index dda8b35..b54d7ec 100644 --- a/src/components/Forms/Review/validations.ts +++ b/src/components/Forms/Review/validations.ts @@ -4,8 +4,8 @@ export const validations = { min: { value: 0.5, message: 'The minimum value for a review is 0.5.' }, max: { value: 5, message: 'The maximum value for a review is 5.' }, }, - played: { - required: 'Please, tell me if you already played this game.', + consumed: { + required: 'Please, tell me if you already consumed this game.', }, review: { minLength: { diff --git a/src/components/GameCard/GameCard.test.tsx b/src/components/GameCard/GameCard.test.tsx index 8e3fe18..edda80b 100644 --- a/src/components/GameCard/GameCard.test.tsx +++ b/src/components/GameCard/GameCard.test.tsx @@ -78,7 +78,10 @@ describe('GameCard Component', () => { name: genre.name, }) - expect(genreLink).toHaveAttribute('href', `/games/genres/${genre.slug}`) + expect(genreLink).toHaveAttribute( + 'href', + `/games/genres/${genre.slug}`, + ) }) }) diff --git a/src/components/GameCard/GameCard.tsx b/src/components/GameCard/GameCard.tsx index 304e21e..8f4a213 100644 --- a/src/components/GameCard/GameCard.tsx +++ b/src/components/GameCard/GameCard.tsx @@ -150,8 +150,10 @@ function GameCard(props: GameCardProps) { {game.crack ? ( - {['cracked', 'cracked-oneday'].includes(game.crack.status) && - game.crack.by && ( + {['cracked', 'cracked-oneday'].includes( + game.crack.status.name, + ) && + game.crack.cracker && ( - {game.crack.by.name} + target="_blank" + rel="noopener noreferrer"> + {game.crack.cracker.name} @@ -187,7 +190,8 @@ function GameCard(props: GameCardProps) { + target="_blank" + rel="noopener noreferrer"> {game.crack.protection.name} @@ -195,22 +199,22 @@ function GameCard(props: GameCardProps) { )} ) : ( ) } className={`font-bold px-3 py-1 rounded-md text-white ${ - game.crack.status === 'uncracked' + game.crack.status.name === 'uncracked' ? 'bg-theme-red-900 animate-pulse' : 'bg-green-500' }`} style={{ boxShadow: `0 4px 10px ${ - game.crack.status === 'uncracked' + game.crack.status.name === 'uncracked' ? 'rgba(255, 77, 77, 0.5)' : 'rgba(76, 175, 80, 0.5)' }`, diff --git a/src/components/Header/modules/HeaderCarousel.tsx b/src/components/Header/modules/HeaderCarousel.tsx index d4fb212..69e0211 100644 --- a/src/components/Header/modules/HeaderCarousel.tsx +++ b/src/components/Header/modules/HeaderCarousel.tsx @@ -1,11 +1,11 @@ import { Box, Chip, Link, Stack, Tooltip, Typography } from '@mui/material' import { Carousel } from 'react-responsive-carousel' -import { Banner } from '@/types' +import { GameBanner } from '@/types' import { mapCrack } from '@/utils' interface HeaderCarouselProps { - banners: Banner[] + banners: GameBanner[] } function HeaderCarousel(props: HeaderCarouselProps) { @@ -23,10 +23,10 @@ function HeaderCarousel(props: HeaderCarouselProps) { emulateTouch preventMovementUntilSwipeScrollTolerance={true} swipeScrollTolerance={50}> - {banners.map(({ id, game }) => ( + {banners.map(({ id, bannerable }) => ( Game Banner @@ -35,19 +35,19 @@ function HeaderCarousel(props: HeaderCarouselProps) { - {game.title} + {bannerable.title} - {game.crack ? ( + {bannerable.crack ? ( ) : ( - {game.short_description} + {bannerable.short_description} Available on: - {game.platforms.map(({ id, slug, name }) => ( + {bannerable.platforms.map(({ id, slug, name }) => ( - {game.genres.map(({ id, slug, name }) => ( + {bannerable.genres.map(({ id, slug, name }) => ( { - await unreadAll() - } - const handleDeleteAll = async () => { await deleteAll() } @@ -119,19 +106,18 @@ function Notifications(props: NotificationsProps) { toggle() - if (notification.content.actionUrl.startsWith('https://')) { - window.open(notification.content.actionUrl, '_blank') + if (notification.data.actionUrl.startsWith('https://')) { + window.open(notification.data.actionUrl, '_blank') return } - go(notification.content.actionUrl) + go(notification.data.actionUrl) } useSuccess(successRead, readData?.message) useSuccess(successUnread, unreadData?.message) useSuccess(successRemove, removeData?.message) useSuccess(successReadAll, readAllData?.message) - useSuccess(successUnreadAll, unreadAllData?.message) useSuccess(successDeleteAll, deleteAllData?.message) const renderEmptyState = () => ( @@ -190,38 +176,7 @@ function Notifications(props: NotificationsProps) { ) - const renderMarkAllAs = () => { - if (notifications.some(({ read_at }) => !read_at)) { - return ( - - ) - } - - return ( - - ) - } - - const renderDeleteAll = () => { - return ( - - ) - } + const hasUnread = notifications.some(({ read_at }) => !read_at) return ( {notifications.length > 0 ? ( <> - - {renderMarkAllAs()} - {renderDeleteAll()} + + {hasUnread && ( + + )} + {groupedNotifications.map(({ date, notifications }) => ( @@ -260,7 +230,7 @@ function Notifications(props: NotificationsProps) { className={`focus:outline-none w-8 h-8 border rounded-full dark:border-gray-200 border-gray-700 flex items-center flex-shrink-0 justify-center ${!notification.read_at ? 'animate-pulse' : ''}`}> @@ -269,13 +239,13 @@ function Notifications(props: NotificationsProps) { - + handleOpenNotification(notification) } className="dark:text-gray-300 text-gray-900 hover:text-yellow-400 transition-colors duration-300 hover:animate-pulse cursor-pointer"> - {s(notification.content.title, 70)} + {s(notification.data.title, 70)} { it('renders the "Redeem Rewards" button when redeemable', async () => { const redeemableMission: Mission = { ...mockMission, - user_mission: { + progress: { completed: false, last_completed_at: new Date().toISOString(), }, @@ -90,7 +90,7 @@ describe('MissionCard', () => { const redeemableMission: Mission = { ...mockMission, - user_mission: { + progress: { completed: false, last_completed_at: subDays(new Date(), 1).toISOString(), }, @@ -125,7 +125,10 @@ describe('MissionCard', () => { it('displays "New" tag for missions created within the last week', () => { const recentMission: Mission = { ...(MOCK_MISSIONS.shift() as Mission), - status: 'available', + status: { + id: 1, + name: 'available', + }, created_at: new Date().toISOString(), } @@ -137,7 +140,10 @@ describe('MissionCard', () => { it('renders the correct status chip based on mission status', () => { const inProgressMission: Mission = { ...(MOCK_MISSIONS.shift() as Mission), - status: 'available', + status: { + id: 1, + name: 'available', + }, } renderWithToaster() @@ -146,7 +152,7 @@ describe('MissionCard', () => { const completedMission: Mission = { ...mockMission, - user_mission: { + progress: { completed: true, last_completed_at: new Date().toISOString(), }, diff --git a/src/components/MissionCard/MissionCard.tsx b/src/components/MissionCard/MissionCard.tsx index 7be6ab6..336217a 100644 --- a/src/components/MissionCard/MissionCard.tsx +++ b/src/components/MissionCard/MissionCard.tsx @@ -29,23 +29,23 @@ function MissionCard(props: MissionCardProps) { useCompleteMissionMutation() const shouldApplyOpacity = ['canceled', 'unavailable'].includes( - mission.status, + mission.status.name, ) const overallProgress = c(mission.requirements) - const isRecurring = mission.frequency !== 'one-time' + const isRecurring = mission.frequency !== 'one_time' const resetTime = isRecurring ? `Resets at: ${formatRelative(new Date(mission.reset_time), new Date())}` : '' const rewardsRedeemable = - !(mission.user_mission && mission.user_mission.completed) && + !(mission.progress && mission.progress.completed) && overallProgress >= 100 && !shouldApplyOpacity const renderNewTag = - mission.status === 'available' && + mission.status.name === 'available' && isWithinInterval(new Date(mission.created_at), { start: subWeeks(new Date(), 1), end: new Date(), @@ -190,28 +190,28 @@ function MissionCard(props: MissionCardProps) { ) : overallProgress === 100 ? ( - ) : mission.status === 'available' ? ( + ) : mission.status.name === 'available' ? ( ) : ( diff --git a/src/components/RewardList/RewardList.stories.tsx b/src/components/RewardList/RewardList.stories.tsx index b06afbb..4da0be6 100644 --- a/src/components/RewardList/RewardList.stories.tsx +++ b/src/components/RewardList/RewardList.stories.tsx @@ -10,8 +10,6 @@ import RewardList from './RewardList' const rewards: TitleReward[] = [ { id: 1, - created_at: '2024-01-01', - updated_at: '2024-01-01', sourceable_type: 'game', rewardable_type: 'titles', title: { diff --git a/src/components/RewardList/RewardList.test.tsx b/src/components/RewardList/RewardList.test.tsx index a384bdf..076093f 100644 --- a/src/components/RewardList/RewardList.test.tsx +++ b/src/components/RewardList/RewardList.test.tsx @@ -9,8 +9,6 @@ describe('RewardList Component', () => { const rewards: TitleReward[] = [ { id: 1, - created_at: '2024-01-01', - updated_at: '2024-01-01', sourceable_type: 'game', rewardable_type: 'titles', title: { @@ -35,8 +33,6 @@ describe('RewardList Component', () => { const rewards: BaseReward[] = [ { id: 2, - created_at: '2024-01-01', - updated_at: '2024-01-01', sourceable_type: 'game', rewardable_type: 'unsupported_type', }, diff --git a/src/components/TitleCard/TitleCard.test.tsx b/src/components/TitleCard/TitleCard.test.tsx index bb96502..32599d5 100644 --- a/src/components/TitleCard/TitleCard.test.tsx +++ b/src/components/TitleCard/TitleCard.test.tsx @@ -1,12 +1,12 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import toast from 'react-hot-toast' -import { MOCK_TITLES, MOCK_USER } from '@/mocks' +import { MOCK_MISSIONS, MOCK_TITLES, MOCK_USER } from '@/mocks' import { useBuyTitleMutation, useToggleTitleMutation, } from '@/services/api' -import { Title, User } from '@/types' +import { Mission, Title, User } from '@/types' import TitleCard from './TitleCard' @@ -21,6 +21,7 @@ jest.mock('react-hot-toast', () => ({ const mockUser: User = MOCK_USER const mockTitle: Title = MOCK_TITLES.pop() as Title +const mockMission: Mission = MOCK_MISSIONS.pop() as Mission describe('TitleCard', () => { beforeEach(() => { @@ -50,18 +51,24 @@ describe('TitleCard', () => { it('shows the progress bar correctly', () => { const halfTitle: Title = { ...mockTitle, - requirements: [ - { - ...mockTitle.requirements[0], - progress: { - id: 1, - progress: mockTitle.requirements[0].goal / 2, - completed: false, - created_at: '2024-01-01T00:00:00.000Z', - updated_at: '2024-01-01T00:00:00.000Z', - }, + rewardable: { + id: 1, + rewardable_type: 'App\\\\Models\\\\Title', + sourceable_type: 'App\\\\Models\\\\Mission', + sourceable: { + ...mockMission, + requirements: [ + { + ...mockMission.requirements[0], + progress: { + id: 1, + progress: mockMission.requirements[0].goal / 2, + completed: false, + }, + }, + ], }, - ], + }, } render() @@ -88,6 +95,7 @@ describe('TitleCard', () => { ...(MOCK_TITLES.shift() as Title), purchasable: true, cost: 50, + own: false, } render( @@ -96,7 +104,7 @@ describe('TitleCard', () => { ...mockUser, wallet: { id: 1, - amount: 50, + balance: 50, }, }} title={purchasable} @@ -163,18 +171,24 @@ describe('TitleCard', () => { const completedTitle: Title = { ...mockTitle, - requirements: [ - { - ...mockTitle.requirements[0], - progress: { - id: 1, - progress: 100, - completed: true, - created_at: '2024-01-01T00:00:00.000Z', - updated_at: '2024-01-01T00:00:00.000Z', - }, + rewardable: { + id: 2, + rewardable_type: 'App\\\\Models\\\\Title', + sourceable_type: 'App\\\\Models\\\\Mission', + sourceable: { + ...mockMission, + requirements: [ + { + ...mockMission.requirements[0], + progress: { + id: 1, + progress: 100, + completed: true, + }, + }, + ], }, - ], + }, } render() diff --git a/src/components/TitleCard/TitleCard.tsx b/src/components/TitleCard/TitleCard.tsx index 89df5dc..8cd2f92 100644 --- a/src/components/TitleCard/TitleCard.tsx +++ b/src/components/TitleCard/TitleCard.tsx @@ -42,16 +42,18 @@ function TitleCard(props: TitleCardProps) { } }, [buySuccess, isSuccess]) - const overallProgress = c(title.requirements) + const overallProgress = c( + title.rewardable?.sourceable.requirements || [], + ) const shouldApplyOpacity = ['canceled', 'unavailable'].includes( - title.status, + title.status.name, ) const handlePurchase = async () => { if (!(title.cost && title.purchasable)) return - if (user.wallet.amount >= title.cost) { + if (user.wallet.balance >= title.cost) { await buy(title.id) } } @@ -61,6 +63,10 @@ function TitleCard(props: TitleCardProps) { } const isEnabled = user.title && user.title.id === title.id + const hasRequirements = + title.rewardable && + title.rewardable.sourceable && + title.rewardable.sourceable.requirements return ( - + {title.title} - + {title.description} @@ -81,78 +88,66 @@ function TitleCard(props: TitleCardProps) { )} - - - - {overallProgress}% - - - - - Requirements - - 1 - ? 'md:grid-cols-2 grid-cols-1' - : 'grid-cols-1' - } md:gap-4 gap-2 w-full`}> - {title.requirements.map((req) => ( - - - {req.progress?.completed ? ( - - ) : ( - `${req.progress?.progress || 0}/${req.goal}` - )} - - }> - - - - ))} - - - {title.purchasable && overallProgress !== 100 && ( - + {title.rewardable && title.rewardable.sourceable && ( + <> + + + + {overallProgress}% + + + + Requirements + + 1 + ? 'md:grid-cols-2 grid-cols-1' + : 'grid-cols-1' + } md:gap-4 gap-2 w-full`}> + {title.rewardable.sourceable.requirements.map((req) => ( + + + {req.progress?.completed ? ( + + ) : ( + `${req.progress?.progress || 0}/${req.goal}` + )} + + }> + + + + ))} + + )} - {overallProgress === 100 && ( + + {title.own ? ( + ) : ( + title.purchasable && ( + + ) )} - {overallProgress === 100 ? ( - } - /> - ) : ( - - )} + {hasRequirements && + (title.own ? ( + } + /> + ) : ( + + ))} ) } diff --git a/src/constants.ts b/src/constants.ts index d825f51..8aa0fe6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,7 +2,7 @@ export const prodEnv = import.meta.env.PROD export const NEWS_API_KEY = '36589b860323e5af3f28a05a8610190436bb7239' export const baseUrl: string = - import.meta.env.VITE_BASE_URL || 'http://localhost:8080' + import.meta.env.VITE_BASE_URL || 'http://localhost:8000' export const USER_KEY = '_gc_OauiWmzbwz27rYcjdWtG2dksUHFz8TPk' export const ARTICLES_KEY = '_gc_Ias83Ksi94Lkddmwe(23*239sdKkssjJ' diff --git a/src/mocks/data/banners.ts b/src/mocks/data/banners.ts index 4ee397e..d55c34a 100644 --- a/src/mocks/data/banners.ts +++ b/src/mocks/data/banners.ts @@ -1,12 +1,12 @@ import { faker } from '@faker-js/faker' -import { Banner } from '@/types' +import { GameBanner } from '@/types' -export const MOCK_BANNERS: Banner[] = [ +export const MOCK_BANNERS: GameBanner[] = [ { id: 1, - bannerable_type: 'games', - game: { + type: 'games', + bannerable: { id: 1, condition: 'hot', slug: 'black-myth-wukong', @@ -45,8 +45,8 @@ export const MOCK_BANNERS: Banner[] = [ }, { id: 2, - bannerable_type: 'games', - game: { + type: 'games', + bannerable: { id: 2, slug: 'god-of-war-ragnarok', title: 'God of War: Ragnarok', @@ -81,8 +81,8 @@ export const MOCK_BANNERS: Banner[] = [ }, { id: 3, - bannerable_type: 'games', - game: { + type: 'games', + bannerable: { id: 3, views_count: faker.number.int(), hearts_count: faker.number.int(), @@ -115,8 +115,8 @@ export const MOCK_BANNERS: Banner[] = [ }, { id: 4, - bannerable_type: 'games', - game: { + type: 'games', + bannerable: { id: 4, slug: 'alan-wake-2', title: 'Alan Wake 2', @@ -158,8 +158,8 @@ export const MOCK_BANNERS: Banner[] = [ }, { id: 5, - bannerable_type: 'games', - game: { + type: 'games', + bannerable: { id: 5, slug: 'hades-ii', title: 'Hades II', @@ -200,8 +200,8 @@ export const MOCK_BANNERS: Banner[] = [ }, { id: 6, - bannerable_type: 'games', - game: { + type: 'games', + bannerable: { id: 6, slug: 'star-wars-outlaws', title: 'Star Wars: Outlaws', diff --git a/src/mocks/data/blogs.ts b/src/mocks/data/blogs.ts index 095d4b8..f530843 100644 --- a/src/mocks/data/blogs.ts +++ b/src/mocks/data/blogs.ts @@ -37,7 +37,7 @@ for (let i = 0; i < 100; i++) { }, wallet: { id: 1, - amount: faker.number.int(), + balance: faker.number.int(), }, }, } @@ -80,7 +80,7 @@ export const MOCK_BLOG_DETAILS: BlogDetails = { }, wallet: { id: 1, - amount: faker.number.int(), + balance: faker.number.int(), }, }, comments: [ diff --git a/src/mocks/data/game.ts b/src/mocks/data/game.ts index a6dfd34..a2cea16 100644 --- a/src/mocks/data/game.ts +++ b/src/mocks/data/game.ts @@ -29,9 +29,12 @@ export const MOCK_GAME_DETAILS: GameDetails = { is_free: false, crack: { id: 1, - status: 'uncracked', + status: { + id: 2, + name: 'uncracked', + }, cracked_at: null, - by: null, + cracker: null, protection: { id: 1, name: 'Denuvo', @@ -86,23 +89,23 @@ export const MOCK_GAME_DETAILS: GameDetails = { { id: 1, path: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2358720/ss_86c4b7462bba219a0d0b89931a35812b9f188976.1920x1080.jpg?t=1725007201', - media_type: { id: 1, name: 'photo' }, + type: { id: 1, name: 'photo' }, }, { id: 2, path: 'http://video.akamai.steamstatic.com/store_trailers/257048125/movie_max.mp4?t=1724238304', - media_type: { id: 2, name: 'video' }, + type: { id: 2, name: 'video' }, }, ], reviews: [ { id: 1, - played: true, + consumed: true, rate: 5, review: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat laudantium praesentium architecto quis iusto aut et non facere, incidunt quo corrupti? Quibusdam ipsum voluptate quaerat distinctio ea. Quasi, debitis maiores! Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat laudantium praesentium architecto quis iusto aut et non facere, incidunt quo corrupti? Quibusdam ipsum voluptate quaerat distinctio ea. Quasi, debitis maiores! Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat laudantium praesentium architecto quis iusto aut et non facere, incidunt quo corrupti? Quibusdam ipsum voluptate quaerat distinctio ea. Quasi, debitis maiores!`, - user: { + by: { id: 1, name: 'Player 1', email: 'dev@dev.com', @@ -114,12 +117,12 @@ export const MOCK_GAME_DETAILS: GameDetails = { }, { id: 2, - played: false, + consumed: false, rate: 3.5, review: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat laudantium praesentium architecto quis iusto aut et non facere, incidunt quo corrupti? Quibusdam ipsum voluptate quaerat distinctio ea. Quasi, debitis maiores! Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat laudantium praesentium architecto quis iusto aut et non facere, incidunt quo corrupti? Quibusdam ipsum voluptate quaerat distinctio ea. Quasi, debitis maiores! Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat laudantium praesentium architecto quis iusto aut et non facere, incidunt quo corrupti? Quibusdam ipsum voluptate quaerat distinctio ea. Quasi, debitis maiores!`, - user: { + by: { id: 2, name: 'Player 2', email: 'dev2@dev.com', @@ -131,11 +134,11 @@ export const MOCK_GAME_DETAILS: GameDetails = { }, { id: 3, - played: false, + consumed: false, rate: 2.5, review: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat laudantium praesentium architecto quis iusto aut et non facere, incidunt quo corrupti? Quibusdam ipsum voluptate quaerat distinctio ea. Quasi, debitis maiores!', - user: { + by: { id: 3, name: 'Player 3', email: 'dev3@dev.com', @@ -147,11 +150,11 @@ export const MOCK_GAME_DETAILS: GameDetails = { }, { id: 4, - played: true, + consumed: true, rate: 2, review: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Placeat laudantium praesentium architecto quis iusto aut et non facere, incidunt quo corrupti? Quibusdam ipsum voluptate quaerat distinctio ea. Quasi, debitis maiores!', - user: { + by: { id: 4, name: 'Player 4', email: 'dev4@dev.com', @@ -166,39 +169,45 @@ export const MOCK_GAME_DETAILS: GameDetails = { { id: 1, release_date: '2024-08-19', - name: 'Black Myth: Wukong Deluxe Edition Upgrade', + title: 'Black Myth: Wukong Deluxe Edition Upgrade', + categories: [], + developers: [], + genres: [], + publishers: [], + tags: [], + slug: 'black-myth-wukong-deluxe-edition-upgrade', cover: 'https://cdn1.epicgames.com/spt-assets/ca9ef1bcf2f54043baac351366aec677/black-myth-wukong-1t5ca.jpg', galleries: [ { id: 1, path: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2672610/ss_fda62ee2e4ead7eac83ef0e825c6a644d6d38fa2.1920x1080.jpg?t=1725007043', - media_type: { id: 1, name: 'photo' }, + type: { id: 1, name: 'photo' }, }, { id: 2, path: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2672610/ss_86c4b7462bba219a0d0b89931a35812b9f188976.1920x1080.jpg?t=1725007043', - media_type: { id: 1, name: 'photo' }, + type: { id: 1, name: 'photo' }, }, { id: 3, path: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2672610/ss_fda62ee2e4ead7eac83ef0e825c6a644d6d38fa2.1920x1080.jpg?t=1725007043', - media_type: { id: 1, name: 'photo' }, + type: { id: 1, name: 'photo' }, }, { id: 4, path: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2672610/ss_86c4b7462bba219a0d0b89931a35812b9f188976.1920x1080.jpg?t=1725007043', - media_type: { id: 1, name: 'photo' }, + type: { id: 1, name: 'photo' }, }, { id: 5, path: 'http://video.akamai.steamstatic.com/store_trailers/257048125/movie_max.mp4?t=1724238304', - media_type: { id: 2, name: 'video' }, + type: { id: 2, name: 'video' }, }, { id: 6, path: 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2672610/ss_86c4b7462bba219a0d0b89931a35812b9f188976.1920x1080.jpg?t=1725007043', - media_type: { id: 1, name: 'photo' }, + type: { id: 1, name: 'photo' }, }, ], platforms: [ @@ -356,7 +365,7 @@ export const MOCK_GAME_DETAILS: GameDetails = { os: 'Windows 10 64-bit', obs: 'HDD Supported, SSD Recommended. The above specifications were tested with DLSS/FSR/XeSS enabled.', network: 'Non necessary', - requirement_type: { id: 1, os: 'minimum', potential: 'windows' }, + type: { id: 1, os: 'windows', potential: 'minimum' }, }, { id: 2, @@ -368,17 +377,18 @@ export const MOCK_GAME_DETAILS: GameDetails = { os: 'Windows 10 64-bit', obs: 'SSD Required. The above specifications were tested with DLSS/FSR/XeSS enabled.', network: 'Non necessary', - requirement_type: { id: 2, os: 'recommended', potential: 'windows' }, + type: { id: 2, os: 'windows', potential: 'recommended' }, }, ], torrents: [ { id: 1, - posted_in: '2024-02-09T00:00:00.000Z', + posted_at: '2024-02-09T00:00:00.000Z', url: 'https://google.com', provider: { id: 1, name: 'FitGirl Repacks', + slug: 'fitgirl-repacks', url: 'https://google.com', }, }, diff --git a/src/mocks/data/home.ts b/src/mocks/data/home.ts index 559d65e..6f9b160 100644 --- a/src/mocks/data/home.ts +++ b/src/mocks/data/home.ts @@ -14,6 +14,6 @@ export const MOCK_HOME: Home = { banners: MOCK_BANNERS, popular: MOCK_POPULAR_GAMES, next_release: MOCK_NEXT_RELEASE, - upcoming_games: MOCK_UPCOMING_GAMES, - most_liked_games: MOCK_MOST_LIKED_GAMES, + upcoming: MOCK_UPCOMING_GAMES, + most_liked: MOCK_MOST_LIKED_GAMES, } diff --git a/src/mocks/data/missions.ts b/src/mocks/data/missions.ts index ec9a5a3..0de2ba5 100644 --- a/src/mocks/data/missions.ts +++ b/src/mocks/data/missions.ts @@ -9,9 +9,9 @@ export const MOCK_MISSIONS: Mission[] = [ coins: 10, experience: 50, created_at: faker.date.anytime().toISOString(), - frequency: 'one-time', + frequency: 'one_time', reset_time: faker.date.anytime().toISOString(), - user_mission: null, + progress: null, description: 'Slay the dragon in the Dark Forest to earn rare rewards.', requirements: [ @@ -39,7 +39,10 @@ export const MOCK_MISSIONS: Mission[] = [ }, ], rewards: [], - status: 'available', + status: { + id: 1, + name: 'available', + }, }, { id: 2, @@ -49,7 +52,7 @@ export const MOCK_MISSIONS: Mission[] = [ created_at: faker.date.anytime().toISOString(), frequency: 'daily', reset_time: faker.date.anytime().toISOString(), - user_mission: null, + progress: null, description: 'Go to your profile and add a custom profile pic!', requirements: [ { @@ -65,7 +68,10 @@ export const MOCK_MISSIONS: Mission[] = [ }, ], rewards: [], - status: 'available', + status: { + id: 1, + name: 'available', + }, }, { id: 3, @@ -75,7 +81,7 @@ export const MOCK_MISSIONS: Mission[] = [ created_at: faker.date.anytime().toISOString(), frequency: 'monthly', reset_time: faker.date.anytime().toISOString(), - user_mission: { + progress: { completed: true, last_completed_at: faker.date.anytime().toISOString(), }, @@ -103,16 +109,23 @@ export const MOCK_MISSIONS: Mission[] = [ updated_at: faker.date.anytime().toISOString(), title: { id: 1, + own: false, title: 'Gaming Senior', description: 'You are the gaming senior!', created_at: faker.date.anytime().toISOString(), + updated_at: faker.date.anytime().toISOString(), purchasable: false, - status: 'available', - requirements: [], + status: { + id: 1, + name: 'available', + }, }, }, ], - status: 'available', + status: { + id: 1, + name: 'available', + }, }, { id: 4, @@ -122,7 +135,7 @@ export const MOCK_MISSIONS: Mission[] = [ created_at: '2024-10-01T00:00:00.000Z', frequency: 'monthly', reset_time: faker.date.anytime().toISOString(), - user_mission: null, + progress: null, description: 'Go to the coins section and make a new transaction to spend your coins!', requirements: [ @@ -147,15 +160,22 @@ export const MOCK_MISSIONS: Mission[] = [ updated_at: faker.date.anytime().toISOString(), title: { id: 1, + own: false, title: 'Gaming Senior', description: 'You are the gaming senior!', created_at: faker.date.anytime().toISOString(), + updated_at: faker.date.anytime().toISOString(), purchasable: false, - status: 'available', - requirements: [], + status: { + id: 1, + name: 'available', + }, }, }, ], - status: 'available', + status: { + id: 1, + name: 'available', + }, }, ] diff --git a/src/mocks/data/notifications.ts b/src/mocks/data/notifications.ts index b12a737..f7e3230 100644 --- a/src/mocks/data/notifications.ts +++ b/src/mocks/data/notifications.ts @@ -8,7 +8,7 @@ export const MOCK_NOTIFICATIONS: Notification[] = [ read_at: faker.date.anytime().toISOString(), created_at: '2024-08-30T19:52:00.000Z', updated_at: '2024-08-30T19:52:00.000Z', - content: { + data: { title: 'The game that you was following was finally released! Check it out.', actionUrl: '/games/2913423', @@ -20,7 +20,7 @@ export const MOCK_NOTIFICATIONS: Notification[] = [ read_at: null, created_at: '2024-08-30T19:52:00.000Z', updated_at: '2024-08-30T19:52:00.000Z', - content: { + data: { title: 'The game you saved on your wishlist is now on sale!', actionUrl: '/games/2913423', icon: 'CiBadgeDollar', @@ -31,7 +31,7 @@ export const MOCK_NOTIFICATIONS: Notification[] = [ read_at: null, created_at: '2024-08-30T19:52:00.000Z', updated_at: '2024-08-30T19:52:00.000Z', - content: { + data: { title: 'A new game with the tag Fantasy was released.', actionUrl: '/games/2913423', icon: 'MdOutlineNewReleases', @@ -42,7 +42,7 @@ export const MOCK_NOTIFICATIONS: Notification[] = [ read_at: faker.date.anytime().toISOString(), created_at: '2024-08-29T19:52:00.000Z', updated_at: '2024-08-30T19:52:00.000Z', - content: { + data: { title: 'John Doe liked your comment.', actionUrl: '/games/2913423', icon: 'IoHeartOutline', @@ -53,7 +53,7 @@ export const MOCK_NOTIFICATIONS: Notification[] = [ read_at: faker.date.anytime().toISOString(), created_at: '2024-08-22T19:52:00.000Z', updated_at: '2024-08-30T19:52:00.000Z', - content: { + data: { title: 'John Doe liked your comment.', actionUrl: '/games/2913423', icon: 'IoHeartOutline', @@ -64,7 +64,7 @@ export const MOCK_NOTIFICATIONS: Notification[] = [ read_at: faker.date.anytime().toISOString(), created_at: '2024-08-26T19:52:00.000Z', updated_at: '2024-08-30T19:52:00.000Z', - content: { + data: { title: 'John Doe liked your comment.', actionUrl: '/games/2913423', icon: 'IoHeartOutline', @@ -75,7 +75,7 @@ export const MOCK_NOTIFICATIONS: Notification[] = [ read_at: null, created_at: '2024-08-26T19:52:00.000Z', updated_at: '2024-08-30T19:52:00.000Z', - content: { + data: { title: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Quos quam optio porro pariatur excepturi quis corrupti blanditiis consectetur at quasi eum veniam mollitia quo dolores quia, repellendus dignissimos necessitatibus libero?', actionUrl: '/games/2913423', diff --git a/src/mocks/data/titles.ts b/src/mocks/data/titles.ts index 903fd95..8d04d06 100644 --- a/src/mocks/data/titles.ts +++ b/src/mocks/data/titles.ts @@ -7,89 +7,151 @@ export const MOCK_TITLES: Title[] = [ id: 1, title: 'Gaming Senior', description: 'You are the master of games!', - requirements: [ - { + own: false, + cost: 1000, + purchasable: true, + status: { + id: 1, + name: 'available', + }, + created_at: faker.date.anytime().toISOString(), + updated_at: faker.date.anytime().toISOString(), + rewardable: { + id: 1, + rewardable_type: 'App\\\\Models\\\\Title', + sourceable_type: 'App\\\\Models\\\\Mission', + sourceable: { id: 1, - task: 'Reach level 10', - goal: 10, + coins: 10, + progress: null, + experience: 10, + frequency: 'one_time', + mission: faker.lorem.words(), + description: faker.lorem.text(), + reset_time: faker.date.anytime().toISOString(), created_at: faker.date.anytime().toISOString(), - updated_at: faker.date.anytime().toISOString(), - progress: { + status: { id: 1, - progress: 8, - completed: false, - created_at: faker.date.anytime().toISOString(), - updated_at: faker.date.anytime().toISOString(), - }, - }, - { - id: 2, - task: 'Obtain a fire sword', - goal: 1, - created_at: faker.date.anytime().toISOString(), - updated_at: faker.date.anytime().toISOString(), - progress: { - id: 2, - progress: 0, - completed: false, - created_at: faker.date.anytime().toISOString(), - updated_at: faker.date.anytime().toISOString(), + name: 'available', }, + rewards: [], + requirements: [ + { + id: 1, + task: 'Reach level 10', + goal: 10, + progress: { + id: 1, + progress: 8, + completed: false, + }, + }, + { + id: 2, + task: 'Obtain a fire sword', + goal: 1, + progress: { + id: 2, + progress: 0, + completed: false, + }, + }, + ], }, - ], - cost: 1000, - purchasable: true, - status: 'progress', - created_at: faker.date.anytime().toISOString(), + }, }, { id: 2, + own: false, title: 'Update your profile picture', description: 'Go to your profile and add a custom profile pic!', - requirements: [ - { - id: 3, - task: 'Update your profile picture.', - goal: 1, + purchasable: false, + status: { + id: 1, + name: 'available', + }, + created_at: faker.date.anytime().toISOString(), + updated_at: faker.date.anytime().toISOString(), + rewardable: { + id: 2, + rewardable_type: 'App\\\\Models\\\\Title', + sourceable_type: 'App\\\\Models\\\\Mission', + sourceable: { + id: 2, + coins: 10, + progress: null, + experience: 10, + frequency: 'one_time', + mission: faker.lorem.words(), + description: faker.lorem.text(), + reset_time: faker.date.anytime().toISOString(), created_at: faker.date.anytime().toISOString(), - updated_at: faker.date.anytime().toISOString(), - progress: { - id: 3, - progress: 0, - completed: false, - created_at: faker.date.anytime().toISOString(), - updated_at: faker.date.anytime().toISOString(), + status: { + id: 1, + name: 'available', }, + rewards: [], + requirements: [ + { + id: 3, + task: 'Update your profile picture.', + goal: 1, + progress: { + id: 3, + progress: 0, + completed: false, + }, + }, + ], }, - ], - purchasable: false, - status: 'available', - created_at: faker.date.anytime().toISOString(), + }, }, { id: 3, + own: true, title: 'Make a new transaction', description: 'Go to the coins section and make a new transaction to spend your coins!', - requirements: [ - { - id: 4, - task: 'Make a new transaction.', - goal: 1, - created_at: faker.date.anytime().toISOString(), - updated_at: faker.date.anytime().toISOString(), - progress: { - id: 4, - progress: 1, - completed: true, - created_at: faker.date.anytime().toISOString(), - updated_at: faker.date.anytime().toISOString(), - }, - }, - ], purchasable: true, cost: 90000, - status: 'completed', + status: { + id: 1, + name: 'available', + }, created_at: faker.date.anytime().toISOString(), + updated_at: faker.date.anytime().toISOString(), + rewardable: { + id: 3, + rewardable_type: 'App\\\\Models\\\\Title', + sourceable_type: 'App\\\\Models\\\\Mission', + sourceable: { + id: 3, + coins: 10, + progress: null, + experience: 10, + frequency: 'one_time', + mission: faker.lorem.words(), + description: faker.lorem.text(), + reset_time: faker.date.anytime().toISOString(), + created_at: faker.date.anytime().toISOString(), + status: { + id: 1, + name: 'available', + }, + rewards: [], + requirements: [ + { + id: 4, + task: 'Make a new transaction.', + goal: 1, + progress: { + id: 4, + progress: 1, + completed: true, + }, + }, + ], + }, + }, }, ] diff --git a/src/mocks/data/transactions.ts b/src/mocks/data/transactions.ts index 6061927..1f17953 100644 --- a/src/mocks/data/transactions.ts +++ b/src/mocks/data/transactions.ts @@ -9,8 +9,11 @@ for (let i = 0; i < 20; i++) { id: 1, amount: faker.number.int(), description: faker.lorem.sentence(), - type: faker.helpers.arrayElement(['addition', 'subtraction']), created_at: faker.date.anytime().toISOString(), + type: { + id: 1, + type: faker.helpers.arrayElement(['addition', 'subtraction']), + }, } const newTransaction: Transaction = { diff --git a/src/mocks/data/user.ts b/src/mocks/data/user.ts index c2f67ba..c4edf38 100644 --- a/src/mocks/data/user.ts +++ b/src/mocks/data/user.ts @@ -22,7 +22,7 @@ export const MOCK_USER: User = { }, wallet: { id: 1, - amount: faker.number.int(), + balance: faker.number.int(), }, } @@ -157,7 +157,7 @@ export const MOCK_TEAM_MEMBERS: TeamMember[] = [ }, ], image: - 'https://media.licdn.com/dms/image/v2/D4D03AQEWxmd_PO-GQw/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1726190852644?e=1731542400&v=beta&t=sjM1wZFhdC_9XENTKpxO2MYNjLGAnC118zFKsosq470', + 'https://media.licdn.com/dms/image/v2/D4D03AQEWxmd_PO-GQw/profile-displayphoto-shrink_800_800/profile-displayphoto-shrink_800_800/0/1726190852644?e=1742428800&v=beta&t=wc_m4BoK2wccWmzEiwX-6oj7nW7n73ldxg2D_DStZyI', socials: { github: 'https://github.com/felipebrsk/', linkedin: 'https://linkedin.com/in/felipe-luz-oliveira/', diff --git a/src/pages/Calendar/Calendar.tsx b/src/pages/Calendar/Calendar.tsx index 4da8ec4..095f739 100644 --- a/src/pages/Calendar/Calendar.tsx +++ b/src/pages/Calendar/Calendar.tsx @@ -7,6 +7,8 @@ import { useMediaQuery, } from '@mui/material' import { format, isSameDay, parseISO } from 'date-fns' +import { useState } from 'react' +import { CalendarProps } from 'react-calendar' import { useNavigate } from 'react-router-dom' import { Icon } from '@/components' @@ -27,6 +29,7 @@ interface CalendarTileProperties { function Calendar() { const mode = useTheme() const go = useNavigate() + const [date, setDate] = useState(new Date()) const isMobile = useMediaQuery('(max-width: 490px)') const { games, isLoading } = useCalendarGamesQuery(undefined, { selectFromResult: ({ data = [], isLoading, isFetching }) => ({ @@ -35,6 +38,8 @@ function Calendar() { }), }) + const currentDate = new Date() + const getTileContent = ({ date, view }: CalendarTileProperties) => { const appointment = games.filter((game) => isSameDay(parseISO(game.release_date), date), @@ -43,7 +48,13 @@ function Calendar() { if (!appointment || view !== 'month') return null return ( - + {appointment.map((item) => ( - + - + )} @@ -89,6 +100,17 @@ function Calendar() { ) } + const props: CalendarProps = { + locale: 'en-US', + minDetail: 'year', + tileContent: (tileProps: CalendarTileProperties) => + getTileContent(tileProps), + onChange: (value) => value && setDate(value as Date), + value: date, + maxDate: new Date(currentDate.getFullYear(), 11), + minDate: new Date(currentDate.getFullYear() - 1, 0), + } + return isLoading ? ( {!isMobile ? ( mode === 'dark' ? ( - + ) : ( - + ) ) : ( diff --git a/src/pages/Calendar/modules/UpcomingList.tsx b/src/pages/Calendar/modules/UpcomingList.tsx index 75eca06..4282d0a 100644 --- a/src/pages/Calendar/modules/UpcomingList.tsx +++ b/src/pages/Calendar/modules/UpcomingList.tsx @@ -3,7 +3,9 @@ import { FormControl, InputLabel, MenuItem, + Pagination, Select, + Typography, } from '@mui/material' import { format, isSameMonth, isSameYear } from 'date-fns' import { useState } from 'react' @@ -15,8 +17,11 @@ interface UpcomingListProps { games: GameList[] } +const ITEMS_PER_PAGE = 10 + function UpcomingList(props: UpcomingListProps) { const { games } = props + const [currentPage, setCurrentPage] = useState(1) const [selectedMonth, setSelectedMonth] = useState( new Date().getMonth(), ) @@ -26,7 +31,7 @@ function UpcomingList(props: UpcomingListProps) { const months = Array.from({ length: 12 }, (_, i) => i) const currentYear = new Date().getFullYear() - const years = Array.from({ length: 5 }, (_, i) => currentYear - 2 + i) + const years = Array.from({ length: 2 }, (_, i) => currentYear - i) const filteredGames = games.filter( (game) => @@ -40,14 +45,28 @@ function UpcomingList(props: UpcomingListProps) { ), ) + const totalGames = filteredGames.length + const totalPages = Math.ceil(totalGames / ITEMS_PER_PAGE) + const paginatedGames = filteredGames.slice( + (currentPage - 1) * ITEMS_PER_PAGE, + currentPage * ITEMS_PER_PAGE, + ) + + const handlePageChange = (_: any, page: number) => { + setCurrentPage(page) + } + return ( - + Month - + Year + + + className="text-theme-red-900" + rel="noopener noreferrer"> click here . diff --git a/src/services/api.ts b/src/services/api.ts index 5ac1df3..444ec9c 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -69,7 +69,7 @@ const api = createApi({ endpoints: (builder) => ({ login: builder.mutation<{ message: string }, LoginCredentials>({ query: (body) => ({ - url: 'auth/login', + url: 'login', method: 'POST', body, }), @@ -78,7 +78,7 @@ const api = createApi({ register: builder.mutation<{ message: string }, RegisterCredentials>({ query: (body) => ({ - url: 'auth/register', + url: 'register', method: 'POST', body, }), @@ -86,7 +86,7 @@ const api = createApi({ forgot: builder.mutation<{ message: string }, { email: string }>({ query: (body) => ({ - url: 'auth/password/email/send', + url: 'password/reset/notify', method: 'POST', body, }), @@ -95,7 +95,7 @@ const api = createApi({ resetPass: builder.mutation<{ message: string }, ResetPasswordPayload>( { query: (body) => ({ - url: 'auth/password/reset', + url: 'password/reset', method: 'POST', body, }), @@ -244,20 +244,9 @@ const api = createApi({ invalidatesTags: (_, error) => (!error ? ['notifications'] : []), }), - markAllNotificationsUnread: builder.mutation< - { message: string }, - void - >({ - query: () => ({ - url: 'notifications/all/unread', - method: 'PUT', - }), - invalidatesTags: (_, error) => (!error ? ['notifications'] : []), - }), - deleteAllNotifications: builder.mutation<{ message: string }, void>({ query: () => ({ - url: 'notifications/all', + url: 'notifications/all/remove', method: 'DELETE', }), invalidatesTags: (_, error) => (!error ? ['notifications'] : []), @@ -300,7 +289,7 @@ const api = createApi({ searchGames: builder.query({ query: (search) => ({ url: 'games/search', - params: { search }, + params: { q: search }, }), transformResponse: (res: Res) => res.data, }), @@ -382,7 +371,6 @@ export const { useMarkNotificationUnreadMutation, useDeleteAllNotificationsMutation, useMarkAllNotificationsReadMutation, - useMarkAllNotificationsUnreadMutation, } = api export default api diff --git a/src/types/banners.ts b/src/types/banners.ts index 66a006e..86d4680 100644 --- a/src/types/banners.ts +++ b/src/types/banners.ts @@ -1,7 +1,7 @@ import { GameList } from '.' -export interface Banner { +export interface GameBanner { id: number - bannerable_type: string - game: GameList + type: string + bannerable: GameList } diff --git a/src/types/cracks.ts b/src/types/cracks.ts index b53b798..eceaefa 100644 --- a/src/types/cracks.ts +++ b/src/types/cracks.ts @@ -1,16 +1,21 @@ import { Protection } from '.' -export interface Crackers { +export interface Cracker { id: number name: string slug: string acting: boolean } +export interface CrackStatus { + id: number + name: 'cracked' | 'uncracked' | 'cracked-oneday' +} + export interface Crack { id: number - status: 'cracked' | 'uncracked' | 'cracked-oneday' cracked_at: string | null - by: Crackers | null + cracker: Cracker | null + status: CrackStatus protection: Protection } diff --git a/src/types/dlcs.ts b/src/types/dlcs.ts index c71c90b..d328b27 100644 --- a/src/types/dlcs.ts +++ b/src/types/dlcs.ts @@ -1,13 +1,29 @@ -import { DLCStore, Gallery, Platform } from '.' +import { + Category, + Developer, + DLCStore, + Gallery, + Genre, + Platform, + Publisher, + Tag, +} from '.' export interface DLC { id: number - name: string + slug: string + title: string about?: string cover: string release_date: string description?: string short_description?: string + legal?: string + categories: Category[] + tags: Tag[] + developers: Developer[] + publishers: Publisher[] + genres: Genre[] stores: DLCStore[] galleries: Gallery[] platforms: Platform[] diff --git a/src/types/events.ts b/src/types/events.ts index 6466aa4..5177e00 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -1,6 +1,6 @@ export interface Notification { id: number - content: { + data: { title: string actionUrl: string icon: string diff --git a/src/types/galleries.ts b/src/types/galleries.ts index 8c10529..60e0529 100644 --- a/src/types/galleries.ts +++ b/src/types/galleries.ts @@ -6,5 +6,5 @@ export interface MediaType { export interface Gallery { id: number path: string - media_type: MediaType + type: MediaType } diff --git a/src/types/home.ts b/src/types/home.ts index 428dc47..ffbc956 100644 --- a/src/types/home.ts +++ b/src/types/home.ts @@ -1,10 +1,10 @@ -import { Banner, GameList, NextRelease } from '.' +import { GameBanner, GameList, NextRelease } from '.' export interface Home { hot: GameList[] - banners: Banner[] popular: GameList[] + banners: GameBanner[] next_release: NextRelease - upcoming_games: GameList[] - most_liked_games: GameList[] + upcoming: GameList[] + most_liked: GameList[] } diff --git a/src/types/missions.ts b/src/types/missions.ts index 5d40662..b0ee8af 100644 --- a/src/types/missions.ts +++ b/src/types/missions.ts @@ -28,17 +28,22 @@ export interface MissionRequirement { progress: MissionProgress | null } +export interface MissionStatus { + id: number + name: 'available' | 'unavailable' | 'canceled' +} + export interface Mission { id: number mission: string description: string coins: number experience: number - status: 'available' | 'unavailable' | 'canceled' - frequency: 'one-time' | 'daily' | 'weekly' | 'monthly' + status: MissionStatus + frequency: 'one_time' | 'daily' | 'weekly' | 'monthly' reset_time: string created_at: string rewards: MissionReward[] requirements: MissionRequirement[] - user_mission: UserMission | null + progress: UserMission | null } diff --git a/src/types/requirements.ts b/src/types/requirements.ts index 4c5844e..25a319d 100644 --- a/src/types/requirements.ts +++ b/src/types/requirements.ts @@ -1,7 +1,7 @@ export interface RequirementType { id: number - os: 'minimum' | 'recommended' | 'maximum' - potential: 'windows' | 'mac' | 'linux' + os: 'windows' | 'mac' | 'linux' + potential: 'minimum' | 'recommended' | 'maximum' } export interface Requirement { @@ -14,5 +14,5 @@ export interface Requirement { rom: string obs?: string network: string - requirement_type: RequirementType + type: RequirementType } diff --git a/src/types/reviews.ts b/src/types/reviews.ts index aac5975..8be5e78 100644 --- a/src/types/reviews.ts +++ b/src/types/reviews.ts @@ -4,14 +4,14 @@ export interface Review { id: number rate: number review: string - played: boolean - user: MinimalUser + consumed: boolean + by: MinimalUser } export interface ReviewStore { rate: number gameId: number userId: number - played: boolean + consumed: boolean review?: string } diff --git a/src/types/rewards.ts b/src/types/rewards.ts index 628dfd9..2ad3be6 100644 --- a/src/types/rewards.ts +++ b/src/types/rewards.ts @@ -1,7 +1,7 @@ +import { Mission } from './missions' + export interface BaseReward { id: number - created_at: string - updated_at: string sourceable_type: string rewardable_type: string } @@ -18,3 +18,7 @@ export interface TitleReward extends BaseReward { requirements: any | null } } + +export interface TitleRewardSource extends BaseReward { + sourceable: Mission +} diff --git a/src/types/titles.ts b/src/types/titles.ts index 1869038..c105f13 100644 --- a/src/types/titles.ts +++ b/src/types/titles.ts @@ -1,33 +1,19 @@ -export interface TitleProgress { - id: number - progress: number - completed: boolean - created_at: string - updated_at: string -} +import { TitleRewardSource } from './rewards' -export interface TitleRequirement { +export interface TitleStatus { id: number - task: string - goal: number - description?: string - created_at: string - updated_at: string - progress?: TitleProgress + name: 'available' | 'unavailable' | 'canceled' } export interface Title { id: number + own: boolean cost?: number title: string description: string purchasable: boolean - status: - | 'available' - | 'unavailable' - | 'completed' - | 'canceled' - | 'progress' + status: TitleStatus created_at: string - requirements: TitleRequirement[] + updated_at: string + rewardable?: TitleRewardSource } diff --git a/src/types/torrents.ts b/src/types/torrents.ts index c97b5f4..20e564b 100644 --- a/src/types/torrents.ts +++ b/src/types/torrents.ts @@ -2,11 +2,12 @@ export interface TorrentProvider { id: number url: string name: string + slug: string } export interface Torrent { id: number url: string - posted_in: string + posted_at: string provider: TorrentProvider } diff --git a/src/types/transactions.ts b/src/types/transactions.ts index 6caef1d..1f38a7c 100644 --- a/src/types/transactions.ts +++ b/src/types/transactions.ts @@ -1,7 +1,12 @@ -export interface Transaction { +export interface TransactionType { id: number type: string +} + +export interface Transaction { + id: number amount: number created_at: string description: string + type: TransactionType } diff --git a/src/types/user.ts b/src/types/user.ts index 74f37eb..07c6e5a 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -82,5 +82,5 @@ export interface Level { export interface Wallet { id: number - amount: number + balance: number } diff --git a/src/utils/number.helpers.test.ts b/src/utils/number.helpers.test.ts index d1d7506..f1e7ff4 100644 --- a/src/utils/number.helpers.test.ts +++ b/src/utils/number.helpers.test.ts @@ -1,4 +1,4 @@ -import { MissionRequirement, TitleRequirement } from '@/types' +import { MissionRequirement } from '@/types' import { calculateOverallProgress, formatPrice } from './number.helpers' @@ -21,7 +21,7 @@ describe('Utility Functions', () => { describe('calculateOverallProgress()', () => { it('calculates overall progress correctly with multiple requirements', () => { - const requirements: (MissionRequirement | TitleRequirement)[] = [ + const requirements: MissionRequirement[] = [ { id: 1, task: 'Fake task', @@ -46,7 +46,7 @@ describe('Utility Functions', () => { }) it('returns 100% if total progress meets or exceeds total goals', () => { - const requirements: (MissionRequirement | TitleRequirement)[] = [ + const requirements: MissionRequirement[] = [ { id: 1, task: 'Fake task', @@ -71,13 +71,13 @@ describe('Utility Functions', () => { }) it('returns 0% if no requirements are provided', () => { - const requirements: (MissionRequirement | TitleRequirement)[] = [] + const requirements: MissionRequirement[] = [] expect(calculateOverallProgress(requirements)).toBe(0) }) it('calculates progress correctly with different goals and progress', () => { - const requirements: (MissionRequirement | TitleRequirement)[] = [ + const requirements: MissionRequirement[] = [ { id: 1, task: 'Fake task', diff --git a/src/utils/number.helpers.ts b/src/utils/number.helpers.ts index f8f39b4..390afc0 100644 --- a/src/utils/number.helpers.ts +++ b/src/utils/number.helpers.ts @@ -1,4 +1,4 @@ -import { MissionRequirement, TitleRequirement } from '@/types' +import { MissionRequirement } from '@/types' export const formatPrice = (value: number | undefined) => { if (!value) return '$ 0.00' @@ -10,7 +10,7 @@ export const formatPrice = (value: number | undefined) => { } export const calculateOverallProgress = ( - requirements: (MissionRequirement | TitleRequirement)[], + requirements: MissionRequirement[], ): number => { if (requirements.length === 0) return 0