Skip to content

Commit

Permalink
Allow showing only started kanji
Browse files Browse the repository at this point in the history
  • Loading branch information
darthorimar committed Oct 8, 2024
1 parent 9fa3600 commit 646a55d
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 18 deletions.
40 changes: 28 additions & 12 deletions app/kanji/[level]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use client'

import { Center } from '@/app/componets/center'
import { getKanjiForLevel, getWanikaniStatus, Kanji, WanikaniStatus } from '@/app/wanikani/WaniKani'
import { getKanjiForLevel, getStartedKanjiIds, getWanikaniStatus, Kanji, WanikaniStatus } from '@/app/wanikani/WaniKani'
import { Button, Card, CardBody } from '@nextui-org/react'
import { useRouter } from 'next/navigation'
import { useRouter, useSearchParams } from 'next/navigation'
import React from 'react'
import { useEffect, useState } from 'react'
import { MdArrowBack } from 'react-icons/md'
Expand All @@ -13,6 +13,8 @@ export default function Page(
{ params }: { params: { level: number } }
) {
const [rerenderCount, setRerenderCount] = useState(0)
const searchParams = useSearchParams()
const onlyStarted = searchParams.get('started') === 'true'

function rerender() {
setRerenderCount(c => c + 1)
Expand All @@ -21,7 +23,7 @@ export default function Page(
return <>
<Navigation rerender={rerender} />
<Center>
<Content level={params.level} rerender={rerender} rerenderCount={rerenderCount} />
<Content level={params.level} onlyStarted={onlyStarted} rerender={rerender} rerenderCount={rerenderCount} />
</Center>
</>
}
Expand All @@ -41,21 +43,28 @@ function ActionButton(
}

function Content(
{ level, rerender, rerenderCount }: { level: number, rerender: () => void, rerenderCount: number }
{ level, onlyStarted, rerender, rerenderCount }: { level: number, onlyStarted: boolean, rerender: () => void, rerenderCount: number }
) {
const [kanjis, setKanjis] = useState<Kanji[]>([])
const [kanjis, setKanjis] = useState<Kanji[] | null>(null)
const [status, setStatus] = useState<WanikaniStatus>('loading')
const router = useRouter()

useEffect(() => {
Promise.all([getWanikaniStatus(), getKanjiForLevel(level)]).then(([status, k]) => {
Promise.all(
[
getWanikaniStatus(),
getKanjiForLevel(level),
onlyStarted ? getStartedKanjiIds(level) : Promise.resolve(null),
]
).then(([status, ks, started]) => {
setStatus(status)
if (k === null) {
if (ks === null) {
return
}
setKanjis(shuffled(k))
const kanjisFiltered = ks.filter(k => started == null || started.includes(k.id))
setKanjis(shuffled(kanjisFiltered))
})
}, [rerenderCount, level])
}, [rerenderCount, level, onlyStarted])

if (status === 'no_key' || status === 'invalid_key') {
return <div className='flex flex-col gap-4'>
Expand All @@ -74,13 +83,20 @@ function Content(
</div>
}

if (status === 'loading') {
if (status === 'loading' || kanjis === null) {
return <div>Loading...</div>
}

return <div className='flex flex-col items-center gap-4' key={rerenderCount}>
if (kanjis.length === 0 && onlyStarted) {
return <div className='flex flex-col gap-4 items-center'>
<div className='text-xl'> No kanji are started at level {level}</div>
<Button color='secondary' variant='bordered' onPress={() => router.push('/')}>Chose another level</Button>
</div>
}

return <div className='flex flex-col items-center gap-4'>
<h1 className='font-bold text-3xl mb-4'>Kanji for level {level}</h1>
<LearnKanji kanjis={kanjis} rerender={rerender} />
<LearnKanji kanjis={kanjis} rerender={rerender} key={kanjis.length} />
</div>
}

Expand Down
42 changes: 36 additions & 6 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,29 @@ import { Center } from '@/app/componets/center'
import { Footer } from '@/app/componets/footer'
import { GitHubRibbon } from '@/app/componets/github_ribbon'
import { getWanikaniStatus, useApiKey, WanikaniStatus } from '@/app/wanikani/WaniKani'
import { Button, CircularProgress, Input, Link } from '@nextui-org/react'
import { Button, ButtonGroup, CircularProgress, cn, Input, Link } from '@nextui-org/react'
import React, { useCallback, useEffect, useRef, useState } from 'react'


export default function Page() {
const [filter, setFilter] = useState<KanjiFilter>('started')

const [wanikaniStatus, setWanikaniStatus] = useState<WanikaniStatus>('no_key')
return <>
<GitHubRibbon />
<Center>
<ApiKeyInput wanikaniStatus={wanikaniStatus} setWanikaniStatus={setWanikaniStatus} />

<div className={wanikaniStatus === '3lvl' || wanikaniStatus == '60lvl' ? 'visisble' : 'invisible'}>
<h1 className='text-3xl text-center mt-16 mb-8'>Chose Kanji Level</h1>

<h1 className='text-3xl text-center mt-16'>Chose Kanji Level</h1>
<KanjiFilterButtons classNames='my-6 w-full' filter={filter} setFilter={setFilter} />
<div className='grid grid-cols-5 sm:grid-cols-10 gap-2'>
{
Array.from({ length: 60 }, (_, i) => i + 1)
.map((i) =>
<Button isDisabled={wanikaniStatus === '3lvl' && i > 3}
className='hover:bg-secondary' key={i} as={Link} href={`/kanji/${i}`} isIconOnly> {i} </Button>
className='hover:bg-secondary' key={i} as={Link} href={`/kanji/${i}${filter === 'started' ? '?started=true' : ''}`} isIconOnly> {i} </Button>
)
}
</div>
Expand Down Expand Up @@ -49,15 +55,15 @@ function ApiKeyInput(
break
}
getWanikaniStatus().then(status => setWanikaniStatus(status))
}, [])
}, [setWanikaniStatus])

useEffect(() => { checkValidity() }, [checkValidity])

return <div className='flex flex-col gap-4 items-stretch justify-center'>
<Input variant='bordered' type='password' label='API Key' placeholder='Enter your WaniKani API Key'
defaultValue={inputValue.current}
color={wanikaniStatus === '3lvl' || wanikaniStatus == '60lvl' ? 'success' : wanikaniStatus === 'invalid_key' ? 'danger' : 'default'}
endContent={wanikaniStatus === 'loading' ? <CircularProgress size="sm" color="default" aria-label="Loading..." /> : <></>}
endContent={wanikaniStatus === 'loading' ? <CircularProgress size='sm' color='default' aria-label='Loading...' /> : <></>}
isInvalid={wanikaniStatus === 'invalid_key'}
errorMessage='Invalid API Key'
description={<span>
Expand All @@ -70,4 +76,28 @@ function ApiKeyInput(
checkValidity()
}} color='secondary'>Update API Key</Button>
</div>
}
}

