Skip to content

Commit

Permalink
chore: πŸ”– release new app version
Browse files Browse the repository at this point in the history
  • Loading branch information
emiliosheinz authored Mar 26, 2023
2 parents 614a2f6 + e2001ce commit 26893c5
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 31 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
# Briskly

![Briskly! Your AI powered flashcards app.](/docs/images/banner.png)
Binary file added docs/images/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 74 additions & 10 deletions src/components/header/header.component.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
import { useEffect, useState } from 'react'

import { Transition } from '@headlessui/react'
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
import { useAtom } from 'jotai'
import { useSession } from 'next-auth/react'
import Link from 'next/link'
import { useRouter } from 'next/router'

import { Image } from '~/components/image'
import { isHeaderSearchInputVisibleAtom } from '~/utils/atoms'

import { AuthButton } from '../auth-button'
import { Input } from '../input'
import { DesktopPrimaryMenu } from './components/desktop-primary-menu'
import { DesktopSecondaryMenu } from './components/desktop-secondary-menu '
import { MobileHamburgerMenu } from './components/mobile-hamburger-menu'

export function Header() {
const { data: session, status } = useSession()
const router = useRouter()

const isLoadingSession = status === 'loading'

const [isSearchInputVisible, setIsSearchInputVisible] = useAtom(
isHeaderSearchInputVisibleAtom,
)
const [searchInputValue, setSearchInputValue] = useState('')

useEffect(() => {
router.events.on('routeChangeStart', () => {
setIsSearchInputVisible(false)
})
}, [router.events, setIsSearchInputVisible])

const handleSearchInputKeyDown = (event: React.KeyboardEvent) => {
if (event.key !== 'Enter') return
if (!searchInputValue) return

router.push(`/search/${searchInputValue.toLowerCase().trim()}`)
}

const renderLogo = () => (
<Image src='/images/logo.png' width={40} height={35} alt='Briskly logo' />
)
Expand All @@ -31,19 +58,56 @@ export function Header() {
return <DesktopSecondaryMenu />
}

const renderSearchButton = () => {
return (
<button
type='button'
onClick={() => setIsSearchInputVisible(isVisible => !isVisible)}
>
<MagnifyingGlassIcon className='h-7 w-7 text-primary-900' />
</button>
)
}

return (
<header className='border-b-2 border-primary-200 bg-primary-50'>
<div className='mx-auto flex h-20 max-w-7xl items-center justify-between px-3 md:space-x-10 md:px-5'>
<div className='flex items-center justify-start'>
<Link href='/' data-testid='home-anchor'>
<span className='sr-only'>Briskly</span>
{renderLogo()}
</Link>
<DesktopPrimaryMenu />
<header>
<div className='border-b-2 border-primary-200 bg-primary-50'>
<div className='mx-auto flex h-20 max-w-7xl items-center justify-between px-3 md:space-x-10 md:px-5'>
<div className='flex items-center justify-start'>
<Link href='/' data-testid='home-anchor'>
<span className='sr-only'>Briskly</span>
{renderLogo()}
</Link>
<DesktopPrimaryMenu />
</div>
<div className='flex gap-5'>
{renderSearchButton()}
<MobileHamburgerMenu />
{renderDesktopSecondaryMenu()}
</div>
</div>
<MobileHamburgerMenu />
{renderDesktopSecondaryMenu()}
</div>
<Transition
show={isSearchInputVisible}
className='overflow-hidden'
enter='transition transition-[max-height] duration-500 ease-in'
enterFrom='transform max-h-0'
enterTo='transform max-h-screen'
leave='transition transition-[max-height] duration-250 ease-out'
leaveFrom='transform max-h-screen'
leaveTo='transform max-h-0'
>
<div className='mx-auto max-w-7xl px-3 pt-4 md:px-5'>
<Input
autoFocus
id='search'
placeholder='Pesquisar'
value={searchInputValue}
onKeyDown={handleSearchInputKeyDown}
onChange={event => setSearchInputValue(event.target.value)}
/>
</div>
</Transition>
</header>
)
}
18 changes: 13 additions & 5 deletions src/components/input/input.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
? 'border-error-700 focus:border-error-700 focus:ring-error-700'
: 'border-primary-500 focus:border-primary-900 focus:ring-primary-900'

