diff --git a/PRA/PRA01Demo.webm b/PRA/PRA01Demo.webm new file mode 100644 index 000000000..f2798e6f7 Binary files /dev/null and b/PRA/PRA01Demo.webm differ diff --git a/PRA/PRA01Filter.png b/PRA/PRA01Filter.png new file mode 100644 index 000000000..e22c48b81 Binary files /dev/null and b/PRA/PRA01Filter.png differ diff --git a/PRA/PRA01OnHover.png b/PRA/PRA01OnHover.png new file mode 100644 index 000000000..55dc99eaf Binary files /dev/null and b/PRA/PRA01OnHover.png differ diff --git a/README.md b/README.md new file mode 100644 index 000000000..84a9b6169 --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +# PRA#01-React Components Frontend Development + +## Overview + +This document serves as both a guide and a log for the PRA#01 React frontend development task.
+Images and a video demo can be found in the [PRA](/PRA) folder + +--- + +## PR Submission Checklist + +### **Tasks Completed**: + +    **Common** + +- [x] Postman mock server is set up with correct endpoint. + +- [x] Axios function is implemented in a decoupled manner. + +- [x] User JSON structure includes varied data types. + +- [x] User component renders correctly with avatar. + +- [x] Difficulty levels and filtering are functional. + +- [x] Synonyms rendering is implemented and error-free. + + **Optional** + +- [x] CSS Enhancements: transient effect on hover + +- [ ] Animation Libraries + +- [ ] Enhanced Card Features + +--- + +## Estimated Time for Tasks + +### Common Part + +| Task | Estimated Time | Actual Time | Impediments and errors | +| --------------------------------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------------------------------- | +| Set up Postman mock server | 1 hour | 2:30h | mock server JSON and the data data-api fetched had different structures | +| Implement axios function for data fetching | 1 hour | 1:15 (and back to mock server) | Needed to install font roboto, emotion and material ui. | +| Create user JSON structure | 1 hour | 10 min | | +| Develop user component with avatar | 2 hours | 4 hours | Potsman example didn't have the URL. 
 Error rendering due to useEffect() and find() | +| Add difficulty levels and implement filtering | 2 hours | 1:30 hours | | +| **Total** | **7 hours** | **9:25 hours** | | + +### Optional Part + +| Task | Estimated Time | Actual Time | Impediments and errors | +| ------------------------------------------- | -------------- | ----------- | ---------------------- | +| CSS Enhancements: transient effect on hover | 1:30 hours | 30 min | | +| Animation Libraries | 1 hour | | | +| Enhanced Card Features | 1 hour | | | +| **Total** | **3:30 hours** | 30 min | | + +--- + +## Error Documentation and Solution + +### Error: `TypeError: Cannot read properties of undefined (reading 'map')` + +**Corresponding task:** + +**Description:** This error occurs when trying to use the `.map()` function on a variable that is either `undefined` or `null`. In JavaScript, `map()` is a method available only on arrays, and attempting to call it on something that is not an array (or is undefined) will result in this error. + +**Error Trace:** + +- **Component:** `WordList` +- **File:** `Cards.jsx` +- **Line:** 40 (in `WordList`) +- **Stack Trace:** + - `WordList (Cards.jsx:40:18)` + - `App` + +**Possible Causes:** + +- The variable passed to `.map()` is `undefined` or `null`. +- The variable might not have been initialized or fetched correctly before calling `.map()`. + +**Solution:** +The error was resolved by modifying the `data-api.js` file. The issue was in how the data was being returned from the API call.Original code in `data-api.js`: + +```javascript +export const fetchWords = async () => { try { const response = await axios.get(`${BASE_URL}/words`); +return response.data.words; // This was causing the issue +} catch (error) { + console.error("Error fetching words:", error); + throw error; + } }; +``` + +Updated code in `api.js`: + +```javascript +export const fetchWords = async () => { try { const response = await axios.get(`${BASE_URL}/words`); +return response.data; // This fixed the issue +} catch (error) { console.error("Error fetching words:", error); +throw error; } }; +``` + +**Explanation:** +The API was returning the array of words directly in the `response.data`, not nested under a `words` property. By changing `return response.data.words;` to `return response.data;`, we correctly access the array of words, allowing the `.map()` function to work as expected in the `WordList` component. + +--- + +## **Undefined Errors During Initial Render: useEffect() and find()** + +### **Initial Rendering:** + +- The component is mounted with an initial empty state (`users = []`). +- The code tries to access `user.name` when `user` is `undefined`, causing an error. + +### **Execution of `useEffect`:** + +- `useEffect` runs **after** the initial render. React ensures the DOM is updated before running the effect. +- The `fetchUsers()` function is called inside `useEffect` to fetch the data. + +### **State Update:** + +- When the data is received, `setUsers(data)` updates the state, triggering a re-render of the component. + +### **Rendering with Data:** + +- The component is re-rendered with the correct data. `users.find()` now finds the correct user and renders their name. + +--- + +## **Main Issue** + +- The error occurs because `useEffect` does **not execute immediately** after the component renders; it runs after the initial render. This can lead to unexpected behavior and errors if not properly handled. + +--- + +## **Solution** + +To solve this error, implement a conditional check before rendering the content that depends on the data: + +`return ( <> {user ?

