From 955f9ab6d572d7e8fbb3f3dc24af37fb01ba26e0 Mon Sep 17 00:00:00 2001 From: Jason Boyett Date: Wed, 6 Dec 2023 18:12:20 -0600 Subject: [PATCH 1/6] pre stream commit --- prisma/schema.prisma | 9 ++++++ src/components/evensandodds.tsx | 2 +- src/components/greendottext.tsx | 51 +++++++++++++++++++-------------- src/server/api/routers/app.ts | 15 ++++++++-- src/utils/types.ts | 4 ++- src/utils/validators.ts | 6 ++++ 6 files changed, 62 insertions(+), 25 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 08968a8..f3eac9a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -90,6 +90,7 @@ model User { lastNumberGuesser String @default(" ") lastLetterMatcher String @default(" ") lastGreenDot String @default(" ") + lastWordPair String @default(" ") numberGuesserFigures Int @default(4) font Font @default(sans) isUsingChecklist Boolean @default(true) @@ -242,3 +243,11 @@ model SpeedQuestion { @@index([id]) } + +model WordPair { + primaryWord String @id @unique + secondaryWord String + language Language @default(english) + + @@index([primaryWord]) +} diff --git a/src/components/evensandodds.tsx b/src/components/evensandodds.tsx index 0c8c3df..73fd655 100644 --- a/src/components/evensandodds.tsx +++ b/src/components/evensandodds.tsx @@ -11,7 +11,7 @@ import { formatDate, navigate } from '~/utils/helpers' const DEFAULT = 'flex text-white md:text-3xl text-xl justify-center md:p-4 p-2 bg-white/10 rounded-md' const HILIGHT = - 'flex text-white md:text-3xl text-xl justify-center md:p-4 p-2 bg-blue-500 gap-0 bg-slate-700/40 rounded-md' + 'flex text-white md:text-3xl text-1xl justify-center md:p-4 p-2 bg-blue-500 gap-0 bg-slate-700/40 rounded-md' type EvenOddProps = { segFigs: number diff --git a/src/components/greendottext.tsx b/src/components/greendottext.tsx index e6d8235..ab793d9 100644 --- a/src/components/greendottext.tsx +++ b/src/components/greendottext.tsx @@ -1,26 +1,35 @@ export default function BackgroundText() { - return( + return (
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eu ultrices est. Vestibulum egestas libero eget placerat tincidunt. Quisque ultricies odio ac mattis interdum. Aliquam erat volutpat. Praesent metus nisl, vulputate a interdum vel, feugiat et odio. Integer finibus nunc in nisi elementum ullamcorper. Etiam nisi sem, accumsan pulvinar lorem tempus, mollis mollis ipsum. Nulla at cursus ante, id efficitur est. Donec tellus urna, pharetra at aliquam vitae, imperdiet eu quam. Phasellus posuere semper imperdiet. Curabitur id purus cursus ipsum bibendum semper iaculis in sapien. - - Donec tristique pharetra leo, pulvinar malesuada risus facilisis vel. Nulla lorem tortor, molestie sed mi sit amet, efficitur efficitur elit. Fusce sed hendrerit nisi. Duis ex nunc, maximus eget nibh molestie, laoreet rutrum ex. Integer urna turpis, fermentum in tempus condimentum, feugiat vitae sapien. Morbi commodo mattis lacinia. Donec sit amet orci consequat enim molestie auctor vitae a eros. Donec bibendum, odio non porta venenatis, sem mi posuere dui, quis elementum ipsum lectus at nibh. - - Aenean nunc neque, luctus fringilla blandit viverra, vestibulum condimentum ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed vitae scelerisque diam. Sed placerat, ipsum a eleifend imperdiet, ligula dui pretium nulla, dapibus mollis enim felis id lectus. Duis varius dapibus suscipit. Quisque lacinia ullamcorper ligula, non mattis arcu ullamcorper ac. Suspendisse potenti. Donec tincidunt, mauris ac ornare gravida, augue justo semper erat, in fringilla arcu dui quis lectus. Ut sodales semper fermentum. Etiam lacinia risus ac aliquet finibus. - - Vivamus fringilla dolor eu justo elementum maximus. Ut vitae turpis purus. Sed pharetra, massa et blandit fringilla, magna diam convallis felis, eget elementum massa ex non purus. Donec malesuada at ipsum vel mattis. Fusce dolor arcu, efficitur sed lectus eu, mollis dignissim augue. Integer erat ipsum, efficitur et eros eu, porta consectetur eros. Donec suscipit dictum mi, eget cursus nulla iaculis sed. Praesent nisl arcu, condimentum mattis sem eu, bibendum molestie erat. Aenean congue diam eget feugiat pulvinar. Mauris lacinia massa et efficitur rutrum. Sed euismod eleifend dui vel dapibus. In fringilla dui eget pretium condimentum. Vestibulum non sapien tellus. - - Mauris in dui vehicula, maximus justo sit amet, euismod metus. Quisque maximus nibh mauris, non aliquet sapien placerat sed. Nam id est auctor, laoreet nibh in, luctus nulla. Donec vestibulum dapibus nisi, vel pulvinar sapien scelerisque sit amet. Aliquam pretium erat a justo semper, non aliquam tortor aliquet. Donec rutrum convallis neque eget efficitur. Ut posuere consectetur magna sed congue. - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eu ultrices est. Vestibulum egestas libero eget placerat tincidunt. Quisque ultricies odio ac mattis interdum. Aliquam erat volutpat. Praesent metus nisl, vulputate a interdum vel, feugiat et odio. Integer finibus nunc in nisi elementum ullamcorper. Etiam nisi sem, accumsan pulvinar lorem tempus, mollis mollis ipsum. Nulla at cursus ante, id efficitur est. Donec tellus urna, pharetra at aliquam vitae, imperdiet eu quam. Phasellus posuere semper imperdiet. Curabitur id purus cursus ipsum bibendum semper iaculis in sapien. - - Donec tristique pharetra leo, pulvinar malesuada risus facilisis vel. Nulla lorem tortor, molestie sed mi sit amet, efficitur efficitur elit. Fusce sed hendrerit nisi. Duis ex nunc, maximus eget nibh molestie, laoreet rutrum ex. Integer urna turpis, fermentum in tempus condimentum, feugiat vitae sapien. Morbi commodo mattis lacinia. Donec sit amet orci consequat enim molestie auctor vitae a eros. Donec bibendum, odio non porta venenatis, sem mi posuere dui, quis elementum ipsum lectus at nibh. - - Aenean nunc neque, luctus fringilla blandit viverra, vestibulum condimentum ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed vitae scelerisque diam. Sed placerat, ipsum a eleifend imperdiet, ligula dui pretium nulla, dapibus mollis enim felis id lectus. Duis varius dapibus suscipit. Quisque lacinia ullamcorper ligula, non mattis arcu ullamcorper ac. Suspendisse potenti. Donec tincidunt, mauris ac ornare gravida, augue justo semper erat, in fringilla arcu dui quis lectus. Ut sodales semper fermentum. Etiam lacinia risus ac aliquet finibus. - - Vivamus fringilla dolor eu justo elementum maximus. Ut vitae turpis purus. Sed pharetra, massa et blandit fringilla, magna diam convallis felis, eget elementum massa ex non purus. Donec malesuada at ipsum vel mattis. Fusce dolor arcu, efficitur sed lectus eu, mollis dignissim augue. Integer erat ipsum, efficitur et eros eu, porta consectetur eros. Donec suscipit dictum mi, eget cursus nulla iaculis sed. Praesent nisl arcu, condimentum mattis sem eu, bibendum molestie erat. Aenean congue diam eget feugiat pulvinar. Mauris lacinia massa et efficitur rutrum. Sed euismod eleifend dui vel dapibus. In fringilla dui eget pretium condimentum. Vestibulum non sapien tellus. - - Mauris in dui vehicula, maximus justo sit amet, euismod metus. Quisque maximus nibh mauris, non aliquet sapien placerat sed. Nam id est auctor, laoreet nibh in, luctus nulla. Donec vestibulum dapibus nisi, vel pulvinar sapien scelerisque sit amet. Aliquam pretium erat a justo semper, non aliquam tortor aliquet. Donec rutrum convallis neque eget efficitur. Ut posuere consectetur magna sed congue. + Saturday morning was come, and all the summer world was bright and fresh, + and brimming with life. There was a song in every heart; and if the heart was young the music issued at the lips. + There was cheer in every face and a spring in every step. The locust-trees were in bloom and the fragrance of the + blossoms filled the air. Cardiff Hill, beyond the village and above it, + was green with vegetation and it lay just far enough away to seem a Delectable Land, dreamy, reposeful, and inviting. + + Tom appeared on the sidewalk with a bucket of whitewash and a long-handled brush. He surveyed the fence, + and all gladness left him and a deep melancholy settled down upon his spirit. Thirty yards of board fence nine feet high. + Life to him seemed hollow, and existence but a burden. Sighing, he dipped his brush and passed it along the topmost plank; + repeated the operation; did it again; compared the insignificant whitewashed streak with the far-reaching continent of unwhitewashed fence, + and sat down on a tree-box discouraged. Jim came skipping out at the gate with a tin pail, and singing Buffalo Gals. + Bringing water from the town pump had always been hateful work in Tom’s eyes, before, but now it did not strike him so. + He remembered that there was company at the pump. White, mulatto, and negro boys and girls were always there waiting their turns, + resting, trading playthings, quarrelling, fighting, skylarking. And he remembered that although the pump was only a hundred and fifty yards off, + Jim never got back with a bucket of water under an hour – and even then somebody generally had to go after him. Tom said: + “Say, Jim, I’ll fetch the water if you’ll whitewash some.” + + Jim shook his head and said: + + “Can’t, Mars Tom. Ole missis, she tole me I got to go an’ git dis water an’ not stop foolin’ roun’ wid anybody. + She say she spec’ Mars Tom gwine to ax me to whitewash, an’ so she tole me go ‘long an’ + ‘tend to my own business – she ‘lowed she’d ‘tend to de whitewashin’.” + + “Oh, never you mind what she said, Jim. That’s the way she always talks. Gimme the bucket – I won’t be gone only a a minute. She won’t ever know.” + + “Oh, I dasn’t, Mars Tom. Ole missis she’d take an’ tar de head off’n me. ‘Deed she would.” + + “She! She never licks anybody – whacks ’em over the head with her thimble – and who cares for that, + I’d like to know. She talks awful, but talk don’t hurt – anyways it don’t if she don’t cry. Jim, I’ll give you a marvel. I’ll give you a white alley!”
) } diff --git a/src/server/api/routers/app.ts b/src/server/api/routers/app.ts index 1876644..59af61c 100644 --- a/src/server/api/routers/app.ts +++ b/src/server/api/routers/app.ts @@ -2,9 +2,9 @@ import axios from 'axios' import { Prisma } from '@prisma/client' import { z } from 'zod' import type { SpeedQuestion } from '@prisma/client' -import type { Language, User } from '~/utils/types' +import type { Language, User, WordPair } from '~/utils/types' import { createTRPCRouter, publicProcedure } from '~/server/api/trpc' -import { schemas, inputs } from '~/utils/validators' +import { schemas, inputs, wordPairData } from '~/utils/validators' import { getNextExercise, getNextURL } from '~/utils/helpers' export const userRouter = createTRPCRouter({ @@ -61,6 +61,7 @@ export const userRouter = createTRPCRouter({ lastCubeByTwo: input.lastCubeByTwo, lastNumberGuesser: input.lastNumberGuesser, lastLetterMatcher: input.lastLetterMatcher, + lastWordPair: input.lastWordPair, lastGreenDot: input.lastGreenDot, numberGuesserFigures: input.numberGuesserFigures, schulteLevel: input.schulteLevel, @@ -197,6 +198,16 @@ export const excercisesPropsRouter = createTRPCRouter({ }) } }), + + getWordPairs: publicProcedure + .input(z.number()) + .output(z.array(wordPairData)) + .query(async ({ input, ctx }) => { + const result = await ctx.prisma.$queryRaw>( + Prisma.sql`SELECT * FROM WordPair ORDER BY RANDOM() LIMIT ${input}`, + ) + return result + }), }) export const createSpeedTestRouter = createTRPCRouter({ diff --git a/src/utils/types.ts b/src/utils/types.ts index 981cdd9..1b5ff5d 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,5 +1,5 @@ import type { z } from 'zod' -import type { userSchema } from '~/utils/validators' +import type { userSchema, wordPairData } from '~/utils/validators' import type { speedTestSchema } from '~/utils/validators' export const Overlay = [ @@ -27,6 +27,8 @@ export type Overlay = (typeof Overlay)[number] **/ export type User = z.infer +export type WordPair = z.infer + export type SpeedTest = z.infer const Highlight = [ diff --git a/src/utils/validators.ts b/src/utils/validators.ts index 3ea2efe..fe96076 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -58,6 +58,7 @@ export const userSchema = z.object({ lastCubeByTwo: z.string().default(' '), lastLetterMatcher: z.string().default(' '), lastGreenDot: z.string().default(' '), + lastWordPair: z.string().default(' '), numberGuesserFigures: z.number().default(0), schulteLevel: z.union([ z.literal('three'), @@ -162,6 +163,11 @@ export const boxFlasherData = z.object({ speed: z.number(), }) +export const wordPairData = z.object({ + primary: z.string(), + secondary: z.string(), +}) + export const schemas = { user: userSchema, speedTest: speedTestSchema, From b10ee490635745077b01434d21c50727ced10d1e Mon Sep 17 00:00:00 2001 From: Jason Boyett Date: Wed, 6 Dec 2023 18:54:11 -0600 Subject: [PATCH 2/6] pre stream update --- src/server/api/routers/app.ts | 4 ++-- src/utils/validators.ts | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/server/api/routers/app.ts b/src/server/api/routers/app.ts index 59af61c..0e1f9b1 100644 --- a/src/server/api/routers/app.ts +++ b/src/server/api/routers/app.ts @@ -4,7 +4,7 @@ import { z } from 'zod' import type { SpeedQuestion } from '@prisma/client' import type { Language, User, WordPair } from '~/utils/types' import { createTRPCRouter, publicProcedure } from '~/server/api/trpc' -import { schemas, inputs, wordPairData } from '~/utils/validators' +import { schemas, inputs, wordPairData, wordPairProps } from '~/utils/validators' import { getNextExercise, getNextURL } from '~/utils/helpers' export const userRouter = createTRPCRouter({ @@ -200,7 +200,7 @@ export const excercisesPropsRouter = createTRPCRouter({ }), getWordPairs: publicProcedure - .input(z.number()) + .input(wordPairProps) .output(z.array(wordPairData)) .query(async ({ input, ctx }) => { const result = await ctx.prisma.$queryRaw>( diff --git a/src/utils/validators.ts b/src/utils/validators.ts index fe96076..6664d69 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -164,8 +164,24 @@ export const boxFlasherData = z.object({ }) export const wordPairData = z.object({ - primary: z.string(), - secondary: z.string(), + primaryWord: z.string(), + secondaryWord: z.string(), + language: z.union([ + z.literal('english'), + z.literal('spanish'), + z.literal('german'), + z.literal('italian'), + ]), +}) + +export const wordPairProps = z.object({ + count: z.number(), + language: z.union([ + z.literal('english'), + z.literal('spanish'), + z.literal('german'), + z.literal('italian'), + ]), }) export const schemas = { From 38b9d091c34c2895e64585aa04a7991eb0be22fd Mon Sep 17 00:00:00 2001 From: Jason Boyett Date: Wed, 6 Dec 2023 18:55:27 -0600 Subject: [PATCH 3/6] pre stream update 2 --- src/server/api/routers/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/api/routers/app.ts b/src/server/api/routers/app.ts index 0e1f9b1..fd556f7 100644 --- a/src/server/api/routers/app.ts +++ b/src/server/api/routers/app.ts @@ -204,7 +204,7 @@ export const excercisesPropsRouter = createTRPCRouter({ .output(z.array(wordPairData)) .query(async ({ input, ctx }) => { const result = await ctx.prisma.$queryRaw>( - Prisma.sql`SELECT * FROM WordPair ORDER BY RANDOM() LIMIT ${input}`, + Prisma.sql`SELECT * FROM WordPair ORDER BY RANDOM() LIMIT ${input.count}`, ) return result }), From 63efc0e6f0c0520c8a822d546a70cdfab7c05823 Mon Sep 17 00:00:00 2001 From: Nathaniel Maile Date: Tue, 12 Dec 2023 22:58:16 -0500 Subject: [PATCH 4/6] wip --- prisma/schema.prisma | 1 - src/components/wordpairs.tsx | 126 ++++++++++++++++++++++++++++++ src/pages/exercises/wordpairs.tsx | 18 +++++ src/server/api/routers/app.ts | 2 +- 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/components/wordpairs.tsx create mode 100644 src/pages/exercises/wordpairs.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f3eac9a..bd3c623 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -90,7 +90,6 @@ model User { lastNumberGuesser String @default(" ") lastLetterMatcher String @default(" ") lastGreenDot String @default(" ") - lastWordPair String @default(" ") numberGuesserFigures Int @default(4) font Font @default(sans) isUsingChecklist Boolean @default(true) diff --git a/src/components/wordpairs.tsx b/src/components/wordpairs.tsx new file mode 100644 index 0000000..7dc5dcb --- /dev/null +++ b/src/components/wordpairs.tsx @@ -0,0 +1,126 @@ +import { w } from 'drizzle-orm/query-promise.d-0dd411fc' +import { useRouter, type SingletonRouter } from 'next/router' +import { useEffect, useRef, useState } from 'react' +import { uuid } from 'uuidv4' +import { FontProviderButton } from '~/cva/fontProvider' +import { useStopWatch } from '~/hooks/useStopWatch' +import useUserStore from '~/stores/userStore' +import { api } from '~/utils/api' +import { Font, User, WordPair } from '~/utils/types' + +type PairsProps = { + diffCount: number +} + +export default function WordPairs({ diffCount }: PairsProps) { + const totalCells = 16 + + const pairs = api.getExcerciseProps.getWordPairs.useQuery( + { + language: "english", + count: diffCount + }) + const random = api.getExcerciseProps.getRandomWords.useQuery( + { + number: totalCells - diffCount, + language: "english", + max: 7 + }) + + const [pairs2, setPairs2] = useState([]) + const [random2, setRandom2] = useState([]) + const [grid, setGrid] = useState([]) + + function GenerateGrid(pairs: WordPair[], random: string[]) { + const cells = [] + for (let i = 0; i < diffCount; i++) { + cells.push(generateSame(pairs)) + } + for (let i = 0; i < totalCells - diffCount; i++) { + cells.push(generateDifferent(random)) + } + return cells.sort(() => Math.random() - 0.5) + } + + function generateSame(list: WordPair[]) { + const pair = list.pop() + return + } + function generateDifferent(list: string[]) { + const randoword = list.pop() ?? "" + return + } + + + useEffect(() => { + if (!pairs.data) return + if (!random.data) return + setPairs2(() => pairs.data as WordPair[]) + setRandom2(() => random.data as string[]) + const pairs3 = [...pairs2] + const random3 = [...random2] + setGrid(GenerateGrid(pairs3, random3)) + }, [pairs.data, random.data]) + + return ( +
+ {grid} +
+ ) +} + +// +type CellProps = { + font?: Font + different: boolean + word1: string + word2: string + id?: string +} + +function Cell({ font,different, word1, word2, id }: CellProps) { + const [cellColor, setCellColor] = useState("bg-lime-500") + function handleClick() { + if (different) { + setCellColor("bg-red-500") + } else { + } + } + useEffect(() => { + if (!id) { + id = "0" + } + if (!font) { + font = 'sans' + } + }, [font, id]) + // todo: delete w and h later + return ( + handleClick()} + id={id?.toString() ?? '0'} + className={cellColor + " items-center grid grid-cols-1 w-32 h-24 rounded-lg"} + > +
+ {word1} +
+
+ {word2} +
+
+ ) +} diff --git a/src/pages/exercises/wordpairs.tsx b/src/pages/exercises/wordpairs.tsx new file mode 100644 index 0000000..a78532f --- /dev/null +++ b/src/pages/exercises/wordpairs.tsx @@ -0,0 +1,18 @@ +import { useEffect, useState } from 'react' +import WordPairs from '~/components/wordpairs' +import useUserStore from '~/stores/userStore' +import { api } from '~/utils/api' +import User from '~/utils/types' +export default function Page() { + return ( +
+
+
+ +
+
+
+ ) +} diff --git a/src/server/api/routers/app.ts b/src/server/api/routers/app.ts index fd556f7..49fea2a 100644 --- a/src/server/api/routers/app.ts +++ b/src/server/api/routers/app.ts @@ -204,7 +204,7 @@ export const excercisesPropsRouter = createTRPCRouter({ .output(z.array(wordPairData)) .query(async ({ input, ctx }) => { const result = await ctx.prisma.$queryRaw>( - Prisma.sql`SELECT * FROM WordPair ORDER BY RANDOM() LIMIT ${input.count}`, + Prisma.sql`SELECT * FROM WordPair ORDER BY RAND () LIMIT ${input.count}`, ) return result }), From fb6f50180bed3190b1a75f4c2b5dd1e6568cf577 Mon Sep 17 00:00:00 2001 From: Jason Boyett Date: Thu, 14 Dec 2023 17:54:51 -0600 Subject: [PATCH 5/6] word pairs working --- prisma/schema.prisma | 17 +- src/components/wordpairs.tsx | 297 +++++++++++++++++----------- src/pages/exercises/wordpairs.tsx | 12 +- src/server/api/root.ts | 2 + src/server/api/routers/app.ts | 5 +- src/server/api/routers/collector.ts | 17 ++ src/stores/usePairsStore.ts | 20 ++ src/utils/helpers.ts | 3 +- src/utils/types.ts | 13 +- src/utils/validators.ts | 44 ++--- 10 files changed, 274 insertions(+), 156 deletions(-) create mode 100644 src/stores/usePairsStore.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bd3c623..b15642e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -76,6 +76,9 @@ model User { wordFlasherSessions WordFlasherSession[] LetterMatcherSessions LetterMatcherSession[] GreenDotSessions GreenDotSession[] + NumberGuesserSession NumberGuesserSession[] + BoxFlasherSession BoxFlasherSession[] + PairsSession PairsSession[] highlightColor Overlay @default(GREY) lastSchulte String @default(" ") lastSpeedTest String @default(" ") @@ -89,6 +92,7 @@ model User { lastCubeByThree String @default(" ") lastNumberGuesser String @default(" ") lastLetterMatcher String @default(" ") + lastWordPairs String @default(" ") lastGreenDot String @default(" ") numberGuesserFigures Int @default(4) font Font @default(sans) @@ -99,8 +103,6 @@ model User { schulteAdvanceCount Int @default(0) language Language @default(english) tested Boolean @default(false) - NumberGuesserSession NumberGuesserSession[] - BoxFlasherSession BoxFlasherSession[] @@index([id]) } @@ -207,6 +209,17 @@ model GreenDotSession { @@index([id]) } +model PairsSession { + id String @unique + userId String + user User @relation(fields: [userId], references: [id]) + date DateTime @db.DateTime + time Int + errorCount Int + + @@index([id]) +} + model SatPassage { id String @unique passageText String diff --git a/src/components/wordpairs.tsx b/src/components/wordpairs.tsx index 7dc5dcb..87a0179 100644 --- a/src/components/wordpairs.tsx +++ b/src/components/wordpairs.tsx @@ -1,126 +1,203 @@ -import { w } from 'drizzle-orm/query-promise.d-0dd411fc' import { useRouter, type SingletonRouter } from 'next/router' -import { useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { uuid } from 'uuidv4' -import { FontProviderButton } from '~/cva/fontProvider' +import { FontProvider } from '~/cva/fontProvider' import { useStopWatch } from '~/hooks/useStopWatch' import useUserStore from '~/stores/userStore' import { api } from '~/utils/api' -import { Font, User, WordPair } from '~/utils/types' +import { formatDate, navigateToNextExercise } from '~/utils/helpers' +import type { Font, WordPair } from '~/utils/types' + +function useGetProps(total = 16, diffCount = 10) { + + const pairs = api.getExcerciseProps.getWordPairs.useQuery({ + count: diffCount, + language: "english", + }) + const words = api.getExcerciseProps.getRandomWords.useQuery({ + number: total - diffCount, + language: "english", + max: 7 + }) + + const result = useMemo(() => { + if (words.isSuccess && pairs.isSuccess) { + return { + words: words.data, + pairs: pairs.data + } + } + else return { + words: undefined, + pairs: undefined + } + }, [pairs, words]) + + return result +} + +function useGerUser() { + const user = api.user.getUnique.useQuery() + const userStore = useUserStore() + useEffect(() => { + if (user.isSuccess) { + userStore.setUser(user.data) + } + }, [user]) +} type PairsProps = { - diffCount: number + diffCount: number } + export default function WordPairs({ diffCount }: PairsProps) { - const totalCells = 16 - - const pairs = api.getExcerciseProps.getWordPairs.useQuery( - { - language: "english", - count: diffCount - }) - const random = api.getExcerciseProps.getRandomWords.useQuery( - { - number: totalCells - diffCount, - language: "english", - max: 7 - }) - - const [pairs2, setPairs2] = useState([]) - const [random2, setRandom2] = useState([]) - const [grid, setGrid] = useState([]) - - function GenerateGrid(pairs: WordPair[], random: string[]) { - const cells = [] - for (let i = 0; i < diffCount; i++) { - cells.push(generateSame(pairs)) - } - for (let i = 0; i < totalCells - diffCount; i++) { - cells.push(generateDifferent(random)) - } - return cells.sort(() => Math.random() - 0.5) - } - - function generateSame(list: WordPair[]) { - const pair = list.pop() - return - } - function generateDifferent(list: string[]) { - const randoword = list.pop() ?? "" - return - } - - - useEffect(() => { - if (!pairs.data) return - if (!random.data) return - setPairs2(() => pairs.data as WordPair[]) - setRandom2(() => random.data as string[]) - const pairs3 = [...pairs2] - const random3 = [...random2] - setGrid(GenerateGrid(pairs3, random3)) - }, [pairs.data, random.data]) - - return ( -
- {grid} -
- ) + + const total = 16 + const { words, pairs } = useGetProps(total, diffCount) + const user = api.user.getUnique.useQuery() + const { mutate: updateUser } = api.user.setUser.useMutation() + const userStore = useUserStore() + const { mutate: collectSessionData } = api.wordPairSession.setUnique.useMutation() + const stopWatch = useStopWatch() + const router = useRouter() + const foud = useRef(0) + const wrongs = useRef(0) + const font = useRef('sans') + const [grid, setGrid] = useState() + + + function generateGrid() { + if (!pairs || !words) return + const cells = new Array() + words.forEach((word) => { + cells.push(generateSame(word)) + }) + pairs.forEach((pair) => { + cells.push(generateDifferent(pair)) + }) + stopWatch.start() + return cells.sort(() => Math.random() - 0.5) + } + + + const handleCellClick = useCallback((answer: 'correct' | 'error') => { + console.log(answer) + if (answer === 'correct') { + foud.current += 1 + } + if (answer === 'error') { + wrongs.current += 1 + } + + if (foud.current === diffCount) tearDown() + console.log('pairsFound', foud.current) + }, []) + + + function tearDown() { + console.log('teardown') + if (!user) return + if (!userStore.user) return + stopWatch.end() + updateUser({ lastWordPairs: formatDate(new Date()) }) + userStore.setUser({ + ...userStore.user, + lastWordPairs: formatDate(new Date()) + }) + if (user.data && user.data.isStudySubject) { + collectSessionData({ + userId: user.data.id, + errorCount: wrongs.current, + time: stopWatch.getDuration() + }) + } + navigateToNextExercise(router as SingletonRouter, user.data ?? userStore.user) + } + + function generateSame(word: string) { + return + } + function generateDifferent(pair: WordPair) { + return + } + + useEffect(() => { + if (!user.data) return + font.current = user.data.font + }, [user]) + + useEffect(() => { + setGrid(() => generateGrid()) + }, [words, pairs]) + + return ( +
+ {grid} +
+ ) } -// type CellProps = { - font?: Font - different: boolean - word1: string - word2: string - id?: string + font?: Font + different: boolean + word1: string + word2: string + id?: string + callback: (answer: 'correct' | 'error') => void } -function Cell({ font,different, word1, word2, id }: CellProps) { - const [cellColor, setCellColor] = useState("bg-lime-500") - function handleClick() { - if (different) { - setCellColor("bg-red-500") - } else { - } - } - useEffect(() => { - if (!id) { - id = "0" - } - if (!font) { - font = 'sans' - } - }, [font, id]) - // todo: delete w and h later - return ( - handleClick()} - id={id?.toString() ?? '0'} - className={cellColor + " items-center grid grid-cols-1 w-32 h-24 rounded-lg"} - > -
- {word1} -
-
- {word2} -
-
- ) +function Cell({ font, different, word1, word2, id, callback }: CellProps) { + const [highlighted, setHighlighted] = useState(false) + const variableStyle = useRef([ + 'items-center grid grid-cols-1 rounded-lg text-white text-3xl p-2', + `${highlighted ? 'bg-white/10' : 'bg-white/20'}`, + 'cursor-pointer', + ].join(' ')) + const [styles, setStyles] = useState(variableStyle.current) + + function handleClick() { + if (different && !highlighted) { + callback('correct') + setHighlighted(() => true) + setStyles(() => variableStyle.current) + } else { + callback('error') + } + } + return ( + handleClick()} + id={id?.toString() ?? '0'} + className={['items-center grid grid-cols-1 rounded-lg text-white text-3xl p-2', + `${highlighted ? 'bg-white/10' : 'bg-white/20'}`, + 'cursor-pointer', + ].join(' ')} + > +
+ {word1} +
+
+ {word2} +
+
+ ) } diff --git a/src/pages/exercises/wordpairs.tsx b/src/pages/exercises/wordpairs.tsx index a78532f..6be2add 100644 --- a/src/pages/exercises/wordpairs.tsx +++ b/src/pages/exercises/wordpairs.tsx @@ -1,12 +1,12 @@ -import { useEffect, useState } from 'react' -import WordPairs from '~/components/wordpairs' -import useUserStore from '~/stores/userStore' -import { api } from '~/utils/api' -import User from '~/utils/types' +import dynamic from 'next/dynamic' +import Sidebar from '~/components/sidebar' + +const WordPairs = dynamic(() => import('~/components/wordpairs'), { ssr: false }) export default function Page() { return (
-
+ +
{ const result = await ctx.prisma.$queryRaw>( - Prisma.sql`SELECT * FROM WordPair ORDER BY RAND () LIMIT ${input.count}`, + Prisma.sql`SELECT * FROM WordPair ORDER BY RAND() LIMIT ${input.count}`, ) + if (result === null || result === undefined) throw new Error('No result') return result }), }) diff --git a/src/server/api/routers/collector.ts b/src/server/api/routers/collector.ts index b002a26..ae5ce8c 100644 --- a/src/server/api/routers/collector.ts +++ b/src/server/api/routers/collector.ts @@ -7,6 +7,7 @@ import { numberGuesserData, schulteData, letterMatcherData, + wordPairSessionData, } from '~/utils/validators' export const highlightSessionRouter = createTRPCRouter({ @@ -121,3 +122,19 @@ export const greenDotRouter = createTRPCRouter({ }) }) }) + +export const wordPairsRouter = createTRPCRouter({ + setUnique: publicProcedure + .input(wordPairSessionData) + .mutation(async ({ input, ctx }) => { + await ctx.prisma.pairsSession.create({ + data: { + id: uuid(), + userId: ctx.auth.userId as string, + time: input.time, + errorCount: input.errorCount, + date: new Date(), + } + }) + }) +}) diff --git a/src/stores/usePairsStore.ts b/src/stores/usePairsStore.ts new file mode 100644 index 0000000..9df6df2 --- /dev/null +++ b/src/stores/usePairsStore.ts @@ -0,0 +1,20 @@ +import { create } from 'zustand' + +export const usePairsStore = create<{ + correct: number + errors: number + incrementCorrect: () => void + incrementErrors: () => void +}>()( + (set) => ({ + correct: 0, + errors: 0, + incrementCorrect: () => set((state) => ({ correct: state.correct + 1 })), + incrementErrors: () => set((state) => ({ errors: state.errors + 1 })), + }), +) + +export default usePairsStore + +export type PairsStore = typeof usePairsStore + diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 4740883..16bcb0a 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -99,8 +99,9 @@ export function isAlreadyDone(user: User, exercise: Exercise) { case 'cubeByTwo': return isToday(user.lastCubeByTwo) case 'cubeByThree': return isToday(user.lastCubeByThree) case 'letterMatcher': return isToday(user.lastLetterMatcher) + case 'wordPairs': return isToday(user.lastWordPair) case 'greenDot': return isToday(user.lastGreenDot) - default: return null + default: return null } } diff --git a/src/utils/types.ts b/src/utils/types.ts index 1b5ff5d..78707e1 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,5 +1,5 @@ import type { z } from 'zod' -import type { userSchema, wordPairData } from '~/utils/validators' +import type { language, userSchema, wordPairData } from '~/utils/validators' import type { speedTestSchema } from '~/utils/validators' export const Overlay = [ @@ -45,14 +45,8 @@ const Answer = ['A', 'B', 'C', 'D'] as const export type Answer = (typeof Answer)[number] -const Language = [ - 'english', - 'spanish', - 'german', - 'italian', -] as const -export type Language = (typeof Language)[number] +export type Language = z.infer export const Exercise = [ 'fourByOne', @@ -67,7 +61,8 @@ export const Exercise = [ 'cubeByThree', 'numberGuesser', 'letterMatcher', - 'greenDot' + 'wordPairs', + 'greenDot', ] as const /** diff --git a/src/utils/validators.ts b/src/utils/validators.ts index 6664d69..a569547 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -2,6 +2,13 @@ import { z } from 'zod' //zod is a library for data validation and parsing //in this code base z represents the zod validation library and its members +export const language = z.union([ + z.literal('english'), + z.literal('spanish'), + z.literal('german'), + z.literal('italian'), +]) + export const userSchema = z.object({ id: z.string(), firstName: z.string(), @@ -38,13 +45,7 @@ export const userSchema = z.object({ z.literal('ibmPlexMono'), ]) .default('sans'), - language: z.union([ - z.literal('english'), - z.literal('spanish'), - z.literal('german'), - z.literal('italian'), - ]) - .default('english'), + language: language.default('english'), lastSchulte: z.string().default(' '), lastSpeedTest: z.string().default(' '), lastFourByOne: z.string().default(' '), @@ -58,7 +59,7 @@ export const userSchema = z.object({ lastCubeByTwo: z.string().default(' '), lastLetterMatcher: z.string().default(' '), lastGreenDot: z.string().default(' '), - lastWordPair: z.string().default(' '), + lastWordPairs: z.string().default(' '), numberGuesserFigures: z.number().default(0), schulteLevel: z.union([ z.literal('three'), @@ -86,12 +87,7 @@ export const speedTestSchema = z.object({ const randomWordInputs = z.object({ number: z.number(), max: z.number(), - language: z.union([ - z.literal('english'), - z.literal('spanish'), - z.literal('german'), - z.literal('italian'), - ]), + language: language, }) const exercise = z.union([ @@ -166,22 +162,18 @@ export const boxFlasherData = z.object({ export const wordPairData = z.object({ primaryWord: z.string(), secondaryWord: z.string(), - language: z.union([ - z.literal('english'), - z.literal('spanish'), - z.literal('german'), - z.literal('italian'), - ]), + language: language, +}) + +export const wordPairSessionData = z.object({ + userId: z.string(), + errorCount: z.number(), + time: z.number(), }) export const wordPairProps = z.object({ count: z.number(), - language: z.union([ - z.literal('english'), - z.literal('spanish'), - z.literal('german'), - z.literal('italian'), - ]), + language: language, }) export const schemas = { From 12d96e94abeeeb6b04e16995c8f1d669a2e4a0f3 Mon Sep 17 00:00:00 2001 From: JasonBoyett Date: Fri, 15 Dec 2023 15:10:03 -0600 Subject: [PATCH 6/6] "word pairs instructions done. Utf8 in sidebar fixed" --- package-lock.json | 9 ++ package.json | 1 + src/components/sidebar.tsx | 8 +- src/components/wordpairs.tsx | 38 +++----- src/pages/admin/testexercise.tsx | 1 + src/pages/exercises/wordpairs.tsx | 2 +- src/pages/instructions/evennumbers.tsx | 1 - src/pages/instructions/wordpairs.tsx | 128 +++++++++++++++++++++++++ src/pages/nav.tsx | 5 + src/utils/helpers.ts | 4 +- 10 files changed, 167 insertions(+), 30 deletions(-) create mode 100644 src/pages/instructions/wordpairs.tsx diff --git a/package-lock.json b/package-lock.json index 3b21296..ed9a330 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "preact": "^10.15.1", "react": "^18.2.0", "react-dom": "18.2.0", + "react-icons": "^4.12.0", "react-p5": "^1.3.35", "react-router-dom": "^6.14.2", "shuffle-array": "^1.0.1", @@ -7857,6 +7858,14 @@ "react": "^18.2.0" } }, + "node_modules/react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 469cfd4..408768e 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "preact": "^10.15.1", "react": "^18.2.0", "react-dom": "18.2.0", + "react-icons": "^4.12.0", "react-p5": "^1.3.35", "react-router-dom": "^6.14.2", "shuffle-array": "^1.0.1", diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 08970e2..7c9e731 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -2,6 +2,10 @@ import { useRouter } from 'next/router' import { useState } from 'react' import { useClerk } from "@clerk/clerk-react" import { motion } from 'framer-motion' +import { + IoArrowForwardCircleOutline, + IoArrowBackCircleOutline +} from "react-icons/io5" export default function Sidebar() { const [showing, setShowing] = useState(false) @@ -59,8 +63,8 @@ export default function Sidebar() { > { showing - ? '⮈' - : '⮊' + ? + : }
diff --git a/src/components/wordpairs.tsx b/src/components/wordpairs.tsx index 87a0179..4e36930 100644 --- a/src/components/wordpairs.tsx +++ b/src/components/wordpairs.tsx @@ -8,7 +8,7 @@ import { api } from '~/utils/api' import { formatDate, navigateToNextExercise } from '~/utils/helpers' import type { Font, WordPair } from '~/utils/types' -function useGetProps(total = 16, diffCount = 10) { +function useGetProps(total = 18, diffCount = 5) { const pairs = api.getExcerciseProps.getWordPairs.useQuery({ count: diffCount, @@ -36,16 +36,6 @@ function useGetProps(total = 16, diffCount = 10) { return result } -function useGerUser() { - const user = api.user.getUnique.useQuery() - const userStore = useUserStore() - useEffect(() => { - if (user.isSuccess) { - userStore.setUser(user.data) - } - }, [user]) -} - type PairsProps = { diffCount: number } @@ -53,7 +43,7 @@ type PairsProps = { export default function WordPairs({ diffCount }: PairsProps) { - const total = 16 + const total = 18 const { words, pairs } = useGetProps(total, diffCount) const user = api.user.getUnique.useQuery() const { mutate: updateUser } = api.user.setUser.useMutation() @@ -147,7 +137,7 @@ export default function WordPairs({ diffCount }: PairsProps) { return (
{grid}
@@ -165,19 +155,14 @@ type CellProps = { function Cell({ font, different, word1, word2, id, callback }: CellProps) { const [highlighted, setHighlighted] = useState(false) - const variableStyle = useRef([ - 'items-center grid grid-cols-1 rounded-lg text-white text-3xl p-2', - `${highlighted ? 'bg-white/10' : 'bg-white/20'}`, - 'cursor-pointer', - ].join(' ')) - const [styles, setStyles] = useState(variableStyle.current) function handleClick() { if (different && !highlighted) { callback('correct') setHighlighted(() => true) - setStyles(() => variableStyle.current) - } else { + } + else if (!different && !highlighted) { + setHighlighted(() => true) callback('error') } } @@ -187,10 +172,13 @@ function Cell({ font, different, word1, word2, id, callback }: CellProps) { key={id} onClick={() => handleClick()} id={id?.toString() ?? '0'} - className={['items-center grid grid-cols-1 rounded-lg text-white text-3xl p-2', - `${highlighted ? 'bg-white/10' : 'bg-white/20'}`, - 'cursor-pointer', - ].join(' ')} + className={[ + 'items-center grid grid-cols-1 rounded-lg text-white', + 'md:text-3xl md:p-2', + 'text-2xl p-1', + `${highlighted ? (different ? 'bg-white/10' : 'bg-red-500/40') : 'bg-white/20'}`, + 'cursor-pointer', + ].join(' ')} >
{word1} diff --git a/src/pages/admin/testexercise.tsx b/src/pages/admin/testexercise.tsx index 3cd5e3e..dcbec79 100644 --- a/src/pages/admin/testexercise.tsx +++ b/src/pages/admin/testexercise.tsx @@ -38,6 +38,7 @@ export default function Page() { +
diff --git a/src/pages/exercises/wordpairs.tsx b/src/pages/exercises/wordpairs.tsx index 6be2add..e7ab181 100644 --- a/src/pages/exercises/wordpairs.tsx +++ b/src/pages/exercises/wordpairs.tsx @@ -9,7 +9,7 @@ export default function Page() {
diff --git a/src/pages/instructions/evennumbers.tsx b/src/pages/instructions/evennumbers.tsx index 60be8a6..cb114b5 100644 --- a/src/pages/instructions/evennumbers.tsx +++ b/src/pages/instructions/evennumbers.tsx @@ -1,5 +1,4 @@ import { useState, useEffect } from 'react' -import type { NextPage } from 'next' import Head from 'next/head' import LoadingSpinner from '~/components/loadingspinner' import { useUserStore } from '~/stores/userStore' diff --git a/src/pages/instructions/wordpairs.tsx b/src/pages/instructions/wordpairs.tsx new file mode 100644 index 0000000..bf8f636 --- /dev/null +++ b/src/pages/instructions/wordpairs.tsx @@ -0,0 +1,128 @@ +import { useState, useEffect } from 'react' +import Head from 'next/head' +import LoadingSpinner from '~/components/loadingspinner' +import { useUserStore } from '~/stores/userStore' +import type { Font, User } from '~/utils/types' +import { useRouter, type SingletonRouter } from "next/router"; +import { FontProvider } from "~/cva/fontProvider"; +import Sidebar from '~/components/sidebar' +import { navigate } from '~/utils/helpers' + +const INSTRUCTION_DELAY = 5_000 + +function Paragraph1({ user }: { user: User | undefined }) { + if (!user) return + if (user.language === 'english') return ( +
+

+ + Each cell will contian two words. In five of the cells the words will be + slightly different and the remaining cells contain matching words. + Click on all of the cells that contain two different words. + +
+ There is no time limit, though your time will be recorded to track your + progression. so try to go as quickly as you can while remaining + accurate. This exercise is designed to help you improve your ability to + focus and your perception. Try to stay relaxed and focused while you are + doing this exercise. It is up to you how you want to approach this + exercise. But we recommend that you either search the table row by row + or column by column. +

+
+ ) + // all of the following are grabbed from google translate and may not be accurate + // if you speak any of these languages and can correct them, please do so. + // TODO get proper translations + if (user.language === 'german') return ( +
+

+ Jede Zelle enthält zwei Wörter. In fünf der Zellen stehen die Wörter + etwas anders und die restlichen Zellen enthalten übereinstimmende Wörter. + Klicken Sie auf alle Zellen, die zwei verschiedene Wörter enthalten. + Es gibt keine zeitliche Begrenzung, Ihre Zeit wird jedoch aufgezeichnet, um Ihre Zeit zu verfolgen + Fortschreiten. Versuchen Sie also, so schnell wie möglich zu gehen, während Sie bleiben + genau. Diese Übung soll Ihnen dabei helfen, Ihre Fähigkeiten zu verbessern + Fokus und Ihre Wahrnehmung. Versuchen Sie dabei entspannt und konzentriert zu bleiben + diese Übung machen. Es liegt an Ihnen, wie Sie dies angehen möchten + Übung. Wir empfehlen Ihnen jedoch, die Tabelle entweder zeilenweise zu durchsuchen + oder Spalte für Spalte. +

+
+ ) + if (user.language === 'spanish') return ( +
+

+ Cada celda contendrá dos palabras. En cinco de las celdas las palabras estarán + ligeramente diferentes y las celdas restantes contienen palabras coincidentes. + Haga clic en todas las celdas que contengan dos palabras diferentes. + No hay límite de tiempo, aunque su tiempo se registrará para realizar un seguimiento de su + progresión. así que intenta ir lo más rápido que puedas mientras permaneces + preciso. Este ejercicio está diseñado para ayudarle a mejorar su capacidad para + enfoque y tu percepción. Intenta mantenerte relajado y concentrado mientras estás + haciendo este ejercicio. Depende de usted cómo quiere abordar esto + ejercicio. Pero le recomendamos que busque en la tabla fila por fila + o columna por columna. +

+
+ ) + if (user.language === 'italian') return ( +
+

+ Ogni cella conterrà due parole. In cinque celle ci saranno le parole + leggermente diverso e le celle rimanenti contengono parole corrispondenti. + Fare clic su tutte le celle che contengono due parole diverse. + Non c'è limite di tempo, anche se il tuo tempo verrà registrato per tenere traccia del tuo + progressione. quindi prova ad andare il più velocemente possibile rimanendo + accurato. Questo esercizio è progettato per aiutarti a migliorare la tua capacità di + concentrazione e la tua percezione. Cerca di rimanere rilassato e concentrato mentre lo sei + facendo questo esercizio. Dipende da te come vuoi affrontare questo problema + esercizio. Ma ti consigliamo di cercare nella tabella riga per riga + o colonna per colonna. +

+
+ ) +} + +function StartButton() { + const [time, setTime] = useState(false) + const router = useRouter() + + + useEffect(() => { + setTimeout(() => setTime(true), INSTRUCTION_DELAY) + }, []) + + return time ? ( + + ) : ( + + ) +} + +export default function Page() { + const userStore = useUserStore() + const [font, setFont] = useState('sans') + useEffect(() => { + if (!userStore.user) return + setFont(userStore.user.font) + }) + + return ( + <> + Even Number Exercise Instructions + + +
+ + +
+
+ + ) +} diff --git a/src/pages/nav.tsx b/src/pages/nav.tsx index 4477a9d..12504e5 100644 --- a/src/pages/nav.tsx +++ b/src/pages/nav.tsx @@ -169,6 +169,11 @@ export default function Page() { exercise='greenDot' user={user as User} /> +
) } diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 16bcb0a..ab920cb 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -99,7 +99,7 @@ export function isAlreadyDone(user: User, exercise: Exercise) { case 'cubeByTwo': return isToday(user.lastCubeByTwo) case 'cubeByThree': return isToday(user.lastCubeByThree) case 'letterMatcher': return isToday(user.lastLetterMatcher) - case 'wordPairs': return isToday(user.lastWordPair) + case 'wordPairs': return isToday(user.lastWordPairs) case 'greenDot': return isToday(user.lastGreenDot) default: return null } @@ -177,6 +177,8 @@ export function getNextURL(next: Exercise | undefined | null): string { return '/instructions/lettermatcher' case 'greenDot': return '/instructions/greendot' + case 'wordPairs': + return '/instructions/wordpairs' default: return '/done' }