return (
<div
aria-disabled={disabled}
className={classNames('w-full', disabled ? 'opacity-50' : '')}
>
const renderLabel = () => {
if (!label) return null

return (
<label
htmlFor={id}
className='block text-sm font-medium capitalize text-primary-800'
>
{label}
</label>
)
}

return (
<div
aria-disabled={disabled}
className={classNames('w-full', disabled ? 'opacity-50' : '')}
>
{renderLabel()}
<div className='mt-1 rounded-md shadow-sm'>
<input
id={id}
Expand Down
2 changes: 1 addition & 1 deletion src/components/input/input.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type InputProps = {
error?: string
label: string
label?: string
id: string
} & Omit<
React.DetailedHTMLProps<
Expand Down
16 changes: 8 additions & 8 deletions src/pages/decks/review/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,23 @@ const ReviewDeckPage: WithAuthentication<
}

if (cardAnswerStage === 'question') {
return <p className='text-base md:text-2xl'>{currentCard?.question}</p>
return <p className='text-base md:text-xl'>{currentCard?.question}</p>
}

if (cardAnswerStage === 'error')
return (
<Tooltip hint='Se o erro persistir vocΓͺ pode passar este Card pressionando o botΓ£o "Passar". Dessa forma vocΓͺ poderΓ‘ tentar responder este Card novamente na sua prΓ³xima revisΓ£o.'>
<span className='max-w-xs text-base text-error-700 md:text-2xl'>
<span className='max-w-xs text-base text-error-700 md:text-xl'>
Houve um erro ao validar a sua resposta. Tente novamente mais tarde!
</span>
</Tooltip>
)

if (!answerResult?.isRight) {
return <span className='text-5xl sm:text-8xl'>πŸ˜ͺ</span>
return <span className='text-5xl sm:text-6xl'>πŸ˜ͺ</span>
}

return <span className='text-5xl sm:text-8xl'>πŸŽ‰</span>
return <span className='text-5xl sm:text-6xl'>πŸŽ‰</span>
}

const renderButtons = () => {
Expand Down Expand Up @@ -145,8 +145,8 @@ const ReviewDeckPage: WithAuthentication<
return (
<div className='flex w-full animate-pulse flex-col gap-5'>
<span className='aspect-video w-full rounded-md bg-primary-200' />
<span className='h-40 w-full rounded-md bg-primary-200 sm:h-56' />
<span className='h-16 w-full rounded-md bg-primary-200' />
<span className='h-40 w-full rounded-md bg-primary-200 sm:h-44' />
<span className='h-14 w-full rounded-md bg-primary-200' />
</div>
)
}
Expand Down Expand Up @@ -208,7 +208,7 @@ const ReviewDeckPage: WithAuthentication<
</div>
<div className='absolute w-full [transform:rotateY(180deg)] [backface-visibility:hidden]'>
<Card fullWidth>
<span className='text-base md:text-2xl'>
<span className='text-base md:text-xl'>
{answerResult?.answer || 'Resposta nΓ£o foi encontrada'}
</span>
{renderReportButton()}
Expand Down Expand Up @@ -250,7 +250,7 @@ const ReviewDeckPage: WithAuthentication<
<title>{`Revisando ${deck?.title ?? 'Carregando...'}`}</title>
<meta name='description' content={deck?.description ?? ''} />
</Head>
<div className='mx-auto flex w-full max-w-3xl justify-center'>
<div className='mx-auto flex w-full max-w-xl justify-center'>
{renderContent()}
</div>
{renderModal()}
Expand Down
6 changes: 3 additions & 3 deletions src/pages/profile/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ const ProfilePage: WithAuthentication<
<Head>
<title>{user?.name || 'Carregando...'}</title>
</Head>
<div className='flex flex-col gap-5 md:flex-row'>
<div className='flex flex-col gap-5'>
<section className='flex flex-col gap-5 md:flex-[2]'>
<div className='flex flex-row items-center gap-3 md:flex-col'>
<div className='flex flex-row items-center gap-3'>
{renderUserImage()}
<div className='flex flex-col items-start md:items-center'>
<div className='flex flex-col items-start'>
<p className='text-center text-xl text-primary-900 md:text-2xl'>
{user?.name}
</p>
Expand Down
87 changes: 87 additions & 0 deletions src/pages/search/[term].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { InView } from 'react-intersection-observer'

