diff --git a/apps/keystone/migrations/20240505034646_added_external_schedule_links/migration.sql b/apps/keystone/migrations/20240505034646_added_external_schedule_links/migration.sql new file mode 100644 index 0000000..da28090 --- /dev/null +++ b/apps/keystone/migrations/20240505034646_added_external_schedule_links/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Event" ADD COLUMN "horaro" TEXT NOT NULL DEFAULT '', +ADD COLUMN "oengus" TEXT NOT NULL DEFAULT ''; diff --git a/apps/keystone/schema.graphql b/apps/keystone/schema.graphql index 0b04d6c..d84c42a 100644 --- a/apps/keystone/schema.graphql +++ b/apps/keystone/schema.graphql @@ -684,6 +684,8 @@ type Event { submissionInstructions: Event_submissionInstructions_Document eventPage: Event_eventPage_Document scheduleBlocks: JSON + horaro: String + oengus: String } type Event_postEventPage_Document { @@ -767,6 +769,8 @@ input EventWhereInput { tickets: TicketManyRelationFilter volunteer: VolunteerManyRelationFilter donationIncentives: IncentiveManyRelationFilter + horaro: StringFilter + oengus: StringFilter } input FloatNullableFilter { @@ -801,6 +805,8 @@ input EventOrderByInput { startDate: OrderDirection endDate: OrderDirection raised: OrderDirection + horaro: OrderDirection + oengus: OrderDirection } input EventUpdateInput { @@ -832,6 +838,8 @@ input EventUpdateInput { submissionInstructions: JSON eventPage: JSON scheduleBlocks: JSON + horaro: String + oengus: String } input IncentiveRelateToManyForUpdateInput { @@ -888,6 +896,8 @@ input EventCreateInput { submissionInstructions: JSON eventPage: JSON scheduleBlocks: JSON + horaro: String + oengus: String } input IncentiveRelateToManyForCreateInput { diff --git a/apps/keystone/schema.prisma b/apps/keystone/schema.prisma index 216fcef..b79e413 100644 --- a/apps/keystone/schema.prisma +++ b/apps/keystone/schema.prisma @@ -138,6 +138,8 @@ model Event { submissionInstructions Json @default("[{\"type\":\"paragraph\",\"children\":[{\"text\":\"\"}]}]") eventPage Json @default("[{\"type\":\"paragraph\",\"children\":[{\"text\":\"\"}]}]") scheduleBlocks Json? + horaro String @default("") + oengus String @default("") from_Post_event Post[] @relation("Post_event") from_Role_event Role[] @relation("Role_event") } diff --git a/apps/keystone/src/schema/event.ts b/apps/keystone/src/schema/event.ts index d019bc8..dd2271f 100644 --- a/apps/keystone/src/schema/event.ts +++ b/apps/keystone/src/schema/event.ts @@ -126,6 +126,13 @@ export const Event: Lists.Event = list({ }, componentBlocks: liveEventComponentBlocks, }), - scheduleBlocks: scheduleBlocks(), + ...group({ + label: "Schedule Metadata", + fields: { + scheduleBlocks: scheduleBlocks(), + horaro: text(), + oengus: text(), + } + }), } }); diff --git a/apps/nextjs/components/Footer/Footer.module.scss b/apps/nextjs/components/Footer/Footer.module.scss index 1d96ff0..98fd8d2 100644 --- a/apps/nextjs/components/Footer/Footer.module.scss +++ b/apps/nextjs/components/Footer/Footer.module.scss @@ -15,6 +15,7 @@ ul { justify-content: center; + flex-wrap: wrap; @include breakpoint($sm-zero-only) { flex-direction: column; diff --git a/apps/nextjs/components/Heroblock/Heroblock.module.scss b/apps/nextjs/components/Heroblock/Heroblock.module.scss index b770606..4264950 100644 --- a/apps/nextjs/components/Heroblock/Heroblock.module.scss +++ b/apps/nextjs/components/Heroblock/Heroblock.module.scss @@ -37,6 +37,10 @@ } } + p { + text-wrap: balance; + } + .ctaBlock { text-align: left; padding: 25px; diff --git a/apps/nextjs/components/Heroblock/Heroblock.tsx b/apps/nextjs/components/Heroblock/Heroblock.tsx index a745af0..aaef5fe 100644 --- a/apps/nextjs/components/Heroblock/Heroblock.tsx +++ b/apps/nextjs/components/Heroblock/Heroblock.tsx @@ -112,8 +112,7 @@ const HeroBlock = ({ event, tagLine, darkText, schedule, submitRuns, ticketLink

- {tagLine ?? - "We will be at The Game Expo! The schedule has been released!"} + {tagLine}

+ )} + {event.oengus && ( + + )} + + + }>Filters + +
Run Type - Donation Incentives + Has Donation Incentives
-
- Platform + Console
{consoleFilterElements}
- +
+
+
+ {currentRunIndex > -1 && ( +
+

Currently Running

+ +
+ )} +
+
+ {getAllDays(runsWithOrder, event.eventTimezone, settings.showLocalTime).map((day) => { + const date = new Date(day); + return ( + + ); + })} +
+ {generateRunItems(runsWithOrder, settings, event.eventTimezone, scheduleBlocks)} +
@@ -380,13 +433,8 @@ function generateSubmissionBlockMap(blocks: Block[], allRuns: QUERY_EVENT_RESULT return blockRunMap; } -function getRunDate(scheduledTime: string, localTime?: string) { - return parseInt( - new Date(scheduledTime).toLocaleDateString(undefined, { - timeZone: localTime, - day: "numeric", - }), - ); +function runEstimateToSeconds(estimate: string) { + return estimate.split(":").reduce((acc, time) => 60 * acc + parseInt(time), 0); } enum BorderState { @@ -395,116 +443,215 @@ enum BorderState { IN_BLOCK, } -function generateRunItems( - sortedRuns: QUERY_EVENT_RESULTS["event"]["runs"], - settings: typeof SETTINGS, - eventTimezone: string, - blocks: Map, -) { - let prevDate = 0; +function getAllDays(runs: Run[], eventTimezone: string, showLocalTime: boolean) { + let days: string[] = []; - const filteredRuns = FilterRuns(sortedRuns, settings.filter); - const runs: JSX.Element[] = []; - - let blockRuns: JSX.Element[] = []; - let scheduleBlockData: Block | undefined; - let removedBorder: BorderState = BorderState.KEEP_BORDER; - let previousBlockData: Block | undefined; - - for (let index = 0; index < filteredRuns.length; index++) { - const run = filteredRuns[index]; - const runDate = getRunDate(run.scheduledTime, settings.showLocalTime ? eventTimezone : undefined); - const nextRunDate = filteredRuns[index + 1] - ? getRunDate(filteredRuns[index + 1].scheduledTime, settings.showLocalTime ? eventTimezone : undefined) - : runDate; - - scheduleBlockData = blocks.get(run.id); - - // Check if we are on a new block, if we are in one check if we were previously in one and post it - if (scheduleBlockData !== previousBlockData && previousBlockData) { - runs.push( - - {blockRuns} - , - ); - - // Reset for next block - blockRuns = []; + for (let i = 0; i < runs.length; i++) { + const runDate = new Date(runs[i].scheduledTime).toLocaleString("en-US", { + timeZone: showLocalTime ? eventTimezone : undefined, + day: "numeric", + month: "numeric", + year: "numeric", + }); + + if (!days.includes(runDate)) { + days.push(runDate); } + } - if (scheduleBlockData) removedBorder = BorderState.KEEP_BORDER; - if (!scheduleBlockData && blocks.get(filteredRuns[index + 1]?.id)) removedBorder = BorderState.REMOVE_BORDER; + const sortedDays = days.sort((a, b) => { + const dateA = new Date(a); + const dateB = new Date(b); + return dateA.getTime() - dateB.getTime(); + }); - if (prevDate !== runDate) { - // End block if we're at a new day - // And check that we have runs to post as well... Goodbye Reginald o7 - if (scheduleBlockData && blockRuns.length > 0) { - runs.push({blockRuns}); - blockRuns = []; - } + return days; +} - runs.push( - , - ); - } +function runsToDays(runs: Run[], eventTimezone: string, showLocalTime: boolean) { + let days: Record = {}; - (scheduleBlockData ? blockRuns : runs).push( - , - ); + for (let i = 0; i < runs.length; i++) { + const run = runs[i]; + const runDate = new Date(run.scheduledTime).toLocaleString("en-US", { + timeZone: showLocalTime ? eventTimezone : undefined, + day: "numeric", + month: "numeric", + year: "numeric", + }); - prevDate = runDate; - previousBlockData = scheduleBlockData; + if (runDate in days) { + days[runDate].push(run); + } else { + days[runDate] = [run]; + } } - if (blockRuns.length > 0 && previousBlockData) { - runs.push( - - {blockRuns} - , - ); - } + const sortedDays = Object.entries(days).sort((a, b) => { + const dateA = new Date(a[0]); + const dateB = new Date(b[0]); + return dateA.getTime() - dateB.getTime(); + }); - return runs; + return sortedDays.map(([day, runs]) => ({ + day, + runs, + })); +} + +const RunHover = styled(({ className, odd, block, ...props }: TooltipProps & { odd: boolean; block?: Block }) => ( + +))(({ odd, block }) => ({ + [`& .${tooltipClasses.tooltip}`]: { + backgroundColor: block?.colour ? `${block.colour}a6` : odd ? "#cc7722a6" : "#437c90a6", + color: block?.textColour ? block.textColour : "#fff", + border: `1px solid ${block?.colour ? block.colour : odd ? "#cc7722" : "#437c90"}`, + backdropFilter: "blur(10px)", + display: "flex", + flexDirection: "column", + alignItems: "center", + maxWidth: 500, + }, +})); + +function generateRunItems( + sortedRuns: Run[], + settings: typeof SETTINGS, + eventTimezone: string, + blocks: Map, +) { + const filteredRuns = FilterRuns(sortedRuns, settings.filter); + const runDays = runsToDays(filteredRuns, eventTimezone, settings.showLocalTime); + + return ( +
+ {runDays.map(({ day, runs }, i) => { + let yesterdayRunTime = 0; + + if (i != 0) { + const yesterdaysFinalRun = runDays[i - 1].runs.at(-1); + + if (yesterdaysFinalRun) { + const endOfYesterdayRun = addSeconds( + new Date(yesterdaysFinalRun.scheduledTime), + runEstimateToSeconds(yesterdaysFinalRun.estimate), + ); + + const startOfFirstRun = new Date(runs[0].scheduledTime); + + const timeBetweenRuns = new Date(startOfFirstRun.getTime() - endOfYesterdayRun.getTime()); + const timeBetweenRunsSeconds = timeBetweenRuns.getTime() / (1000 * 60); + + if (timeBetweenRunsSeconds <= 30) { + // Within 30 minutes, calculate the time difference to add to the start of the day + const startOfToday = new Date( + startOfFirstRun.getFullYear(), + startOfFirstRun.getMonth(), + startOfFirstRun.getDate(), + ); + const yesterdayRunExtraTime = endOfYesterdayRun.getTime() - startOfToday.getTime(); + yesterdayRunTime = yesterdayRunExtraTime / 1000; + } + } + } + + const totalSeconds = + runs.reduce((acc, run) => acc + Math.max(runEstimateToSeconds(run.estimate), 300), 0) + + yesterdayRunTime; + const runDay = new Date(day); + + return ( +
+ +
+
+ {yesterdayRunTime > 0 && ( + + )} + {runs.map((run, i) => { + let width = + (Math.max(runEstimateToSeconds(run.estimate), 300) / totalSeconds) * 100; + + if (i == runs.length - 1) { + // If the run goes to the next day, subtract the overlap + + const runScheduledTime = new Date(run.scheduledTime); + const finalRunEndTime = addSeconds( + runScheduledTime, + runEstimateToSeconds(run.estimate), + ); + + const nextDay = new Date( + runScheduledTime.getFullYear(), + runScheduledTime.getMonth(), + runScheduledTime.getDate(), + ); + + nextDay.setDate(nextDay.getDate() + 1); + + if (nextDay.getDate() === finalRunEndTime.getDate()) { + const overlapTime = nextDay.getTime() - finalRunEndTime.getTime(); + const overlapSeconds = overlapTime / 1000; + width = + ((runEstimateToSeconds(run.estimate) - overlapSeconds) / totalSeconds) * + 100; + } + } + + return ; + })} +
+
+ {runs.map((run) => ( + + ))} +
+
+
+ ); + })} +
+ ); } interface DateDividerProps { date: Date; - showLocalTime: boolean; - eventTimezone: string; } const DateDivider: React.FC = (props: DateDividerProps) => { - const dateString = props.showLocalTime - ? formatInTimeZone(props.date, props.eventTimezone, "EEEE do, MMMM") - : format(props.date, "EEEE do, MMMM"); - return
{dateString}
; + const dateString = format(props.date, "EEEE do, MMMM"); + return ( +
window.scrollTo({ top: 0, behavior: "smooth" })}> + {dateString} +
+ ); }; interface RunItemProps { - run: QUERY_EVENT_RESULTS["event"]["runs"][0]; + run: Run; showLocalTime: boolean; eventTimezone: string; isLive?: boolean; + block?: Block; style?: React.CSSProperties; } // Runner parsing -function runnerParsing(runnersArray: QUERY_EVENT_RESULTS["event"]["runs"][0]["runners"]) { +function runnerParsing(runnersArray: Run["runners"]) { + if (runnersArray.length == 0) { + return <>???; + } + return (
{runnersArray.map((runner, i) => { @@ -559,7 +706,7 @@ const RunItem: React.FC = (props: RunItemProps) => { }) : new Date(run.scheduledTime).toLocaleTimeString("en-AU", runItemOptions); - if (convertedTimezone[0] === "0") convertedTimezone = " " + convertedTimezone.substring(1); + if (convertedTimezone[0] === "0") convertedTimezone = convertedTimezone.substring(1); if (run.game === "Setup Buffer") { return ( @@ -570,8 +717,8 @@ const RunItem: React.FC = (props: RunItemProps) => { } let categoryExtras = <>; - if (run.race) categoryExtras = RACE ; - if (run.coop) categoryExtras = CO-OP ; + if (run.race) categoryExtras = RACE; + if (run.coop) categoryExtras = CO-OP; const runClassNames = [styles.run]; if (props.isLive) runClassNames.push(styles.liveRun); @@ -579,28 +726,104 @@ const RunItem: React.FC = (props: RunItemProps) => { const estimateSplit = run.estimate.split(":"); const hours = parseInt(estimateSplit[0]); const minutes = parseInt(estimateSplit[1]); - const formattedHours = hours === 0 ? " 0" : hours < 10 ? ` ${hours}` : hours.toString().padStart(2, " "); + const formattedHours = hours.toString(); const formattedMinutes = minutes.toString().padStart(2, "0"); const estimateText = `${formattedHours}:${formattedMinutes}:00`; + // Bad hardcoding bad! + let overwriteFilter; + if (props.block) { + if (props.block.textColour === "#ffffff") { + overwriteFilter = "invert(100%)"; + } else { + overwriteFilter = "unset"; + } + } + return ( -
- {convertedTimezone} +
+ + {convertedTimezone} + + {props.block?.name && {props.block?.name}} {run.game} - + {categoryExtras} {run.category} - {run.runners.length > 0 ? runnerParsing(run.runners) : run.racer} - {estimateText} - {run.platform} - - {run.donationIncentiveObject?.map((incentive) => incentive.title).join(" | ")} - +
+ + + {run.runners.length > 0 ? runnerParsing(run.runners) : run.racer} + + + + {estimateText} + + + + {run.platform} + +
+ {run.donationIncentiveObject && run.donationIncentiveObject.length > 0 && ( +
+ + Incentives + + {run.donationIncentiveObject?.map((incentive) => ( + + {incentive.title} + + ))} +
+ )}
); }; +const RunVisualiser = ({ run, proportion, block }: { run: Run; proportion: number; block?: Block }) => { + const oddRun = run.order % 2 != 0; + + return ( + + {block &&

{block.name}

} +

{run.game}

+

{run.category}

+

+ + {run.runners.length > 0 ? runnerParsing(run.runners) : run.racer} +

+
+ } + run-odd={oddRun.toString()} + odd={oddRun} + block={block}> +
{ + const runElement = document.querySelector(`#${run.id}`); + if (runElement != null) { + runElement.scrollIntoView({ behavior: "smooth", block: "center" }); + } + }} + /> + + ); +}; + export const getServerSideProps: GetServerSideProps = async (ctx) => { const ssrCache = ssrExchange({ isClient: false }); const client = initUrqlClient( @@ -609,7 +832,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { process.env.NODE_ENV === "production" ? "https://keystone.ausspeedruns.com/api/graphql" : "http://localhost:8000/api/graphql", - exchanges: [dedupExchange, cacheExchange, ssrCache, fetchExchange], + exchanges: [cacheExchange, ssrCache, fetchExchange], }, false, ); diff --git a/apps/nextjs/pages/index.tsx b/apps/nextjs/pages/index.tsx index e5da534..ca4af34 100644 --- a/apps/nextjs/pages/index.tsx +++ b/apps/nextjs/pages/index.tsx @@ -106,22 +106,10 @@ export default function Home() { {/* */} - {/* */} span { - padding-bottom: 4px; - } +.externalSchedules { + display: flex; + flex-wrap: wrap; + gap: 8px; +} - .scheduleHeader { - display: grid; - grid-template-columns: min-content repeat(3, 1fr); - border: 1px solid $secondary; - border-radius: 5px; +.day { + display: grid; + // grid-template-columns: 10% 90%; + gap: 16px; +} - span { - padding: 8px; - } +.visualiser { + display: flex; + // flex-direction: column; + gap: 1px; + margin-bottom: 1rem; - .topRow { - border-bottom: 1px solid $secondary; - } + .visualiserRun { + cursor: pointer; + height: 40px; + flex-grow: 1; - .notLast { - border-right: 1px solid $secondary; + &[run-odd="true"] { + background: $primary; } - .donationIncentive { - grid-column: 3 / 5; - font-style: italic; + &[run-odd="false"] { + background: $secondary; } } } -.localTimeToggle { - margin: 0.5rem auto !important; +.visualiserTooltip { + // cursor: pointer; + + * { + text-align: center; + text-wrap: balance; + } + + a { + color: inherit; + text-decoration: none; + } + + h3 { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + + img { + filter: invert(100%); + } + } } .schedule { // border: 1px solid $color-off-white; display: flex; flex-direction: column; - align-items: end; + width: 100%; + + .dayButtons { + display: flex; + gap: 8px; + + @include breakpoint($sm-zero-only) { + flex-wrap: wrap; + } + } .dateDivider { - max-width: $normalWidth; width: 100%; - border-bottom: 5px solid $primary; + border-bottom: 2px solid $primary; color: $secondary; font-weight: bold; font-size: 1.3rem; padding: 1rem; margin-top: 1rem; + text-align: center; + + position: sticky; + top: 0; + background: rgba(255, 255, 255, 0.808); + backdrop-filter: blur(10px); + z-index: 2; } .setupBuffer { @@ -154,47 +186,185 @@ $normalWidth: 800px; border-bottom: 2px solid $secondary; } + .runs { + padding: 0 1rem; + } + .run { - max-width: $normalWidth; - width: $normalWidth; - padding: 4px; - display: grid; - grid-template-columns: min-content repeat(3, 1fr); - border-bottom: 2px solid $secondary; + padding: 8px; + margin-top: 4px; + display: flex; + flex-direction: column; + align-items: center; + font-size: 1.1rem; + border-radius: 20px 16px 16px 16px; + position: relative; + width: 100%; - &:nth-child(odd) { - background: #f9f9f900; + @include breakpoint($sm-zero-only) { + border-radius: 16px; + } + + + &[run-odd="true"] { + background: $primary-50; + --icon-filter: invert(48%) sepia(57%) saturate(650%) hue-rotate(349deg) brightness(97%) contrast(89%); + --colour-accent: #{$primary-600}; + --colour-full: #{$primary-200}; + } + + &[run-odd="false"] { + background: $secondary-50; + --icon-filter: invert(46%) sepia(14%) saturate(1227%) hue-rotate(150deg) brightness(94%) contrast(98%); + --colour-accent: #{$secondary-600}; + --colour-full: #{$secondary-200}; } & > span { padding: 4px; + text-align: center; + text-wrap: balance; } .game { + font-family: Russo One; + font-size: 170%; + margin-bottom: -0.25rem; + max-width: 70%; + + @include breakpoint($sm-zero-only) { + max-width: 100%; + } + } + + .category { font-weight: bold; + font-size: 120%; + margin-bottom: 0.5rem; + color: var(--colour-accent); + } + + .metaData { + width: 100%; + display: grid; + gap: 32px; + grid-template-columns: 1fr auto 1fr; + color: var(--colour-accent); + padding: 0 8px; + + @include breakpoint($sm-zero-only) { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 16px; + } + } + + .runners, + .estimate, + .platform { + display: flex; + align-items: center; + gap: 8px; + + img { + height: 1.5rem; + width: 1.5rem; + filter: var(--icon-filter); + } + } + + .runners, + .platform { + flex-grow: 1; + + @include breakpoint($sm-zero) { + flex-grow: 0; + } + } + + .runners { + text-align: center; + text-wrap: balance; + justify-content: flex-end; + + a { + color: var(--color-accent); + } } .time { + padding: 8px; + font-weight: bold; + font-size: 120%; white-space: nowrap; text-transform: uppercase; - margin-right: 8px; + min-width: 115px; + + position: absolute; + top: 0; + background: var(--colour-accent); + left: 0; + border-radius: 16px 0; + color: $light-text; + + @include breakpoint($sm-zero-only) { + position: relative; + width: 100%; + border-radius: 8px 8px 0px 0px; + } } .categoryExtras { font-weight: bold; - // margin-left: -4px; + background: var(--colour-accent); + color: $light-text; + margin-right: 12px; + padding: 0 6px; } - a { - color: $dark-text; - // text-decoration: none; - // font-weight: 600; - // margin: 0 -2px; + .donationIncentives { + margin-top: 0.5rem; + display: flex; + flex-direction: column; + align-items: center; + color: var(--colour-accent); + border: 3px solid var(--colour-accent); + padding: 8px; + border-radius: 8px; + + .title { + display: flex; + align-items: center; + } } .donationIncentive { - grid-column: 3 / 5; font-style: italic; + font-weight: bold; + } + + .blockName { + position: absolute; + top: 0; + right: 0; + padding: 8px; + font-weight: bold; + font-size: 120%; + border: 3px solid white; + border-top: 0; + border-right: 0; + border-bottom-left-radius: 16px; + + @include breakpoint($sm-zero-only) { + position: relative; + width: 100%; + border-radius: 0; + border-left: 3px solid white; + border-right: 3px solid white; + border-radius: 0 0 8px 8px; + margin-bottom: 0.5rem; + } } @include breakpoint($sm-zero-only) { diff --git a/apps/nextjs/styles/colors.scss b/apps/nextjs/styles/colors.scss index ca1e83a..f07561c 100644 --- a/apps/nextjs/styles/colors.scss +++ b/apps/nextjs/styles/colors.scss @@ -58,3 +58,30 @@ $dh-red: #FF0046; $dh-orange-to-orange: linear-gradient(90deg, $dh-light-orange, $dh-orange); $dh-yellow-to-orange: linear-gradient(90deg, $dh-yellow, $dh-light-orange); $dh-orange-to-red: linear-gradient(90deg, $dh-orange, $dh-red); + +// Tailwind Colours + +$primary-50: #fdf9ed; +$primary-100: #f7eece; +$primary-200: #efdb98; +$primary-300: #e7c362; +$primary-400: #e1ad3e; +$primary-500: #d89128; +$primary-600: #cc7722; +$primary-700: #9f511e; +$primary-800: #82401e; +$primary-900: #6b361c; +$primary-950: #3d1a0b; + +$secondary-50: #f2f8f9; +$secondary-100: #ddecf0; +$secondary-200: #c0dae1; +$secondary-300: #94c0cc; +$secondary-400: #619caf; +$secondary-500: #437c90; +$secondary-600: #3c697e; +$secondary-700: #365768; +$secondary-800: #324b58; +$secondary-900: #2e404b; +$secondary-950: #1a2832; + diff --git a/apps/nextjs/styles/img/icons/console.svg b/apps/nextjs/styles/img/icons/console.svg new file mode 100644 index 0000000..9ee4ea3 --- /dev/null +++ b/apps/nextjs/styles/img/icons/console.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/nextjs/styles/img/icons/runner.svg b/apps/nextjs/styles/img/icons/runner.svg new file mode 100644 index 0000000..4f9986d --- /dev/null +++ b/apps/nextjs/styles/img/icons/runner.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/nextjs/styles/img/icons/stopwatch.svg b/apps/nextjs/styles/img/icons/stopwatch.svg new file mode 100644 index 0000000..6ef3145 --- /dev/null +++ b/apps/nextjs/styles/img/icons/stopwatch.svg @@ -0,0 +1,3 @@ + + + diff --git a/package-lock.json b/package-lock.json index 9f04654..aab7807 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10933,6 +10933,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -10948,6 +10949,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -10963,6 +10965,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -10978,6 +10981,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -10993,6 +10997,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -11008,6 +11013,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -11023,6 +11029,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -11038,6 +11045,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -11053,6 +11061,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -11068,6 +11077,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -35176,60 +35186,70 @@ "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.102.tgz", "integrity": "sha512-CJDxA5Wd2cUMULj3bjx4GEoiYyyiyL8oIOu4Nhrs9X+tlg8DnkCm4nI57RJGP8Mf6BaXPIJkHX8yjcefK2RlDA==", + "dev": true, "optional": true }, "@swc/core-darwin-x64": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.102.tgz", "integrity": "sha512-X5akDkHwk6oAer49oER0qZMjNMkLH3IOZaV1m98uXIasAGyjo5WH1MKPeMLY1sY6V6TrufzwiSwD4ds571ytcg==", + "dev": true, "optional": true }, "@swc/core-linux-arm-gnueabihf": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.102.tgz", "integrity": "sha512-kJH3XtZP9YQdjq/wYVBeFuiVQl4HaC4WwRrIxAHwe2OyvrwUI43dpW3LpxSggBnxXcVCXYWf36sTnv8S75o2Gw==", + "dev": true, "optional": true }, "@swc/core-linux-arm64-gnu": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.102.tgz", "integrity": "sha512-flQP2WDyCgO24WmKA1wjjTx+xfCmavUete2Kp6yrM+631IHLGnr17eu7rYJ/d4EnDBId/ytMyrnWbTVkaVrpbQ==", + "dev": true, "optional": true }, "@swc/core-linux-arm64-musl": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.102.tgz", "integrity": "sha512-bQEQSnC44DyoIGLw1+fNXKVGoCHi7eJOHr8BdH0y1ooy9ArskMjwobBFae3GX4T1AfnrTaejyr0FvLYIb0Zkog==", + "dev": true, "optional": true }, "@swc/core-linux-x64-gnu": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.102.tgz", "integrity": "sha512-dFvnhpI478svQSxqISMt00MKTDS0e4YtIr+ioZDG/uJ/q+RpcNy3QI2KMm05Fsc8Y0d4krVtvCKWgfUMsJZXAg==", + "dev": true, "optional": true }, "@swc/core-linux-x64-musl": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.102.tgz", "integrity": "sha512-+a0M3CvjeIRNA/jTCzWEDh2V+mhKGvLreHOL7J97oULZy5yg4gf7h8lQX9J8t9QLbf6fsk+0F8bVH1Ie/PbXjA==", + "dev": true, "optional": true }, "@swc/core-win32-arm64-msvc": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.102.tgz", "integrity": "sha512-w76JWLjkZNOfkB25nqdWUNCbt0zJ41CnWrJPZ+LxEai3zAnb2YtgB/cCIrwxDebRuMgE9EJXRj7gDDaTEAMOOQ==", + "dev": true, "optional": true }, "@swc/core-win32-ia32-msvc": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.102.tgz", "integrity": "sha512-vlDb09HiGqKwz+2cxDS9T5/461ipUQBplvuhW+cCbzzGuPq8lll2xeyZU0N1E4Sz3MVdSPx1tJREuRvlQjrwNg==", + "dev": true, "optional": true }, "@swc/core-win32-x64-msvc": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.102.tgz", "integrity": "sha512-E/jfSD7sShllxBwwgDPeXp1UxvIqehj/ShSUqq1pjR/IDRXngcRSXKJK92mJkNFY7suH6BcCWwzrxZgkO7sWmw==", + "dev": true, "optional": true }, "@swc/counter": { diff --git a/package.json b/package.json index 7c73215..f4a3f16 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@mui/icons-material": "^5.15.4", "@mui/material": "^5.15.4", "@mui/x-date-pickers": "^6.19.0", + "@nx/plugin": "16.6.0", "@stripe/stripe-js": "^2.3.0", "@swc/helpers": "0.5.3", "animejs": "^3.2.2", @@ -60,8 +61,7 @@ "underscore": "^1.13.6", "urql": "^4.0.6", "uuid": "^9.0.1", - "zod": "^3.22.4", - "@nx/plugin": "16.6.0" + "zod": "^3.22.4" }, "devDependencies": { "@babel/preset-react": "^7.23.3", @@ -70,6 +70,7 @@ "@nx-tools/nx-container": "^5.1.0", "@nx/devkit": "16.6.0", "@nx/eslint-plugin": "16.6.0", + "@nx/jest": "16.6.0", "@nx/js": "16.6.0", "@nx/linter": "16.6.0", "@nx/next": "16.6.0", @@ -129,7 +130,6 @@ "vite": "4.4.8", "vite-plugin-eslint": "^1.8.1", "vite-tsconfig-paths": "4.2.3", - "vitest": "0.34.1", - "@nx/jest": "16.6.0" + "vitest": "0.34.1" } }