diff --git a/.gitignore b/.gitignore index 1d2246a..78b6ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ dist-ssr *.sln *.sw? -firebase.config.ts \ No newline at end of file +src/plugins/firebase.config.ts \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index de074b8..d1c486b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,13 @@ import { Notifications } from "@mantine/notifications" import { BrowserRouter as Router } from "react-router-dom" -import DetermineLayout from './determineLayout.tsx' +import Layout from './layouts' export default function App() { return ( <> - + ) diff --git a/src/axios.ts b/src/axios.ts index ab13acd..3ad585b 100644 --- a/src/axios.ts +++ b/src/axios.ts @@ -1,17 +1,15 @@ import _axios from "axios" // import useUserStore from "./store/user" -import { getAuth, getIdToken, onAuthStateChanged } from "firebase/auth" +import { Auth, User, getAuth, getIdToken, onAuthStateChanged } from "firebase/auth" -// const auth = getAuth() -// let idToken = '' -// onAuthStateChanged(auth, async (user) => { -// if (!user) return; - -// idToken = await getIdToken(user, true) -// }) - -// const idToken = use -// const idToken = useUserStore.getState().idToken +const checkFirebaseAuth = (auth: Auth) => { + return new Promise((resolve) => { + onAuthStateChanged(auth, (user) => { + // user オブジェクトを resolve + resolve(user); + }); + }); +} const axios = _axios.create({ baseURL: 'http://localhost:9000/v2', @@ -23,18 +21,16 @@ const axios = _axios.create({ responseType: 'json', }) -axios.interceptors.request.use((request) => { +axios.interceptors.request.use(async (request) => { //リクエスト前に毎回idTokenを取得する const auth = getAuth() - onAuthStateChanged(auth, async (user) => { - if (!user) return; - - const idToken = await getIdToken(user, true) + const user = await checkFirebaseAuth(auth) - if (!idToken) return; + if (!user) return request; - request.headers.Authorization = idToken - }) + const idToken = await getIdToken(user, true) + request.headers.Authorization = idToken + return request },(error) => { // リクエスト エラーの処理 diff --git a/src/components/FilteringLevel.tsx b/src/components/FilteringLevel.tsx new file mode 100644 index 0000000..487f0ef --- /dev/null +++ b/src/components/FilteringLevel.tsx @@ -0,0 +1,43 @@ +import useSWR from "swr"; +import { fetcher } from "../fetchers"; +import { Checkbox, DefaultProps, Group } from "@mantine/core"; + +interface FilteringLevelProps extends DefaultProps { + value: string[] | undefined, + onChange: React.Dispatch>, +} + +export interface Level{ + id: number, + name: string, + color: string, +} + +export default function FilteringLevel({ + value, + onChange, + ...others +}: FilteringLevelProps) { + const { data: levels } = useSWR('/levels', fetcher) + + return ( + + + {levels?.map(level => + + )} + + + ) +} \ No newline at end of file diff --git a/src/components/FilteringWord.tsx b/src/components/FilteringWord.tsx new file mode 100644 index 0000000..7a063f9 --- /dev/null +++ b/src/components/FilteringWord.tsx @@ -0,0 +1,51 @@ +import { DefaultProps, Group, Radio, TextInput } from "@mantine/core" +import { formInputProps } from "../hooks" + +export type optionProps = { + value: string, + onChange: (value: string) => void +} + +interface FilteringWordProps extends DefaultProps { + wordInputProps: formInputProps, + wordSearchOption: optionProps, +} + +export default function FilteringWord({ + wordInputProps, + wordSearchOption, + className, + ...others +}: FilteringWordProps) { + return ( + <> + + + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/components/FilteringWorkbook.tsx b/src/components/FilteringWorkbook.tsx new file mode 100644 index 0000000..c2e561a --- /dev/null +++ b/src/components/FilteringWorkbook.tsx @@ -0,0 +1,57 @@ +import React, { forwardRef } from "react"; +import useSWR from "swr"; +import { fetcher } from "../fetchers"; +import { Badge, DefaultProps, Group, MultiSelect } from "@mantine/core"; + +interface WorkbookProps extends React.ComponentPropsWithoutRef<'div'> { + id: string, + label: string, + color: string, +} + +export interface Workbook { + id: number, + label: string, + color: string, +} + +interface FilteringWorkbookProps extends DefaultProps { + value: string[] | undefined, + onChange: React.Dispatch> +} + +export default function FilteringWorkbook({ + value, + onChange, + ...others + }: FilteringWorkbookProps ) { + const { data: workbooks } = useSWR('/workbooks/color', fetcher) + + const data = workbooks ? workbooks.map(({id, label, ...others}) => ({...others, value: String(id), key: id, label})) : [] + + const Item = forwardRef( + ({ id, label, color, ...others}: WorkbookProps, ref) => ( +
+ + {label} + +
+ )) + + return ( + !selected && (item.label?.includes(value) || false)} + value={value} + onChange={onChange} + {...others} + /> + ) +} \ No newline at end of file diff --git a/src/components/QuizCard.tsx b/src/components/QuizCard.tsx index 5df6490..9102ae9 100644 --- a/src/components/QuizCard.tsx +++ b/src/components/QuizCard.tsx @@ -1,7 +1,16 @@ import { Badge, Card, DefaultProps, Flex, Group, MantineNumberSize, Selectors, Text, } from "@mantine/core"; import QuizMylistButton from "./QuizMylistButton"; import QuizFavoriteButton from "./QuizFavoriteButton"; -import useStyles, { QuizCardStylesParams } from "./QuizCard.styles"; +import useStyles, { QuizCardStylesParams } from "./styles/QuizCard.styles"; + +export interface Quiz { + id: number, + question: string, + answer: string, + workbook: string, + level: string, + date: string, +} // このtypeは,useStyleに定義されたすべてのselectorsを含む結合が存在する. // ここではroot | title | descriptionである. @@ -10,8 +19,7 @@ type QuizCardStylesNames = Selectors interface QuizCardProps extends DefaultProps { margin?: MantineNumberSize, index: number, - question: string, - answer: string, + quiz: Quiz, } export default function QuizCard({ @@ -20,10 +28,8 @@ export default function QuizCard({ unstyled, className, margin, - index = 1, - question = "", - answer="", - + index, + quiz, }: QuizCardProps) { const { classes, cx } = useStyles( { margin }, @@ -36,16 +42,21 @@ export default function QuizCard({ No.{index} - {question} + {quiz.question} - {answer} + {quiz.answer} - abc2014 + + {quiz.workbook}({quiz.date.slice(0, 4)}) + ) diff --git a/src/components/QuizCardList.tsx b/src/components/QuizCardList.tsx index 6ce0321..f577d8d 100644 --- a/src/components/QuizCardList.tsx +++ b/src/components/QuizCardList.tsx @@ -1,16 +1,9 @@ import QuizCard from "./QuizCard" -import { useEffect } from "react" import useQuizzesStore from "../store/quiz" import { Center, Loader } from "@mantine/core" -// import { useFetch } from "../hooks" export default function QuizCardList() { const quizzes = useQuizzesStore(state => state.quizzes) - const getQuizzes = useQuizzesStore(state => state.getQuiz) - - useEffect(() => { - getQuizzes() - }, []) if (!quizzes) { return ( @@ -22,12 +15,11 @@ export default function QuizCardList() { return ( <> - {quizzes?.map(({question, answer}, idx) => ( + {quizzes?.map((quiz, idx) => ( ({ root: { marginBottom: theme.spacing.xs diff --git a/src/components/QuizFilteringModal.tsx b/src/components/QuizFilteringModal.tsx new file mode 100644 index 0000000..c68538d --- /dev/null +++ b/src/components/QuizFilteringModal.tsx @@ -0,0 +1,78 @@ +import { Button, Group, Modal } from "@mantine/core" +import { useDisclosure } from "@mantine/hooks" +import { useState } from "react" +import FilteringWorkbook from "./FilteringWorkbook" +import FilteringLevel from "./FilteringLevel" +import { useInput, useIsMobile } from "../hooks" +import FilteringWord from "./FilteringWord" +import { IconFilter, IconSearch } from "@tabler/icons-react" +import useQuizzesStore, { QuizRequestParams } from "../store/quiz" + +export type Level = { + id: number, + name: string, + color: string, +} + +export default function QuizFilteringModal() { + const [ opened, { open, close } ] = useDisclosure(true) + const [ selectedWorkbook, setSelectedWorkbook ] = useState([]) + const [ selectedLevel, setSelectedLevel ] = useState([]) + const [ keywordProps ] = useInput('') + const [ keywordOption, setkeywordOption ] = useState('1') + const isMobile = useIsMobile() + const getQuiz = useQuizzesStore(state => state.getQuiz) + + const filtering = async () => { + const params: QuizRequestParams = { + workbook: selectedWorkbook, + level: selectedLevel, + keyword: keywordProps.value, + keywordOption: keywordOption + } + close() + await getQuiz(params) + } + + return ( + <> + + + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/components/QuizCard.styles.ts b/src/components/styles/QuizCard.styles.ts similarity index 100% rename from src/components/QuizCard.styles.ts rename to src/components/styles/QuizCard.styles.ts diff --git a/src/hooks.ts b/src/hooks.ts index cb3b28e..71778ca 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,16 +1,21 @@ import { useEffect, useState } from "react" import useUserStore from "./store/user" +import { useMediaQuery } from "@mantine/hooks" + +export interface formInputProps { + value: string, + onChange: (e: React.ChangeEvent) => void +} + +export type formInputReset = () => void export const useInput = ( - initialValue: number | string, + initialValue: string, ): [ - { - value: number | string, - onChange: (e: React.ChangeEvent) => void - }, - () => void, + formInputProps, + formInputReset ] => { - const [ value, setValue ] = useState(initialValue) + const [ value, setValue ] = useState(initialValue) const props = { value, @@ -23,6 +28,11 @@ export const useInput = ( ] } +export const useIsMobile = () => { + const matches = useMediaQuery('(min-width: 48em)'); + return !matches +} + export const useFetch = ( url: string, init?: RequestInit diff --git a/src/layouts/login.tsx b/src/layouts/draft.tsx similarity index 100% rename from src/layouts/login.tsx rename to src/layouts/draft.tsx diff --git a/src/determineLayout.tsx b/src/layouts/index.tsx similarity index 58% rename from src/determineLayout.tsx rename to src/layouts/index.tsx index df8b292..8e5f07f 100644 --- a/src/determineLayout.tsx +++ b/src/layouts/index.tsx @@ -1,21 +1,22 @@ -import { Route, Routes, useLocation } from "react-router-dom" -import Home from "./routers/home" -import Login from "./routers/login" -import DefaultLayout from "./layouts/default" -import LoginLayout from "./layouts/login" - -export default function Root() { - const location = useLocation() - const requiredLogin = location.pathname !== '/login' - - const Layout = requiredLogin ? DefaultLayout : LoginLayout - - return ( - - - }/> - }/> - - - ) +import { Route, Routes, useLocation } from "react-router-dom" +import Home from "../pages/home" +import Login from "../pages/login" +import DefaultLayout from "./default" +import DraftLayout from "./draft" + +export default function Root() { + const location = useLocation() + const requiredLogin = location.pathname !== '/login' + + const Layout = requiredLogin ? DefaultLayout : DraftLayout + //const Layout = DefaultLayout + + return ( + + + }/> + }/> + + + ) } \ No newline at end of file diff --git a/src/routers/home.tsx b/src/pages/home.tsx similarity index 57% rename from src/routers/home.tsx rename to src/pages/home.tsx index dea22ba..25c03ae 100644 --- a/src/routers/home.tsx +++ b/src/pages/home.tsx @@ -1,10 +1,10 @@ import QuizCardList from '../components/QuizCardList' -import LogoutButtom from '../components/LogoutButton' +import QuizFilteringModal from '../components/QuizFilteringModal' export default function Home() { return ( <> - + ) diff --git a/src/routers/login.tsx b/src/pages/login.tsx similarity index 93% rename from src/routers/login.tsx rename to src/pages/login.tsx index 3a8ee42..4a8c965 100644 --- a/src/routers/login.tsx +++ b/src/pages/login.tsx @@ -1,5 +1,5 @@ import { useNavigate } from "react-router-dom" -import { auth } from "../firebase" +import { auth } from "../plugins/firebase" import { signInWithEmailAndPassword } from "firebase/auth" import { Button, Center, Grid, Paper, PasswordInput, TextInput } from "@mantine/core" import { useInput } from "../hooks" diff --git a/src/firebase.ts b/src/plugins/firebase.ts similarity index 100% rename from src/firebase.ts rename to src/plugins/firebase.ts diff --git a/src/store/quiz.ts b/src/store/quiz.ts index e06728d..40f7780 100644 --- a/src/store/quiz.ts +++ b/src/store/quiz.ts @@ -1,6 +1,5 @@ import { create } from 'zustand' import axios from '../axios' -import { AxiosRequestConfig } from 'axios' export type Quiz = { id: number, @@ -8,35 +7,36 @@ export type Quiz = { answer: string, workbook: string, level: string, - date: Date, + date: string, total: number, right: number, isFavorite: boolean, registerdMylist: number[], } -export type QuizRequestParams = { - page: number, - maxView: number, - seed: number, - workbooks: number[], - levels: number[], - queWord: string, - ansWord: string, - start?: string, - end?: string, - judgement?: string, +export interface QuizRequestParams { + page?: number, + maxView?: number, + seed?: number, + workbook?: string[], + level?: string[], + keyword?: string, + keywordOption?: string, + since?: string, + until?: string, + judgement?: number, } export type QuizState = { quizzes: Quiz[] | null, - getQuiz: (params?: AxiosRequestConfig) => void + getQuiz: (params?: QuizRequestParams) => void } const useQuizzesStore = create((set) => ({ quizzes: null, - getQuiz: async (params) => { - const quizzes = await axios.get('/quizzes/8', params).then(res => res.data) + getQuiz: async (params?: QuizRequestParams) => { + console.log(params) + const quizzes = await axios.get('/quizzes/8', { params }).then(res => res.data) set({ quizzes }) } })) diff --git a/src/store/workbook.ts b/src/store/workbook.ts new file mode 100644 index 0000000..17b27ef --- /dev/null +++ b/src/store/workbook.ts @@ -0,0 +1,23 @@ +import { create } from "zustand" +import axios from "../axios" + +export type Workbook = { + id: number, + label: string, + color: string, +} + +export type WorkbookState = { + workbooks: Workbook[] | null, + getWorkbook: () => void +} + +const useWorkbookStore = create((set) => ({ + workbooks: null, + getWorkbook: async () => { + const workbooks = await axios('/workbooks/color').then(res => res.data) + set({ workbooks }) + } +})) + +export default useWorkbookStore \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 5a33944..3bdb42a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,5 +3,10 @@ import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ + server: { + proxy: { + "^/api/.*": "http://localhost:4000", + }, + }, plugins: [react()], })