diff --git a/.vscode/settings.json b/.vscode/settings.json index 408e260..8a9c2ec 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "cSpell.words": ["superjson", "trpc", "TRPC"] + "cSpell.words": ["healthcheck", "Healthcheck", "superjson", "trpc", "TRPC"] } diff --git a/src/contexts/create-new-deck/create-new-deck.context.tsx b/src/contexts/create-new-deck/create-new-deck.context.tsx index 086d8a2..eb5369a 100644 --- a/src/contexts/create-new-deck/create-new-deck.context.tsx +++ b/src/contexts/create-new-deck/create-new-deck.context.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react' +import React, { useContext, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' @@ -149,6 +149,13 @@ export function CreateNewDeckContextProvider( }, ) + /** + * Healthcheck to wake up the generate flash cards API + */ + useEffect(() => { + fetch(`${env.NEXT_PUBLIC_BRISKLY_GENERATE_FLASH_CARDS_API_URL}/healthcheck`) + }, []) + const addTopic = (topic: string) => { setTopics(topics => [ ...topics.filter(({ title }) => title !== topic), diff --git a/src/modules/decks/components/study-session-card.component.tsx b/src/modules/decks/components/study-session-card.component.tsx index 8fb4191..8db3ba1 100644 --- a/src/modules/decks/components/study-session-card.component.tsx +++ b/src/modules/decks/components/study-session-card.component.tsx @@ -26,12 +26,14 @@ export const StudySessionCard = (props: { deckId: string }) => { const createStudySessionMutation = api.studySession.create.useMutation({ onSuccess: () => { apiContext.decks.toBeReviewed.invalidate() + apiContext.decks.withStudySession.invalidate() apiContext.studySession.getStudySessionBasicInfo.invalidate({ deckId }) }, }) const deleteStudySessionMutation = api.studySession.delete.useMutation({ onSuccess: () => { apiContext.decks.toBeReviewed.invalidate() + apiContext.decks.withStudySession.invalidate() apiContext.studySession.getStudySessionBasicInfo.invalidate({ deckId }) notify.success('Sessão de estudo deletada com sucesso!') }, diff --git a/src/modules/decks/create/components/cards.component.tsx b/src/modules/decks/create/components/cards.component.tsx index e7113be..2ec4c50 100644 --- a/src/modules/decks/create/components/cards.component.tsx +++ b/src/modules/decks/create/components/cards.component.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { PlusCircleIcon } from '@heroicons/react/24/outline' @@ -14,6 +14,33 @@ type NewCardModalState = { cardIdx?: number } +const loadingSteps = [ + 'Coletando informações', + 'Analisando o contexto', + 'Gerando os cards', +] + +const CYCLE_INTERVAL = 3000 + +export const AiPoweredFlashCardsLoader = () => { + const [currentLoadingStep, setCurrentLoadingStep] = useState(0) + + useEffect(() => { + const interval = setInterval(() => { + setCurrentLoadingStep(state => (state + 1) % loadingSteps.length) + }, CYCLE_INTERVAL) + + return () => clearInterval(interval) + }, []) + + return ( +
+ +

{loadingSteps[currentLoadingStep]}

+
+ ) +} + export const Cards = () => { const { cards, @@ -52,7 +79,7 @@ export const Cards = () => { const renderAiCardsButton = () => { const successContent = isGeneratingAiPoweredCards ? ( - + ) : ( 🤖 ) @@ -67,9 +94,7 @@ export const Cards = () => { {hasErrorGeneratingAiPoweredCards ? errorContent : successContent}
- +
) diff --git a/src/modules/profile/components/decks-with-study-session.component.tsx b/src/modules/profile/components/decks-with-study-session.component.tsx new file mode 100644 index 0000000..a3304d8 --- /dev/null +++ b/src/modules/profile/components/decks-with-study-session.component.tsx @@ -0,0 +1,22 @@ +import { DeckCardList } from '~/components/deck-card-list' +import { api } from '~/utils/api' + +type UserDecksProps = { + userId: string + isVisible: boolean +} + +export function DecksWithStudySession(props: UserDecksProps) { + const { isVisible } = props + + const { data, isLoading, isError, refetch } = + api.decks.withStudySession.useQuery(undefined, { enabled: isVisible }) + + if (isLoading) return + + if (isError) { + return + } + + return +} diff --git a/src/modules/profile/constants/profile-menu-tabs.tsx b/src/modules/profile/constants/profile-menu-tabs.tsx index b4d2e34..1868755 100644 --- a/src/modules/profile/constants/profile-menu-tabs.tsx +++ b/src/modules/profile/constants/profile-menu-tabs.tsx @@ -15,12 +15,23 @@ const UserFavoriteDecks = dynamic(() => ), ) +const DecksWithStudySession = dynamic(() => + import('../components/decks-with-study-session.component').then( + module => module.DecksWithStudySession, + ), +) + export const profileMenuTabs = [ { name: 'Decks', content: UserDecks, isProfileOwnerOnly: false, }, + { + name: 'Meus Estudos', + content: DecksWithStudySession, + isProfileOwnerOnly: true, + }, { name: 'Para Revisar', content: DecksToBeReviewed, diff --git a/src/pages/profile/[id].tsx b/src/pages/profile/[id].tsx index 82add03..0941cca 100644 --- a/src/pages/profile/[id].tsx +++ b/src/pages/profile/[id].tsx @@ -111,13 +111,13 @@ const ProfilePage: WithAuthentication<
- + {tabs.map(tab => ( classNames( - 'border-b-2 py-2 px-5 text-base font-medium leading-5 text-primary-900 ring-primary-50 ring-opacity-60 ring-offset-2 ring-offset-primary-500 focus:outline-none focus:ring-2', + 'min-w-max border-b-2 py-2 px-5 text-base font-medium leading-5 text-primary-900 ring-primary-50 ring-opacity-60 ring-offset-2 ring-offset-primary-500 focus:outline-none focus:ring-2', selected ? 'border-primary-900' : 'border-primary-50', ) } diff --git a/src/server/api/routers/decks/decks.router.ts b/src/server/api/routers/decks/decks.router.ts index 08496df..d0d025b 100644 --- a/src/server/api/routers/decks/decks.router.ts +++ b/src/server/api/routers/decks/decks.router.ts @@ -191,6 +191,32 @@ export const decksRouter = createTRPCRouter({ nextCursor, } }), + withStudySession: protectedProcedure.query(async ({ ctx }) => { + const { user } = ctx.session + + const decks = await ctx.prisma.deck.findMany({ + where: { + studySessions: { + some: { + userId: user.id, + }, + }, + }, + orderBy: { createdAt: 'desc' }, + include: { + favorites: true, + }, + }) + + return decks.map(deck => ({ + ...deck, + image: getS3ImageUrl(deck.image), + favorites: deck.favorites.length, + isFavorite: deck.favorites + .map(favorite => favorite.userId) + .includes(user.id), + })) + }), byUser: protectedProcedure .input(z.object({ userId: z.string() })) .query(async ({ input: { userId }, ctx }) => { @@ -218,6 +244,7 @@ export const decksRouter = createTRPCRouter({ .includes(signedInUser.id), })) }), + addFavorite: protectedProcedure .input(z.object({ deckId: z.string() })) .mutation(async ({ input: { deckId }, ctx }) => {