├── vocabularybuilder ├── public │ ├── _redirects │ ├── robots.txt │ ├── manifest.json │ └── index.html ├── src │ ├── index.css │ ├── setupTests.js │ ├── services │ │ ├── pexelAPI.js │ │ └── wordAPI.js │ ├── reportWebVitals.js │ ├── pages │ │ ├── NotFound.js │ │ ├── Review │ │ │ ├── QuizResult.js │ │ │ ├── FlashcardsResult.js │ │ │ ├── Quiz.js │ │ │ ├── Flashcards.js │ │ │ ├── Review.js │ │ │ ├── FlashcardsMode.js │ │ │ ├── QuizMode.jsx │ │ │ ├── QuizQuestion.js │ │ │ └── Card.js │ │ ├── Search │ │ │ ├── Search.js │ │ │ ├── PossibleWords.js │ │ │ └── WordMeanings.js │ │ ├── Home.js │ │ └── Journal.js │ ├── components │ │ ├── Common │ │ │ ├── Overlay.js │ │ │ ├── TextArea.js │ │ │ ├── SearchField.js │ │ │ ├── Popup.js │ │ │ ├── Button.js │ │ │ └── Filter.js │ │ ├── UI │ │ │ ├── Layout.js │ │ │ ├── Footer.js │ │ │ └── Header.js │ │ └── Features │ │ │ ├── Review │ │ │ ├── BackPopup.js │ │ │ ├── NumberChoice.jsx │ │ │ ├── EndSessionPopup.js │ │ │ ├── ModeChoice.js │ │ │ ├── ResultTable.js │ │ │ ├── MCQuestion.js │ │ │ └── BlanksQuestion.jsx │ │ │ ├── WordForm │ │ │ ├── WordFormHeader.js │ │ │ ├── WordFormField.js │ │ │ ├── ImageDropZone.js │ │ │ ├── WordFormImages.js │ │ │ └── WordForm.js │ │ │ └── Word │ │ │ ├── WordDetails.js │ │ │ ├── Word.js │ │ │ └── WordHeader.js │ ├── hooks │ │ ├── useIsMobile.js │ │ └── useReviewResult.js │ ├── index.js │ ├── store.js │ ├── reducers │ │ ├── possibleWordsReducer.js │ │ ├── searchReducer.js │ │ ├── possibleWordsReducer.test.js │ │ ├── searchReducer.test.js │ │ ├── flashcardsReducer.js │ │ ├── flashcardsReducer.test.js │ │ ├── quizReducer.js │ │ ├── journalReducer.test.js │ │ ├── wordMeaningsReducer.test.js │ │ ├── journalReducer.js │ │ ├── quizReducer.test.js │ │ └── wordMeaningsReducer.js │ ├── App.js │ └── utils │ │ └── reviewHelper.js ├── example.env ├── tailwind.config.js ├── .eslintrc.json ├── .gitignore └── package.json └── README.md /vocabularybuilder/public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /vocabularybuilder/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /vocabularybuilder/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /vocabularybuilder/example.env: -------------------------------------------------------------------------------- 1 | REACT_APP_WORD_API_KEY= 2 | REACT_APP_WORD_API_HOST="wordsapiv1.p.rapidapi.com" 3 | REACT_APP_PEXELS_API_KEY= 4 | -------------------------------------------------------------------------------- /vocabularybuilder/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{html,js,jsx}'], 4 | theme: { 5 | extend: {} 6 | }, 7 | plugins: [] 8 | } 9 | -------------------------------------------------------------------------------- /vocabularybuilder/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /vocabularybuilder/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /vocabularybuilder/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "standard", 8 | "plugin:react/recommended" 9 | ], 10 | "parserOptions": { 11 | "ecmaVersion": "latest", 12 | "sourceType": "module" 13 | }, 14 | "plugins": [ 15 | "react" 16 | ], 17 | "rules": { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vocabularybuilder/src/services/pexelAPI.js: -------------------------------------------------------------------------------- 1 | import { createClient } from 'pexels' 2 | 3 | const getImage = async (query, num = 1) => { 4 | const client = createClient(process.env.REACT_APP_PEXELS_API_KEY) 5 | try { 6 | const result = await client.photos.search({ query, per_page: num }) 7 | return result.photos 8 | } catch (error) { 9 | console.error(error) 10 | throw error 11 | } 12 | } 13 | 14 | export { getImage } 15 | -------------------------------------------------------------------------------- /vocabularybuilder/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry) 5 | getFID(onPerfEntry) 6 | getFCP(onPerfEntry) 7 | getLCP(onPerfEntry) 8 | getTTFB(onPerfEntry) 9 | }) 10 | } 11 | } 12 | 13 | export default reportWebVitals 14 | -------------------------------------------------------------------------------- /vocabularybuilder/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .env 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | netlify.toml 26 | -------------------------------------------------------------------------------- /vocabularybuilder/src/pages/NotFound.js: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom' 2 | import React from 'react' 3 | 4 | const NotFound = () => { 5 | return ( 6 |
7 |

Vocabulary Builder: Page Not Found

8 | Click{' '} 9 | 12 | here 13 | {' '} 14 | to go back to the home page. 15 |
16 | ) 17 | } 18 | 19 | export default NotFound 20 | -------------------------------------------------------------------------------- /vocabularybuilder/src/components/Common/Overlay.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const Overlay = ({ children }) => { 5 | return ( 6 | <> 7 |
8 |
9 | {children} 10 |
11 | 12 | ) 13 | } 14 | 15 | export default Overlay 16 | 17 | Overlay.propTypes = { 18 | children: PropTypes.node.isRequired 19 | } 20 | -------------------------------------------------------------------------------- /vocabularybuilder/src/hooks/useIsMobile.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | const useIsMobile = (screenSize) => { 4 | const [isMobile, setIsMobile] = useState(false) 5 | 6 | useEffect(() => { 7 | const handleResize = () => { 8 | setIsMobile(window.innerWidth <= screenSize) 9 | } 10 | 11 | handleResize() 12 | window.addEventListener('resize', handleResize) 13 | return () => window.removeEventListener('resize', handleResize) 14 | }, [screenSize]) 15 | return { isMobile } 16 | } 17 | 18 | export default useIsMobile 19 | -------------------------------------------------------------------------------- /vocabularybuilder/src/components/UI/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Outlet } from 'react-router-dom' 3 | import Header from './Header' 4 | import Footer from './Footer' 5 | 6 | const Layout = () => { 7 | return ( 8 |
9 |
10 |
11 | 12 |
13 |
14 |
15 | ) 16 | } 17 | 18 | export default Layout 19 | -------------------------------------------------------------------------------- /vocabularybuilder/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import './index.css' 4 | import reportWebVitals from './reportWebVitals' 5 | import { Provider } from 'react-redux' 6 | import store from './store' 7 | import App from './App' 8 | 9 | const root = ReactDOM.createRoot(document.getElementById('root')) 10 | root.render( 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | 18 | // If you want to start measuring performance in your app, pass a function 19 | // to log results (for example: reportWebVitals(console.log)) 20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 21 | reportWebVitals() 22 | -------------------------------------------------------------------------------- /vocabularybuilder/src/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit' 2 | import journalReducer from './reducers/journalReducer' 3 | import searchReducer from './reducers/searchReducer' 4 | import possibleWordsReducer from './reducers/possibleWordsReducer' 5 | import wordMeaningsReducer from './reducers/wordMeaningsReducer' 6 | import flashcardsReducer from './reducers/flashcardsReducer' 7 | import quizReducer from './reducers/quizReducer' 8 | 9 | const store = configureStore({ 10 | reducer: { 11 | journal: journalReducer, 12 | search: searchReducer, 13 | possibleWords: possibleWordsReducer, 14 | wordMeanings: wordMeaningsReducer, 15 | flashcards: flashcardsReducer, 16 | quiz: quizReducer 17 | } 18 | }) 19 | 20 | export default store 21 | -------------------------------------------------------------------------------- /vocabularybuilder/src/reducers/possibleWordsReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | const initialState = { 4 | matchedWords: [], 5 | isLoading: false, 6 | error: null 7 | } 8 | 9 | const possibleWordsSlice = createSlice({ 10 | name: 'possibleWords', 11 | initialState, 12 | reducers: { 13 | setMatchedWords: (state, action) => { 14 | state.matchedWords = action.payload 15 | }, 16 | setIsLoading: (state, action) => { 17 | state.isLoading = action.payload 18 | }, 19 | setError: (state, action) => { 20 | state.error = action.payload 21 | } 22 | } 23 | }) 24 | 25 | export const { setMatchedWords, setIsLoading, setError } = 26 | possibleWordsSlice.actions 27 | export default possibleWordsSlice.reducer 28 | -------------------------------------------------------------------------------- /vocabularybuilder/src/reducers/searchReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | const initialState = { 4 | searchValue: '', 5 | showForm: false, 6 | formWord: '', 7 | currentPage: 'search' 8 | } 9 | 10 | const searchSlice = createSlice({ 11 | name: 'search', 12 | initialState, 13 | reducers: { 14 | setSearchValue: (state, action) => { 15 | state.searchValue = action.payload 16 | }, 17 | setShowForm: (state, action) => { 18 | state.showForm = action.payload 19 | }, 20 | setFormWord: (state, action) => { 21 | state.formWord = action.payload 22 | }, 23 | setCurrentPage: (state, action) => { 24 | state.currentPage = action.payload 25 | } 26 | } 27 | }) 28 | 29 | export const { setSearchValue, setShowForm, setFormWord, setCurrentPage } = 30 | searchSlice.actions 31 | export default searchSlice.reducer 32 | -------------------------------------------------------------------------------- /vocabularybuilder/src/pages/Review/QuizResult.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux' 2 | import { useNavigate } from 'react-router-dom' 3 | import ResultTable from '../../components/Features/Review/ResultTable' 4 | import useReviewResult from '../../hooks/useReviewResult' 5 | import React from 'react' 6 | 7 | const QuizResult = () => { 8 | const { wordArray } = useSelector((state) => state.quiz) 9 | const navigate = useNavigate() 10 | useReviewResult('quiz', wordArray) 11 | 12 | return ( 13 |
14 |

Result

15 | 16 | 21 |
22 | ) 23 | } 24 | 25 | export default QuizResult 26 | -------------------------------------------------------------------------------- /vocabularybuilder/src/components/Common/TextArea.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import PropType from 'prop-types' 3 | 4 | const TextArea = ({ id, name, index, value, onBlur, height }) => { 5 | const [text, setText] = useState(value) 6 | 7 | const handleChange = (e) => { 8 | setText(e.target.value) 9 | } 10 | 11 | return ( 12 | <> 13 |