diff --git a/package.json b/package.json
index 4ae2436..d9c1257 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@chakra-ui/react": "^2.10.3",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
+ "axios": "^1.7.7",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.35.0",
"framer-motion": "^11.11.10",
diff --git a/src/components/search-bar/SearchBar.tsx b/src/components/search-bar/SearchBar.tsx
new file mode 100644
index 0000000..82eba0f
--- /dev/null
+++ b/src/components/search-bar/SearchBar.tsx
@@ -0,0 +1,43 @@
+import { IconButton, Input, InputGroup, InputRightElement } from "@chakra-ui/react";
+import { Search } from "lucide-react";
+import React from "react";
+import { useNavigate } from "react-router-dom";
+
+type Props = {
+ word: string;
+};
+
+const SearchBar = ({ word }: Props) => {
+ const navigate = useNavigate();
+ const [searchText, setSearchText] = React.useState(word);
+ const handleSearch = (e: React.FormEvent) => {
+ e.preventDefault();
+ navigate("/dictionary?q=" + searchText);
+ };
+
+ return (
+
+ );
+};
+
+export default SearchBar;
diff --git a/src/components/word-result/WordResult.tsx b/src/components/word-result/WordResult.tsx
new file mode 100644
index 0000000..51c1434
--- /dev/null
+++ b/src/components/word-result/WordResult.tsx
@@ -0,0 +1,73 @@
+import { Volume2 } from "lucide-react";
+import { Box, Code, Heading, HStack, IconButton, Text, VStack } from "@chakra-ui/react";
+import { IWord, IWordNotFound } from "@/types/dictionary";
+
+type Props = {
+ wordFound: IWord[] | undefined;
+ wordNotFound: IWordNotFound | undefined;
+};
+
+const WordResult = ({ wordFound, wordNotFound }: Props) => {
+ return (
+ <>
+ {wordFound && (
+ <>
+
+ {wordFound[0].word}
+ {wordFound[0].phonetics
+ .filter((e) => e.audio != "")
+ .map((phonetic, index) => (
+
+ }
+ bg="transparent"
+ _hover={{ bg: "rgba(0, 0, 0, 0.04)" }}
+ onClick={() => new Audio(phonetic.audio).play()}
+ />
+
+ {phonetic.text}
+
+
+ ))}
+
+ {wordFound.map((wordFound, index) => (
+
+ {wordFound.meanings.map((meaning, index) => (
+
+
+ {meaning.partOfSpeech}
+
+ {meaning.definitions.map((definition, index) => (
+
+ {definition.definition}
+ {definition.example && (
+
+
+ Example:
+
+ {definition.example}
+
+ )}
+
+ ))}
+
+ ))}
+
+ ))}
+ >
+ )}
+ {wordNotFound && (
+
+ {wordNotFound.title}
+
+ {wordNotFound.message}
+ {wordNotFound.resolution}
+
+
+ )}
+ >
+ );
+};
+
+export default WordResult;
diff --git a/src/config/api/dictionary/apiDictionary.ts b/src/config/api/dictionary/apiDictionary.ts
new file mode 100644
index 0000000..4c73283
--- /dev/null
+++ b/src/config/api/dictionary/apiDictionary.ts
@@ -0,0 +1,13 @@
+import { AxiosResponse } from "axios";
+import AxiosClient from "./axios";
+
+export const searchWord = async (word: string) => {
+ const response: AxiosResponse = await AxiosClient.get(`/${word}`);
+ if (response.status == 404) {
+ return response.data;
+ }
+ if (response.status != 200) {
+ throw new Error("Something went wrong");
+ }
+ return response;
+};
diff --git a/src/config/api/dictionary/axios/index.ts b/src/config/api/dictionary/axios/index.ts
new file mode 100644
index 0000000..eab3473
--- /dev/null
+++ b/src/config/api/dictionary/axios/index.ts
@@ -0,0 +1,13 @@
+import axios from "axios";
+import { setupInterceptors } from "./interceptor";
+
+const AxiosClient = axios.create({
+ baseURL: "https://api.dictionaryapi.dev/api/v2/entries/en",
+ headers: {
+ Accept: "application/json",
+ },
+});
+
+setupInterceptors(AxiosClient);
+
+export default AxiosClient;
diff --git a/src/config/api/dictionary/axios/interceptor.ts b/src/config/api/dictionary/axios/interceptor.ts
new file mode 100644
index 0000000..07f9c62
--- /dev/null
+++ b/src/config/api/dictionary/axios/interceptor.ts
@@ -0,0 +1,30 @@
+import { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from "axios";
+
+interface IRequestAxios extends InternalAxiosRequestConfig {
+ skipLoading?: boolean;
+}
+
+const onRequestConfig = (config: IRequestAxios) => {
+ if (!config.headers["Content-Type"]) {
+ config.headers["Content-Type"] = "application/json";
+ }
+ config.timeout = 30000;
+ return config;
+};
+
+const onRequestError = (error: AxiosError): Promise => {
+ return Promise.reject(error);
+};
+
+const onResponse = (res: AxiosResponse): AxiosResponse => {
+ return res;
+};
+
+const onResponseError = async (err: AxiosError): Promise => {
+ return Promise.reject(err?.response?.data);
+};
+
+export const setupInterceptors = (axiosInstance: AxiosInstance) => {
+ axiosInstance.interceptors.request.use(onRequestConfig, onRequestError);
+ axiosInstance.interceptors.response.use(onResponse, (err: AxiosError) => onResponseError(err));
+};
diff --git a/src/config/router/router.tsx b/src/config/router/router.tsx
index b08b3ff..bcf9af9 100644
--- a/src/config/router/router.tsx
+++ b/src/config/router/router.tsx
@@ -1,11 +1,46 @@
+import DictionaryPage from "@/features/dictionary/DictionaryPage";
+import FlashCardSetsPage from "@/features/flash-card/FlashCardSetsPage";
+import LearnFlashCardPage from "@/features/flash-card/LearnFlashCardPage";
+import GrammarCheckPage from "@/features/grammar-check/GrammarCheckPage";
+import LearnThroughImagesPage from "@/features/learn-through-images/LearnThroughImagesPage";
+import SpellCheckPage from "@/features/spell-check/SpellCheckPage";
import Layout from "@/layout/Layout";
-import { createBrowserRouter } from "react-router-dom";
+import { createBrowserRouter, Navigate } from "react-router-dom";
const router = createBrowserRouter(
[
{
element: ,
- path: "*",
+ children: [
+ {
+ path: "/",
+ element: ,
+ },
+ {
+ path: "/dictionary",
+ element: ,
+ },
+ {
+ path: "/learn-through-images",
+ element: ,
+ },
+ {
+ path: "/learn-flashcard",
+ element: ,
+ },
+ {
+ path: "/learn-flashcard/:ListId",
+ element: ,
+ },
+ {
+ path: "/check-spelling",
+ element: ,
+ },
+ {
+ path: "check-grammar",
+ element: ,
+ },
+ ],
},
],
{
diff --git a/src/features/dictionary/DictionaryPage.tsx b/src/features/dictionary/DictionaryPage.tsx
new file mode 100644
index 0000000..0965576
--- /dev/null
+++ b/src/features/dictionary/DictionaryPage.tsx
@@ -0,0 +1,46 @@
+import SearchBar from "@/components/search-bar/SearchBar";
+import WordResult from "@/components/word-result/WordResult";
+import { searchWord } from "@/config/api/dictionary/apiDictionary";
+import { IWord, IWordNotFound } from "@/types/dictionary";
+import { HStack } from "@chakra-ui/react";
+import { AxiosResponse } from "axios";
+import React from "react";
+import { useSearchParams } from "react-router-dom";
+
+const DictionaryPage = () => {
+ const [searchParams] = useSearchParams();
+ const [word, setWord] = React.useState("");
+ const [wordFound, setWordFound] = React.useState();
+ const [wordNotFound, setWordNotFound] = React.useState();
+
+ React.useEffect(() => {
+ if (!searchParams.has("q")) return;
+ setWord(searchParams.get("q")!);
+ // handle search words
+ searchWord(searchParams!.get("q")!)
+ .then((res: AxiosResponse) => {
+ setWordFound(res.data as IWord[]);
+ setWordNotFound(undefined);
+ })
+ .catch((err) => {
+ console.log(err);
+ setWordFound(undefined);
+ setWordNotFound({
+ title: "No Definitions Found",
+ message: "Sorry pal, we couldn't find definitions for the word you were looking for.",
+ resolution: "You can try the search again at later time or head to the web instead",
+ } as IWordNotFound);
+ });
+ }, [searchParams]);
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+export default DictionaryPage;
diff --git a/src/features/flash-card/FlashCardSetsPage.tsx b/src/features/flash-card/FlashCardSetsPage.tsx
new file mode 100644
index 0000000..d1a5753
--- /dev/null
+++ b/src/features/flash-card/FlashCardSetsPage.tsx
@@ -0,0 +1,5 @@
+const FlashCardSetsPage = () => {
+ return FlashCardSetsPage
;
+};
+
+export default FlashCardSetsPage;
diff --git a/src/features/flash-card/LearnFlashCardPage.tsx b/src/features/flash-card/LearnFlashCardPage.tsx
new file mode 100644
index 0000000..bd16205
--- /dev/null
+++ b/src/features/flash-card/LearnFlashCardPage.tsx
@@ -0,0 +1,5 @@
+const LearnFlashCardPage = () => {
+ return LearnFlashCardPage
;
+};
+
+export default LearnFlashCardPage;
diff --git a/src/features/grammar-check/GrammarCheckPage.tsx b/src/features/grammar-check/GrammarCheckPage.tsx
new file mode 100644
index 0000000..6e14565
--- /dev/null
+++ b/src/features/grammar-check/GrammarCheckPage.tsx
@@ -0,0 +1,5 @@
+const GrammarCheckPage = () => {
+ return GrammarCheckPage
;
+};
+
+export default GrammarCheckPage;
diff --git a/src/features/learn-through-images/LearnThroughImagesPage.tsx b/src/features/learn-through-images/LearnThroughImagesPage.tsx
new file mode 100644
index 0000000..721936b
--- /dev/null
+++ b/src/features/learn-through-images/LearnThroughImagesPage.tsx
@@ -0,0 +1,5 @@
+const LearnThroughImagesPage = () => {
+ return LearnThroughImagesPage
;
+};
+
+export default LearnThroughImagesPage;
diff --git a/src/features/spell-check/SpellCheckPage.tsx b/src/features/spell-check/SpellCheckPage.tsx
new file mode 100644
index 0000000..7dac50d
--- /dev/null
+++ b/src/features/spell-check/SpellCheckPage.tsx
@@ -0,0 +1,5 @@
+const SpellCheckPage = () => {
+ return SpellCheckPage
;
+};
+
+export default SpellCheckPage;
diff --git a/src/layout/Layout.tsx b/src/layout/Layout.tsx
index d4cc021..991e01b 100644
--- a/src/layout/Layout.tsx
+++ b/src/layout/Layout.tsx
@@ -16,7 +16,7 @@ const Layout = () => {
-
+
diff --git a/src/types/dictionary.ts b/src/types/dictionary.ts
new file mode 100644
index 0000000..7f46628
--- /dev/null
+++ b/src/types/dictionary.ts
@@ -0,0 +1,35 @@
+export type IWordNotFound = {
+ title: string;
+ message: string;
+ resolution: string;
+};
+
+export type IWord = {
+ word: string;
+ phonetic: string;
+ phonetics: {
+ text: string;
+ audio: string;
+ sourceUrl: string;
+ license?: {
+ name: string;
+ url: string;
+ };
+ }[];
+ meanings: {
+ partOfSpeech: string;
+ definitions: {
+ definition: string;
+ synonyms: string[];
+ antonyms: string[];
+ example?: string;
+ }[];
+ synonyms: string[];
+ antonyms: string[];
+ }[];
+ license: {
+ name: string;
+ url: string;
+ };
+ sourceUrls: string[];
+};
diff --git a/yarn.lock b/yarn.lock
index 64ba247..ec384b2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1221,6 +1221,11 @@ arraybuffer.prototype.slice@^1.0.3:
is-array-buffer "^3.0.4"
is-shared-array-buffer "^1.0.2"
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
autoprefixer@^10.4.20:
version "10.4.20"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b"
@@ -1240,6 +1245,15 @@ available-typed-arrays@^1.0.7:
dependencies:
possible-typed-array-names "^1.0.0"
+axios@^1.7.7:
+ version "1.7.7"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f"
+ integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==
+ dependencies:
+ follow-redirects "^1.15.6"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
babel-plugin-macros@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
@@ -1392,6 +1406,13 @@ color2k@^2.0.2:
resolved "https://registry.yarnpkg.com/color2k/-/color2k-2.0.3.tgz#a771244f6b6285541c82aa65ff0a0c624046e533"
integrity sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==
+combined-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
@@ -1582,6 +1603,11 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
detect-node-es@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
@@ -2074,6 +2100,11 @@ focus-lock@^1.3.5:
dependencies:
tslib "^2.0.3"
+follow-redirects@^1.15.6:
+ version "1.15.9"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
+ integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
+
for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@@ -2089,6 +2120,15 @@ foreground-child@^3.1.0:
cross-spawn "^7.0.0"
signal-exit "^4.0.1"
+form-data@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48"
+ integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
fraction.js@^4.3.7:
version "4.3.7"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
@@ -2826,6 +2866,18 @@ micromatch@^4.0.4, micromatch@^4.0.5:
braces "^3.0.3"
picomatch "^2.3.1"
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
mimic-fn@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
@@ -3192,6 +3244,11 @@ prop-types@^15.6.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"