import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { type NextPage } from 'next'
import Head from 'next/head'

import { DeckCardList } from '~/components/deck-card-list'
import { Loader } from '~/components/loader'
import { api } from '~/utils/api'

export const getServerSideProps: GetServerSideProps<{
searchedTerm: string
}> = async context => {
const searchedTerm = context.params?.term as string

if (!searchedTerm) return { notFound: true }

return {
props: {
searchedTerm,
},
}
}

const SearchDecksPage: NextPage<
InferGetServerSidePropsType<typeof getServerSideProps>
> = props => {
const { searchedTerm } = props

const {
data,
isError,
refetch,
isLoading,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
} = api.decks.searchByTerm.useInfiniteQuery(
{ term: searchedTerm },
{
getNextPageParam: lastPage => lastPage.nextCursor,
keepPreviousData: true,
},
)

const decks = data?.pages.flatMap(page => page.decks) ?? []
const hasLoadedDecks = decks.length > 0

const renderContent = () => {
if (isLoading) return <DeckCardList.Loading />

if (!hasLoadedDecks && isError) {
return <DeckCardList.Error onRetryPress={refetch} />
}

return <DeckCardList decks={decks} />
}

return (
<>
<Head>
<title>{`Decks sobre ${searchedTerm}`}</title>
<meta
name='description'
content={`Uma lista completa de decks sobre ${searchedTerm}`}
/>
</Head>
<h1 className='mb-5 text-2xl font-semibold'>{`Resultados para "${searchedTerm}"`}</h1>
<div className='flex flex-col items-center'>
{renderContent()}
<InView
as='div'
className='mt-5 flex w-full items-center justify-center'
onChange={inView => {
if (inView && hasNextPage && !isError && !isFetchingNextPage) {
fetchNextPage()
}
}}
>
{isFetchingNextPage ? <Loader /> : null}
</InView>
</div>
</>
)
}

export default SearchDecksPage
45 changes: 45 additions & 0 deletions src/server/api/routers/decks/decks.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,4 +371,49 @@ export const decksRouter = createTRPCRouter({
})),
}
}),
searchByTerm: publicProcedure
.input(z.object({ term: z.string(), cursor: z.string().nullish() }))
.query(async ({ input: { term, cursor }, ctx }) => {
const user = ctx.session?.user

const decks = await ctx.prisma.deck.findMany({
where: {
OR: [
{
title: { contains: term, mode: 'insensitive' },
},
{
description: { contains: term, mode: 'insensitive' },
},
{
topics: {
some: { title: { contains: term, mode: 'insensitive' } },
},
},
],
visibility: Visibility.Public,
},
orderBy: { createdAt: 'desc' },
take: ITEMS_PER_PAGE + 1, // get an extra item at the end which we'll use as next cursor
cursor: cursor ? { id: cursor } : undefined,
include: {
favorites: true,
},
})

const hasNextCursor = decks.length > ITEMS_PER_PAGE
const nextCursor = hasNextCursor ? decks.pop()!.id : undefined

return {
nextCursor,
decks: decks.map(deck => ({
...deck,
image: getS3ImageUrl(deck.image),
favorites: deck.favorites.length,
isFavorite: user
? deck.favorites.some(favorite => favorite.userId === user.id)
: false,
})),
}
}),
})
1 change: 1 addition & 0 deletions src/utils/atoms/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { atom } from 'jotai'

export const fullScreenLoaderAtom = atom(false)
export const isHeaderSearchInputVisibleAtom = atom(false)
1 change: 1 addition & 0 deletions src/utils/navigation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export const routes = {
decksForYou: () => '/decks/for-you',
favorites: () => '/decks/favorites',
answerValidationReports: (id: string) => `/decks/reports/${id}`,
search: (term: string) => `/search/${term}`,
}
Loading

1 comment on commit 26893c5

@vercel
Copy link

@vercel vercel bot commented on 26893c5 Mar 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

briskly – ./

briskly-emiliosheinz.vercel.app
briskly.app
briskly-git-main-emiliosheinz.vercel.app

Please sign in to comment.