{user.name}

:

Loading...

} );` + +This solution prevents the error on the first render by showing a loading message while the data is still being fetched. + +--- + +## **Best Practices** + +1. **React Hooks Usage:** Ensure that React Hooks are always called at the top level of the component function. Do not place them inside conditions, loops, or nested functions, as React relies on the order of hook execution to manage state properly. +2. **Dependencies in `useEffect`:** Identify all dependencies used inside the `useEffect` hook and ensure they are included in the dependency array. This guarantees that the effect is re-executed when relevant values change. + +### **Alternative Approach Using Loading State:** + +`const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchData = async () => { try { const data = await fetchUsers(); setUsers(data); } finally { setIsLoading(false); } }; fetchData(); }, []); return ( <> {isLoading ?

Loading...

: user &&

{user.name}

} );` + +This approach provides better control over the loading state and avoids errors related to unavailable data during the initial render. + +--- + +## Future Improvements + +- Add unit and integration tests for key components. +- Enhance UI/UX with additional CSS animations or interactive features. +- Refactor components for improved reusability and maintainability. + +--- diff --git a/frontend/pronunciationAppFront/package-lock.json b/frontend/pronunciationAppFront/package-lock.json index 96c25f82d..9e738a9db 100644 --- a/frontend/pronunciationAppFront/package-lock.json +++ b/frontend/pronunciationAppFront/package-lock.json @@ -8,6 +8,12 @@ "name": "pronunciationappfront", "version": "0.0.0", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fontsource/roboto": "^5.1.1", + "@mui/icons-material": "^6.4.0", + "@mui/material": "^6.4.0", + "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -24,6 +30,271 @@ "vite": "^6.0.5" } }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "dependencies": { + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", + "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", + "dependencies": { + "@babel/types": "^7.26.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.5.tgz", + "integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.5", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.5", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", + "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", @@ -554,6 +825,11 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fontsource/roboto": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.1.tgz", + "integrity": "sha512-XwVVXtERDQIM7HPUIbyDe0FP4SRovpjF7zMI8M7pbqFp3ahLJsJTd18h+E6pkar6UbV3btbwkKjYARr5M+SQow==" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -615,6 +891,290 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.0.tgz", + "integrity": "sha512-6u74wi+9zeNlukrCtYYET8Ed/n9AS27DiaXCZKAD3TRGFaqiyYSsQgN2disW83pI/cM1Q2lJY1JX4YfwvNtlNw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.0.tgz", + "integrity": "sha512-zF0Vqt8a+Zp2Oz8P+WvJflba6lLe3PhxIz1NNqn+n4A+wKLPbkeqY8ShmKjPyiCTg0RMbPrp993oUDl9xGsDlQ==", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.4.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.0.tgz", + "integrity": "sha512-hNIgwdM9U3DNmowZ8mU59oFmWoDKjc92FqQnQva3Pxh6xRKWtD2Ej7POUHMX8Dwr1OpcSUlT2+tEMeLb7WYsIg==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.4.0", + "@mui/system": "^6.4.0", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.4.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==" + }, + "node_modules/@mui/private-theming": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.0.tgz", + "integrity": "sha512-rNHci8MP6NOdEWAfZ/RBMO5Rhtp1T6fUDMSmingg9F1T6wiUeodIQ+NuTHh2/pMoUSeP9GdHdgMhMmfsXxOMuw==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz", + "integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.0.tgz", + "integrity": "sha512-wTDyfRlaZCo2sW2IuOsrjeE5dl0Usrs6J7DxE3GwNCVFqS5wMplM2YeNiV3DO7s53RfCqbho+gJY6xaB9KThUA==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.0", + "@mui/styled-engine": "^6.4.0", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.0.tgz", + "integrity": "sha512-woOTATWNsTNR3YBh2Ixkj3l5RaxSiGoC9G8gOpYoFw1mZM77LWJeuMHFax7iIW4ahK0Cr35TF9DKtrafJmOmNQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==" + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.30.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz", @@ -1087,17 +1647,20 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "dev": true + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" }, "node_modules/@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "dev": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1112,6 +1675,14 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@vitejs/plugin-react-swc": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.2.tgz", @@ -1311,6 +1882,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1326,6 +1902,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1393,7 +2012,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -1414,6 +2032,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1432,12 +2058,51 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1455,8 +2120,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/data-view-buffer": { "version": "1.0.2", @@ -1513,7 +2177,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -1566,6 +2229,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -1578,6 +2249,15 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1592,6 +2272,14 @@ "node": ">= 0.4" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -1799,7 +2487,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -2036,6 +2723,11 @@ "node": ">=16.0.0" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2071,6 +2763,25 @@ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -2080,6 +2791,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2098,7 +2822,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2317,7 +3040,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -2325,6 +3047,14 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2338,7 +3068,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2390,6 +3119,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, "node_modules/is-async-function": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.0.tgz", @@ -2455,7 +3189,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "dependencies": { "hasown": "^2.0.2" }, @@ -2763,12 +3496,28 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2818,6 +3567,11 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2859,6 +3613,25 @@ "node": ">= 0.4" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2874,8 +3647,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { "version": "3.3.8", @@ -2905,7 +3677,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3069,7 +3840,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -3077,6 +3847,23 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3098,14 +3885,20 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/possible-typed-array-names": { "version": "1.0.0", @@ -3157,13 +3950,17 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3199,8 +3996,22 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -3224,6 +4035,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -3265,7 +4081,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -3516,6 +4331,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3630,6 +4453,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3646,7 +4474,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3946,6 +4773,20 @@ "node": ">=0.10.0" } }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/pronunciationAppFront/package.json b/frontend/pronunciationAppFront/package.json index 19d0c456f..5f5aa5287 100644 --- a/frontend/pronunciationAppFront/package.json +++ b/frontend/pronunciationAppFront/package.json @@ -10,6 +10,12 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@fontsource/roboto": "^5.1.1", + "@mui/icons-material": "^6.4.0", + "@mui/material": "^6.4.0", + "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/frontend/pronunciationAppFront/src/App.css b/frontend/pronunciationAppFront/src/App.css index b9d355df2..d7882d451 100644 --- a/frontend/pronunciationAppFront/src/App.css +++ b/frontend/pronunciationAppFront/src/App.css @@ -5,38 +5,7 @@ text-align: center; } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} +ul { + list-style-type: none; +} \ No newline at end of file diff --git a/frontend/pronunciationAppFront/src/App.jsx b/frontend/pronunciationAppFront/src/App.jsx index f67355ae0..f418b0104 100644 --- a/frontend/pronunciationAppFront/src/App.jsx +++ b/frontend/pronunciationAppFront/src/App.jsx @@ -1,35 +1,24 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' import './App.css' +import Cards from "./Cards.jsx" +import Header from "./Hero.jsx" +import User from "./Users.jsx"; function App() { - const [count, setCount] = useState(0) + return ( <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.jsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

+

Welcome to PronunciationApp

+ + + +
+ + + + - ) + ); } export default App diff --git a/frontend/pronunciationAppFront/src/Cards.jsx b/frontend/pronunciationAppFront/src/Cards.jsx new file mode 100644 index 000000000..97f9cab85 --- /dev/null +++ b/frontend/pronunciationAppFront/src/Cards.jsx @@ -0,0 +1,150 @@ +import { useState, useEffect } from "react"; +import { + Box, + Card, + CardContent, + Typography, + Container, + Chip, + Stack, + Grid, +} from "@mui/material"; +import { fetchWords } from "./data-api"; + +export default function WordList() { + const [words, setWords] = useState([]); + const [difficultyFilter, setDifficultyFilter] = useState("all"); + + useEffect(() => { + const getWords = async () => { + try { + const data = await fetchWords(); + setWords(data); + } catch (error) { + console.error("Failed to fetch words:", error); + } + }; + + getWords(); + }, []); + + const getColor = (difficulty) => { + switch (difficulty) { + case "easy": + return "#4CAF50"; + case "medium": + return "#FFC107"; + case "hard": + return "#F44336"; + default: + return "#B0B8C1"; + } + }; + + const filteredWords = + difficultyFilter === "all" + ? words + : words.filter((word) => word.difficulty === difficultyFilter); + + return ( + + + + Word List + + + + {["all", "easy", "medium", "hard"].map((level) => ( + setDifficultyFilter(level)} + sx={{ + backgroundColor: + difficultyFilter === level ? getColor(level) : "rgba(255, 255, 255, 0.1)", + color: difficultyFilter === level ? "#fff" : "#B0B8C1", + cursor: "pointer", + fontWeight: "bold", + }} + /> + ))} + + + + {filteredWords.map((word) => ( + + + + + {word.word} + + + + Pronunciation: {word.pronunciation} + + {word.synonyms?.length > 0 && ( + + {word.synonyms.map((synonym, index) => ( + + ))} + + )} + + + + ))} + + + + ); +} \ No newline at end of file diff --git a/frontend/pronunciationAppFront/src/Hero.jsx b/frontend/pronunciationAppFront/src/Hero.jsx new file mode 100644 index 000000000..1d889fc1c --- /dev/null +++ b/frontend/pronunciationAppFront/src/Hero.jsx @@ -0,0 +1,33 @@ +export default function HeroSection(){ + + +return ( + <> +

Learn English once and for all

+
+

Practice Real-World Conversations

+ Practice Real-World Conversations +
    +
  • + Speak: + practice your pronunciation +
  • +
  • + Drill: + repeat until perfection! +
  • +
  • + Listen: + get used to real english sounds +
  • +
+
+ +); + + +} \ No newline at end of file diff --git a/frontend/pronunciationAppFront/src/Users.jsx b/frontend/pronunciationAppFront/src/Users.jsx new file mode 100644 index 000000000..74e27d117 --- /dev/null +++ b/frontend/pronunciationAppFront/src/Users.jsx @@ -0,0 +1,96 @@ +import { useState, useEffect } from "react"; + +import { fetchUsers } from "./data-api"; + +import { + Card, + CardHeader, + Avatar, + CardContent, + Typography, + Chip, + Box +} from '@mui/material'; + +export default function Users() { + const [users, setUsers] = useState([]); + + useEffect(() => { + const getUsers = async () => { + try { + const data = await fetchUsers(); + setUsers(data); + } catch (error) { + console.error("Failed to fetch users:", error); + } + }; + + getUsers(); + }, []); + + const user = users.find((user) => (user.id === "1")); + + const getInitials = (name) => { + return name.split(' ').map(part => part[0]).join('').toUpperCase(); + }; + + return ( + + {user ? ( + + + {getInitials(user.name)} + + } + title={ + <> + + {user.name} + + + } + subheader={ + <> + + {user.age} years old + + + + } + /> + + + + Email:{" "} + {user.email} + + + Member since:{" "} + {user.joinDate} + + + + + ) : ( + + Loading user data... + + )} + +); +} \ No newline at end of file diff --git a/frontend/pronunciationAppFront/src/assets/hero-image.png b/frontend/pronunciationAppFront/src/assets/hero-image.png new file mode 100644 index 000000000..142e2916b Binary files /dev/null and b/frontend/pronunciationAppFront/src/assets/hero-image.png differ diff --git a/frontend/pronunciationAppFront/src/assets/profile.webp b/frontend/pronunciationAppFront/src/assets/profile.webp new file mode 100644 index 000000000..cd88adb3b Binary files /dev/null and b/frontend/pronunciationAppFront/src/assets/profile.webp differ diff --git a/frontend/pronunciationAppFront/src/assets/react.svg b/frontend/pronunciationAppFront/src/assets/react.svg deleted file mode 100644 index 6c87de9bb..000000000 --- a/frontend/pronunciationAppFront/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/pronunciationAppFront/src/data-api.js b/frontend/pronunciationAppFront/src/data-api.js new file mode 100644 index 000000000..889230160 --- /dev/null +++ b/frontend/pronunciationAppFront/src/data-api.js @@ -0,0 +1,25 @@ +// api.js +import axios from "axios"; + +const BASE_URL = "https://8eeb858c-019e-4bd3-8287-f53ccc9400a7.mock.pstmn.io"; + + +export const fetchWords = async () => { + try { + const response = await axios.get(`${BASE_URL}/words`); + return response.data; + } catch (error) { + console.error("Error fetching words:", error); + throw error; + } +}; + +export const fetchUsers = async () => { + try { + const response = await axios.get(`${BASE_URL}/users`); + return response.data; + } catch (error) { + console.error("Error fetching users:", error); + throw error; + } +}; diff --git a/frontend/pronunciationAppFront/src/index.css b/frontend/pronunciationAppFront/src/index.css index 6119ad9a8..413a99385 100644 --- a/frontend/pronunciationAppFront/src/index.css +++ b/frontend/pronunciationAppFront/src/index.css @@ -5,7 +5,6 @@ color-scheme: light dark; color: rgba(255, 255, 255, 0.87); - background-color: #242424; font-synthesis: none; text-rendering: optimizeLegibility; @@ -13,56 +12,19 @@ -moz-osx-font-smoothing: grayscale; } -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + background: linear-gradient(to bottom, #4b6cb7, #182848); + color: #F0F4F8; /* Soft light gray text */ + line-height: 1.6; + letter-spacing: 0.5px; } + + h1 { font-size: 3.2em; line-height: 1.1; } -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} + diff --git a/frontend/pronunciationAppFront/src/main.jsx b/frontend/pronunciationAppFront/src/main.jsx index b9a1a6dea..4ec53d92f 100644 --- a/frontend/pronunciationAppFront/src/main.jsx +++ b/frontend/pronunciationAppFront/src/main.jsx @@ -2,6 +2,10 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.jsx' +import "@fontsource/roboto/300.css"; +import "@fontsource/roboto/400.css"; +import "@fontsource/roboto/500.css"; +import "@fontsource/roboto/700.css"; createRoot(document.getElementById('root')).render( diff --git a/frontend/resources/CreateReactviteProject.md b/frontend/resources/create-React-vite/createReactviteProject.md similarity index 100% rename from frontend/resources/CreateReactviteProject.md rename to frontend/resources/create-React-vite/createReactviteProject.md diff --git a/frontend/resources/pronunciationAppFront-v0.0-project-structure.png b/frontend/resources/create-React-vite/pronunciationAppFront-v0.0-project-structure.png similarity index 100% rename from frontend/resources/pronunciationAppFront-v0.0-project-structure.png rename to frontend/resources/create-React-vite/pronunciationAppFront-v0.0-project-structure.png diff --git a/frontend/resources/pronunciationAppFront-v0.0-run-dev.png b/frontend/resources/create-React-vite/pronunciationAppFront-v0.0-run-dev.png similarity index 100% rename from frontend/resources/pronunciationAppFront-v0.0-run-dev.png rename to frontend/resources/create-React-vite/pronunciationAppFront-v0.0-run-dev.png diff --git a/frontend/resources/create-React-vite/pronunciationAppFront-v0.0-web-1.png b/frontend/resources/create-React-vite/pronunciationAppFront-v0.0-web-1.png new file mode 100644 index 000000000..653a20fa5 Binary files /dev/null and b/frontend/resources/create-React-vite/pronunciationAppFront-v0.0-web-1.png differ diff --git a/frontend/resources/pronunciationAppFront-v0.0-web.png b/frontend/resources/create-React-vite/pronunciationAppFront-v0.0-web.png similarity index 100% rename from frontend/resources/pronunciationAppFront-v0.0-web.png rename to frontend/resources/create-React-vite/pronunciationAppFront-v0.0-web.png diff --git a/frontend/resources/data-JSON/words.json b/frontend/resources/data-JSON/words.json new file mode 100644 index 000000000..863968d84 --- /dev/null +++ b/frontend/resources/data-JSON/words.json @@ -0,0 +1,54 @@ +{ + "words": [ + { + "id": "8b9248a4e0b64bbccf82e7723a3734279bf9bbc4", + "word": "benevolent", + "pronunciation": "/bɪˈnɛvələnt/" + }, + { + "id": "3a7bd3e2a07e8c7e9b6e0d2c1f4a5b8d9c0e3f2", + "word": "serendipity", + "pronunciation": "/ˌserənˈdɪpɪti/" + }, + { + "id": "5c9d7f3e1a2b4d6e8g0h9i7j6k5l4m3n2o1p", + "word": "ephemeral", + "pronunciation": "/ɪˈfɛmərəl/" + }, + { + "id": "2f4e6d8c0b2a4d6e8f0a2c4e6g8i0k2m4o6q", + "word": "ubiquitous", + "pronunciation": "/juːˈbɪkwɪtəs/" + }, + { + "id": "7h9j1l3n5p7r9t1v3x5z7b9d1f3h5j7l9n", + "word": "mellifluous", + "pronunciation": "/məˈlɪfluəs/" + }, + { + "id": "1a3c5e7g9i1k3m5o7q9s1u3w5y7a9c1e3", + "word": "eloquent", + "pronunciation": "/ˈɛləkwənt/" + }, + { + "id": "4b6d8f0h2j4l6n8p0r2t4v6x8z0b2d4f6", + "word": "quintessential", + "pronunciation": "/ˌkwɪntɪˈsenʃəl/" + }, + { + "id": "9k1m3o5q7s9u1w3y5a7c9e1g3i5k7m9o", + "word": "ethereal", + "pronunciation": "/ɪˈθɪəriəl/" + }, + { + "id": "2p4r6t8v0x2z4b6d8f0h2j4l6n8p0r2t", + "word": "surreptitious", + "pronunciation": "/ˌsʌrəpˈtɪʃəs/" + }, + { + "id": "5u7w9y1a3c5e7g9i1k3m5o7q9s1u3w5", + "word": "labyrinthine", + "pronunciation": "/ˌlæbəˈrɪnθaɪn/" + } + ] +} diff --git a/frontend/resources/material-ui/CSS-Box-Model.png b/frontend/resources/material-ui/CSS-Box-Model.png new file mode 100644 index 000000000..d55e46211 Binary files /dev/null and b/frontend/resources/material-ui/CSS-Box-Model.png differ diff --git a/frontend/resources/material-ui/boxing-containers.md b/frontend/resources/material-ui/boxing-containers.md new file mode 100644 index 000000000..39ae6f92e --- /dev/null +++ b/frontend/resources/material-ui/boxing-containers.md @@ -0,0 +1,159 @@ +# CSS basics: box and containers + +> `CSS (Cascading Style Sheets)` is a fundamental technology for web design, and understanding the `box model and containers` is key for layout and styling. + +## CSS Box Model + +The CSS box model is the foundation of layout in CSS. It treats every HTML element as a box with four components[1][4]: + +1. `Content`: The actual text, images, or other media within the element. +2. `Padding`: Transparent space around the content. +3. `Border`: A line surrounding the padding and content. +4. `Margin`: Transparent space outside the border. + +These components work together to determine the total size and spacing of elements on a webpage. For example: + +```css +div { + width: 350px; + height: 150px; + padding: 25px; + border: 5px solid black; + margin: 10px; +} +``` + +In this case, the total width of the element would be 410px (350px content + 50px padding + 10px border) and the height would be 210px (150px content + 50px padding + 10px border)[4]. + +## CSS Containers + +Containers in CSS are elements used to group and structure content within a webpage[9]. They help control the placement and styling of elements, creating organized and responsive layouts. Common container elements include: + +- `
`: A generic container for flow content. +- `
`: A thematic grouping of content. +- `
`: A self-contained composition. + +Containers are often styled using CSS classes: + +```css +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} +``` + +This creates a centered container with a maximum width and some padding[9]. + +## Advanced Container Concepts + +1. Flexbox: A one-dimensional layout model for flexible container elements. +2. Grid: A two-dimensional layout system for more complex designs. +3. Container Queries: A newer feature allowing styles to be applied based on the container's size rather than the viewport[6]. + +Understanding these concepts allows developers to create responsive, well-structured layouts that adapt to various screen sizes and devices, enhancing the overall user experience of websites. + +Citations: +[1] https://www.w3schools.com/css/css_boxmodel.asp +[2] https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_box_model +[3] https://www.smashingmagazine.com/2021/05/complete-guide-css-container-queries/ +[4] https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Box_model +[5] https://www.youtube.com/watch?v=nSst4-WbEZk +[6] https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries +[7] https://www.programiz.com/css/box-model +[8] https://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug +[9] https://www.javatpoint.com/css-container +[10] https://www.simplilearn.com/tutorials/css-tutorial/css-box-model +[11] https://www.geeksforgeeks.org/css-box-model/ + +## Example + +```js +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { + Box, + Card, + CardContent, + Typography, + Container, + Grid +} from '@mui/material'; + + +export default function WordList() { + + // business logic to fetch data: words + + return ( + + + + Word List + + + {words.map((word) => ( + + + + + {word.word} + + + Pronunciation: {word.pronunciation} + + + + + ))} + + + + ); +} +``` + +This table provides a balanced view of the advantages and potential drawbacks of using these Material-UI components in React applications.: explaining the pros and cons of using `Container`, `Box`, and `Grid` components in React with Material-UI: + +| Pros | Cons | +| --------------------------------------- | ------------------------------------------------------ | +| Responsive design out of the box | Learning curve for new developers | +| Consistent spacing and layout | Potential over-reliance on MUI components | +| Improved code organization | Extra abstraction layer | +| Better performance optimization | Increased bundle size | +| Adherence to Material Design principles | Less flexibility for highly custom designs | +| Enhanced accessibility | Potential performance impact with nested grids | +| Scalable structure | Possible verbosity in component structure | +| Cross-browser compatibility | Dependency on external library (MUI) | +| Easy theming and customization | Version compatibility issues during updates | +| Reduced development time | Potential for unnecessary complexity in simple layouts | + +### Box vs Container Comparison + +| Aspect | Box | Container | +| --------------------- | ------------------------------------- | ------------------------------------------ | +| Definition | Individual element rendering model | Structural element for grouping content | +| Primary Purpose | Define element dimensions and spacing | Organize and layout multiple elements | +| CSS Properties | `margin`, `padding`, `border` | `max-width`, `display`, `flex`, `grid` | +| Scope | Single HTML element | Multiple elements or child components | +| Responsiveness | Element-specific sizing | Layout-wide adaptability | +| Nesting Capability | Limited | High (can contain multiple boxes/elements) | +| Layout Behavior | Static individual element | Dynamic content arrangement | +| Typical HTML Elements | Every HTML tag | `
`, `
`, `
` | +| Flexibility | Fixed to element properties | Adaptable to different layout strategies | +| Performance Impact | Minimal | Can affect overall page rendering | + +## Key Takeaways + +- Boxes are about individual element rendering +- Containers are about structural organization + +### Links + +- [Container Component](https://mui.com/material-ui/react-container/) + +- [Box Component](https://mui.com/material-ui/react-box/) + +- [Container Component](https://mui.com/material-ui/react-container/) + +- [Grid version 2](https://mui.com/material-ui/react-grid2/) diff --git a/frontend/resources/material-ui/material-ui-install.md b/frontend/resources/material-ui/material-ui-install.md new file mode 100644 index 000000000..4d588e785 --- /dev/null +++ b/frontend/resources/material-ui/material-ui-install.md @@ -0,0 +1,61 @@ +# Material React UI + +To install Material-UI (now known as MUI) for React using npm, follow these steps: + +1. Open your terminal and navigate to your React project directory. + +2. Run the following command to install MUI and its peer dependencies: + +```bash +npm install @mui/material @emotion/react @emotion/styled +``` + +3. If you want to use icons, install the icons package: + +```bash +npm install @mui/icons-material +``` + +4. For optimal font loading, install Roboto font: + +```bash +npm install @fontsource/roboto +``` + +After installation, you can import and use MUI components in your React application. Here's a basic example of how to use a MUI component: + +```jsx +import React from 'react'; +import Button from '@mui/material/Button'; + +function App() { + return ( + + ); +} + +export default App; +``` + +Remember to import the Roboto font in your main `index.js` or `App.js` file: + +```jsx +import '@fontsource/roboto/300.css'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; +import '@fontsource/roboto/700.css'; +``` + +This setup will allow you to use MUI components and styles in your React application[1][2][3]. + +Citations: +[1] https://v3.mui.com/getting-started/installation/ +[2] https://v5-0-6.mui.com/getting-started/installation/ +[3] https://mui.com/material-ui/getting-started/installation/ +[4] https://v4.mui.com/getting-started/installation/ +[5] https://www.educative.io/answers/how-to-install-material-ui-v5 +[6] https://www.npmjs.com/package/@mui/material +[7] https://v4.mui.com/es/getting-started/installation/ +[8] https://www.npmjs.com/package/@material-ui/core diff --git a/frontend/resources/pronunciationAppFront-v0.1-flow-data-1.png b/frontend/resources/pronunciationAppFront-v0.1-flow-data-1.png new file mode 100644 index 000000000..34e15a47d Binary files /dev/null and b/frontend/resources/pronunciationAppFront-v0.1-flow-data-1.png differ diff --git a/frontend/resources/pronunciationAppFront-v0.1-flow-data-2.png b/frontend/resources/pronunciationAppFront-v0.1-flow-data-2.png new file mode 100644 index 000000000..bbeee187a Binary files /dev/null and b/frontend/resources/pronunciationAppFront-v0.1-flow-data-2.png differ diff --git a/frontend/resources/pronunciationAppFront-v0.1-flow-data-3.png b/frontend/resources/pronunciationAppFront-v0.1-flow-data-3.png new file mode 100644 index 000000000..b39d8202e Binary files /dev/null and b/frontend/resources/pronunciationAppFront-v0.1-flow-data-3.png differ diff --git a/frontend/resources/pronunciationAppFront-v0.1-flow-data-4.png b/frontend/resources/pronunciationAppFront-v0.1-flow-data-4.png new file mode 100644 index 000000000..762e1c320 Binary files /dev/null and b/frontend/resources/pronunciationAppFront-v0.1-flow-data-4.png differ diff --git a/frontend/resources/pronunciationAppFront-v0.1-render-1-cards.png b/frontend/resources/pronunciationAppFront-v0.1-render-1-cards.png new file mode 100644 index 000000000..a9395cef5 Binary files /dev/null and b/frontend/resources/pronunciationAppFront-v0.1-render-1-cards.png differ diff --git a/frontend/resources/pronunciationAppFront-v0.1-render-2-cards.png b/frontend/resources/pronunciationAppFront-v0.1-render-2-cards.png new file mode 100644 index 000000000..4a0bf0ac0 Binary files /dev/null and b/frontend/resources/pronunciationAppFront-v0.1-render-2-cards.png differ diff --git a/frontend/resources/pronunciationAppFront-v0.1-render-3-cards.png b/frontend/resources/pronunciationAppFront-v0.1-render-3-cards.png new file mode 100644 index 000000000..15fe15340 Binary files /dev/null and b/frontend/resources/pronunciationAppFront-v0.1-render-3-cards.png differ diff --git a/frontend/resources/pronunciationAppFront-v0.1-render-4-data.png b/frontend/resources/pronunciationAppFront-v0.1-render-4-data.png new file mode 100644 index 000000000..729ae1ed4 Binary files /dev/null and b/frontend/resources/pronunciationAppFront-v0.1-render-4-data.png differ diff --git a/frontend/resources/pronunciatonApp-v0.1.md b/frontend/resources/pronunciatonApp-v0.1.md new file mode 100644 index 000000000..6743517d0 --- /dev/null +++ b/frontend/resources/pronunciatonApp-v0.1.md @@ -0,0 +1,284 @@ +# PronunciationApp v0.1 + +## Reference Lab + +- [Lab#RE06-1: healthyFood Restaurant – albertprofe wiki](https://albertprofe.dev/reactjs/rjslab6-1.html): with AWS workflow CD/CI deploy +- [Lab#RE01-1: API Rest Axios – albertprofe wiki](https://albertprofe.dev/reactjs/rjslab1.html): with Axios + +## Postman Mock Data Server + +> A **Postman mock server is a simulated API endpoint that mimics the behavior of a real server** without the need for a fully implemented backend. It allows developers to test and prototype APIs by returning predefined responses to requests. + +```js +const BASE_URL = 'https://a387bb02-2aa0-41a6-b8f7-9cc5247b9d5f.mock.pstmn.io'; +``` + +`Mock servers` are created from Postman collections, which contain saved examples of requests and responses. When a request is sent to the mock server, it matches the incoming request to the closest saved example and returns the corresponding response. + +- [Configure and use a Postman mock server | Postman Docs](https://learning.postman.com/docs/designing-and-developing-your-api/mocking-data/setting-up-mock/) + +These servers can be public or private, with private servers requiring an API key for access. They're useful for API development, testing, and simulating various scenarios without relying on a live backend. + +`Mock servers` can also simulate network delays and generate dynamic responses using variables and templates, enhancing the realism of API testing. They're particularly valuable in the early stages of API development, allowing frontend and backend teams to work concurrently without waiting for a fully functional API. + +### Fake data words + +```json +{ "words" : [ { "id": "8b9248a4e0b64bbccf82e7723a3734279bf9bbc4", "word": "benevolent", "pronunciation": "/bɪˈnɛvələnt/" }, { "id": "3a7bd3e2a07e8c7e9b6e0d2c1f4a5b8d9c0e3f2", "word": "serendipity", "pronunciation": "/ˌserənˈdɪpɪti/" }, { "id": "5c9d7f3e1a2b4d6e8g0h9i7j6k5l4m3n2o1p", "word": "ephemeral", "pronunciation": "/ɪˈfɛmərəl/" }, { "id": "2f4e6d8c0b2a4d6e8f0a2c4e6g8i0k2m4o6q", "word": "ubiquitous", "pronunciation": "/juːˈbɪkwɪtəs/" }, { "id": "7h9j1l3n5p7r9t1v3x5z7b9d1f3h5j7l9n", "word": "mellifluous", "pronunciation": "/məˈlɪfluəs/" }, { "id": "1a3c5e7g9i1k3m5o7q9s1u3w5y7a9c1e3", "word": "eloquent", "pronunciation": "/ˈɛləkwənt/" }, { "id": "4b6d8f0h2j4l6n8p0r2t4v6x8z0b2d4f6", "word": "quintessential", "pronunciation": "/ˌkwɪntɪˈsenʃəl/" }, { "id": "9k1m3o5q7s9u1w3y5a7c9e1g3i5k7m9o", "word": "ethereal", "pronunciation": "/ɪˈθɪəriəl/" }, { "id": "2p4r6t8v0x2z4b6d8f0h2j4l6n8p0r2t", "word": "surreptitious", "pronunciation": "/ˌsʌrəpˈtɪʃəs/" }, { "id": "5u7w9y1a3c5e7g9i1k3m5o7q9s1u3w5", "word": "labyrinthine", "pronunciation": "/ˌlæbəˈrɪnθaɪn/" } ]} +``` + +## Project + +### Tech stack + +Draft-sandbox coupled code using: + +- `Material UI`, + +- `Postam` Mock Data Server API endpoint, + +- `Axios` for data fetching, + +- `Hooks` for state management + +- and rendering the words + + - from the provided API endpoint using `map` js function + +This code combines the Material UI components with Axios for data fetching. It uses the `useState` and `useEffect` hooks to manage the state and side effects. The `fetchWords` function makes a GET request to the provided API endpoint to retrieve the word data. The component then renders the fetched words in a responsive grid of Material UI cards. + +Remember to install the necessary dependencies: + +```bash +npm install @mui/material @emotion/react @emotion/styled axios +``` + +This setup provides a clean, modern look with Material UI components and efficiently fetches data from the API using Axios. + +### Flow + +> This flow simulate API interactions without a real backend. + +The mock server returns predefined data, which Axios fetches in the useEffect hook. The fetched data is then stored in state using useState. Finally, the component maps over the state data, rendering each word as a card in the UI, creating a seamless development experience before the actual API is ready. + +The flow from a Postman mock server to rendering in React involves these steps: + +1. **Postman mock server**: Simulates an API endpoint, providing predefined responses to requests. + +2. **Axios**: Makes an HTTP GET request to the mock server URL. + +3. **useEffect**: Triggers the Axios request when the component mounts. + +4. **useState**: Stores the fetched data in the component's state. + +5. **words.map()**: Iterates over the array of words stored in state. + +6. **Render**: Displays each word as a Material UI Card component in the UI. + +### Code sandbox + +Coupled `fetchWords` and `WordList()` + +```js +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { + Box, + Card, + CardContent, + Typography, + Container, + Grid +} from '@mui/material'; + +const BASE_URL = 'https://a387bb02-2aa0-41a6-b8f7-9cc5247b9d5f.mock.pstmn.io'; + +const fetchWords = async () => { + try { + const response = await axios.get(`${BASE_URL}/words`); + return response.data.words; + } catch (error) { + console.error('Error fetching words:', error); + throw error; + } +}; + +export default function WordList() { + const [words, setWords] = useState([]); + + useEffect(() => { + const getWords = async () => { + try { + const data = await fetchWords(); + setWords(data); + } catch (error) { + console.error('Failed to fetch words:', error); + } + }; + + getWords(); + }, []); + + return ( + + + + Word List + + + {words.map((word) => ( + + + + + {word.word} + + + Pronunciation: {word.pronunciation} + + + + + ))} + + + + ); +} +``` + +### Code sandbox without MUI + +`./pronunciationAppFront/src/Cards.jsx` + +```jsx +import { useState, useEffect } from "react"; +import { fetchWords } from "./data-api"; + +export default function WordList() { + const [words, setWords] = useState([]); + + useEffect(() => { + const getWords = async () => { + try { + const data = await fetchWords(); + setWords(data); + } catch (error) { + console.error("Failed to fetch words:", error); + } + }; + + getWords(); + }, []); + + return ( + <> +

Words List

+ {words.map((word) => ( +

+ {word.id}: {word.word} {word.pronunciation} +

+ ))} + + ); +} +``` + +`./pronunciationAppFront/src/data-api.js` + +```js +// api.js +import axios from "axios"; + +const BASE_URL = "https://a387bb02-2aa0-41a6-b8f7-9cc5247b9d5f.mock.pstmn.io"; + +export const fetchWords = async () => { + try { + const response = await axios.get(`${BASE_URL}/words`); + return response.data.words; + } catch (error) { + console.error("Error fetching words:", error); + throw error; + } +}; +``` + +### Code typescript + +`./pronunciationAppFront/src/Cards.jsx` + +```typescript +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { + Box, + Card, + CardContent, + Typography, + Container, + Grid +} from '@mui/material'; + +const BASE_URL = 'https://a387bb02-2aa0-41a6-b8f7-9cc5247b9d5f.mock.pstmn.io'; + +interface Word { + id: string; + word: string; + pronunciation: string; +} + +const fetchWords = async (): Promise => { + try { + const response = +await axios.get<{ words: Word[] }>(`${BASE_URL}/words`); + return response.data.words; + } catch (error) { + console.error('Error fetching words:', error); + throw error; + } +}; + +export default function WordList(): JSX.Element { + const [words, setWords] = useState([]); + + useEffect(() => { + const getWords = async () => { + try { + const data = await fetchWords(); + setWords(data); + } catch (error) { + console.error('Failed to fetch words:', error); + } + }; + + getWords(); + }, []); + + return ( + + + + Word List + + + {words.map((word) => ( + + + + + {word.word} + + + Pronunciation: {word.pronunciation} + + + + + ))} + + + + ); +} + +```