type KanjiFilter = 'all' | 'started'

function KanjiFilterButtons(
{ filter, setFilter, classNames }
: { filter: KanjiFilter, setFilter: (filter: KanjiFilter) => void, classNames?: string }
) {
return (
<div className={cn('flex justify-center mt-4', classNames)}>
<ButtonGroup>
<Button
color={filter === 'all' ? 'secondary' : 'default'}
onPress={() => setFilter('all')} >
All Kanji
</Button>
<Button
color={filter === 'started' ? 'secondary' : 'default'}
onPress={() => setFilter('started')}>
Started Kanji
</Button>
</ButtonGroup>
</div>
)
}
10 changes: 10 additions & 0 deletions app/wanikani/WaniKani.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,22 @@ export async function getKanjiForLevel(level: number): Promise<Kanji[] | null> {
const json = await response.json()
return json.data.map((d: any) => {
return {
id: d.id,
kanji: d.data.characters,
meaning: d.data.meanings.find((m: any) => m.primary).meaning,
reading: d.data.readings.find((r: any) => r.primary).reading,
}
})
}

export async function getStartedKanjiIds(level: number): Promise<number[] | null> {
const headers = createWanikaniHeaders()
const response = await fetch(`${API_BASE_URL}/assignments?subject_types=kanji&started=true&levels=${level}`, { headers: headers })
if (!response.ok) return null
const json = await response.json()
return json.data.map((d: any) => d.data.subject_id)
}

function createWanikaniHeaders(): Headers {
const headers = new Headers()
headers.append('Authorization', `Bearer ${localStorage.getItem(KEY)?.trim()}`)
Expand All @@ -42,6 +51,7 @@ function createWanikaniHeaders(): Headers {
const API_BASE_URL = 'https://api.wanikani.com/v2'

export interface Kanji {
id: number
kanji: string
meaning: string
reading: string
Expand Down
120 changes: 120 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 646a55d

Please sign in to comment.