From 42223915864316c0e8441e747686fbe95804ce74 Mon Sep 17 00:00:00 2001 From: Lu Xinming Date: Tue, 19 Mar 2024 19:48:50 +0800 Subject: [PATCH 1/3] added prettier --- .prettierignore | 3 +++ .prettierrc | 1 + package.json | 1 + yarn.lock | 5 +++++ 4 files changed, 10 insertions(+) create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1b07c39 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +# Ignore artifacts: +build +coverage \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/package.json b/package.json index 57b0849..f6061a5 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", "jsdom": "^24.0.0", + "prettier": "3.2.5", "tsc-alias": "^1.8.8", "typescript": "^5.4.2", "vite": "^5.0.8", diff --git a/yarn.lock b/yarn.lock index 93e88e7..6b8f157 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3037,6 +3037,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier@3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" + integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== + pretty-format@^27.0.2: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" From e34321488eda400249777b906cecfc756b7e4c6e Mon Sep 17 00:00:00 2001 From: Lu Xinming Date: Tue, 19 Mar 2024 19:48:58 +0800 Subject: [PATCH 2/3] ran prettier --- .eslintrc.cjs | 24 +-- .github/ISSUE_TEMPLATE/bug_report.md | 24 +-- .github/ISSUE_TEMPLATE/feature_request.md | 7 +- .github/pull_request_template.md | 7 +- .github/workflows/autoupdate.yml | 8 +- .github/workflows/test.yml | 4 +- README.md | 21 +- public/mockServiceWorker.js | 186 +++++++++--------- src/api/authentication.ts | 35 ++-- src/api/fetch.ts | 15 +- .../MobileLoginContainer.module.css | 69 ++++--- .../WebLoginContainer.module.css | 24 +-- src/assets/css/reset.css | 73 +++---- .../authentication/MobileLoginContainer.tsx | 10 +- .../authentication/WebLoginContainer.tsx | 10 +- src/components/constants.tsx | 5 +- src/main.tsx | 31 +-- src/mocks/authentication/auth_handlers.ts | 73 +++---- src/mocks/handlers.ts | 21 +- src/mocks/server.ts | 5 +- src/mocks/worker.ts | 10 +- src/routes/App.tsx | 11 +- src/routes/router.tsx | 11 +- src/routes/shared/TestPage.tsx | 16 +- tests/authentication/auth.test.tsx | 39 ++-- tests/test.test.tsx | 25 +-- vite.config.ts | 16 +- vitest.setup.ts | 12 +- 28 files changed, 406 insertions(+), 386 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f8a9fb4..2158fdf 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -2,31 +2,31 @@ module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react/recommended', - 'plugin:react-hooks/recommended', + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', + ignorePatterns: ["dist", ".eslintrc.cjs"], + parser: "@typescript-eslint/parser", parserOptions: { ecmaVersion: 2021, - sourceType: 'module', + sourceType: "module", ecmaFeatures: { jsx: true, }, }, - plugins: ['react-refresh', '@typescript-eslint', 'react', 'react-hooks'], + plugins: ["react-refresh", "@typescript-eslint", "react", "react-hooks"], rules: { "react/react-in-jsx-scope": "off", - 'react-refresh/only-export-components': [ - 'warn', + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], }, settings: { react: { - version: 'detect', + version: "detect", }, }, -} +}; diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea7..9b77ea7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7..2bc5d5f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index abf02fe..6b46775 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,12 +1,15 @@ # Pull Request ## Description + [Provide a brief description of the changes in this pull request.] ## Related Issues + [Reference any related issues that this pull request addresses or resolves.] ## Checklist + Please review and check the following before submitting your pull request: - [ ] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) document. @@ -17,6 +20,7 @@ Please review and check the following before submitting your pull request: - [ ] I have added comments to my code, particularly in areas that may be confusing. ## Screenshots (if applicable) + [Include any relevant screenshots or GIFs to visually demonstrate the changes.] ### Desktop @@ -24,8 +28,9 @@ Please review and check the following before submitting your pull request: ### Mobile ## Additional Information + [Include any additional information that might be helpful for reviewers or maintainers.] ## Reviewer Notes -[Optional: Include specific instructions or notes for reviewers, if necessary.] +[Optional: Include specific instructions or notes for reviewers, if necessary.] diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index 0e3a813..56a858e 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -2,9 +2,9 @@ name: autoupdate on: # This will trigger on all pushes to all branches. push: - # Alternatively, you can only trigger if commits are pushed to certain branches, e.g.: - branches: - - main + # Alternatively, you can only trigger if commits are pushed to certain branches, e.g.: + branches: + - main jobs: autoupdate: @@ -13,6 +13,6 @@ jobs: steps: - uses: docker://chinthakagodawita/autoupdate-action:v1 env: - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" PR_FILTER: "labelled" PR_LABELS: "epic" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index adf4948..57b0dc5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,10 +22,10 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 - cache: 'yarn' + cache: "yarn" - name: Install dependencies run: yarn - name: Run Vitest - run: yarn vitest \ No newline at end of file + run: yarn vitest diff --git a/README.md b/README.md index b5dcdae..ac71066 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,11 @@ This template provides a minimal setup to get React working in Vite with HMR and ## Setup Guide This project was set up with NodeJS v20.11.0 LTS with yarn. + 1. Install packages - ```shell - yarn - ``` + ```shell + yarn + ``` 2. Create a file `.env.development.local` in root folder with the following information ```text VITE_IGNORE_MSW=false @@ -16,9 +17,9 @@ This project was set up with NodeJS v20.11.0 LTS with yarn. If you would like to ignore mock service worker, set it to `true`. 3. Create another file `.env.test.local` in root folder with same content as above 4. Run the development server - ```shell - yarn dev - ``` + ```shell + yarn dev + ``` ## Dependencies @@ -47,12 +48,12 @@ If you are developing a production application, we recommend updating the config export default { // other rules... parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['./tsconfig.json', './tsconfig.node.json'], + ecmaVersion: "latest", + sourceType: "module", + project: ["./tsconfig.json", "./tsconfig.node.json"], tsconfigRootDir: __dirname, }, -} +}; ``` - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index a2745d1..1af3a7c 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -8,124 +8,124 @@ * - Please do NOT serve this file on production. */ -const INTEGRITY_CHECKSUM = '223d191a56023cd36aa88c802961b911' -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') -const activeClientIds = new Set() +const INTEGRITY_CHECKSUM = "223d191a56023cd36aa88c802961b911"; +const IS_MOCKED_RESPONSE = Symbol("isMockedResponse"); +const activeClientIds = new Set(); -self.addEventListener('install', function () { - self.skipWaiting() -}) +self.addEventListener("install", function () { + self.skipWaiting(); +}); -self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) +self.addEventListener("activate", function (event) { + event.waitUntil(self.clients.claim()); +}); -self.addEventListener('message', async function (event) { - const clientId = event.source.id +self.addEventListener("message", async function (event) { + const clientId = event.source.id; if (!clientId || !self.clients) { - return + return; } - const client = await self.clients.get(clientId) + const client = await self.clients.get(clientId); if (!client) { - return + return; } const allClients = await self.clients.matchAll({ - type: 'window', - }) + type: "window", + }); switch (event.data) { - case 'KEEPALIVE_REQUEST': { + case "KEEPALIVE_REQUEST": { sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }) - break + type: "KEEPALIVE_RESPONSE", + }); + break; } - case 'INTEGRITY_CHECK_REQUEST': { + case "INTEGRITY_CHECK_REQUEST": { sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', + type: "INTEGRITY_CHECK_RESPONSE", payload: INTEGRITY_CHECKSUM, - }) - break + }); + break; } - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) + case "MOCK_ACTIVATE": { + activeClientIds.add(clientId); sendToClient(client, { - type: 'MOCKING_ENABLED', + type: "MOCKING_ENABLED", payload: true, - }) - break + }); + break; } - case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId) - break + case "MOCK_DEACTIVATE": { + activeClientIds.delete(clientId); + break; } - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) + case "CLIENT_CLOSED": { + activeClientIds.delete(clientId); const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) + return client.id !== clientId; + }); // Unregister itself when there are no more clients if (remainingClients.length === 0) { - self.registration.unregister() + self.registration.unregister(); } - break + break; } } -}) +}); -self.addEventListener('fetch', function (event) { - const { request } = event +self.addEventListener("fetch", function (event) { + const { request } = event; // Bypass navigation requests. - if (request.mode === 'navigate') { - return + if (request.mode === "navigate") { + return; } // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. - if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { - return + if (request.cache === "only-if-cached" && request.mode !== "same-origin") { + return; } // Bypass all requests when there are no active clients. // Prevents the self-unregistered worked from handling requests // after it's been deleted (still remains active until the next reload). if (activeClientIds.size === 0) { - return + return; } // Generate unique request ID. - const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId)) -}) + const requestId = crypto.randomUUID(); + event.respondWith(handleRequest(event, requestId)); +}); async function handleRequest(event, requestId) { - const client = await resolveMainClient(event) - const response = await getResponse(event, client, requestId) + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); // Send back the response clone for the "response:*" life-cycle events. // Ensure MSW is active and ready to handle the message, otherwise // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { - ;(async function () { - const responseClone = response.clone() + (async function () { + const responseClone = response.clone(); sendToClient( client, { - type: 'RESPONSE', + type: "RESPONSE", payload: { requestId, isMockedResponse: IS_MOCKED_RESPONSE in response, @@ -137,11 +137,11 @@ async function handleRequest(event, requestId) { }, }, [responseClone.body], - ) - })() + ); + })(); } - return response + return response; } // Resolve the main client for the given event. @@ -149,49 +149,49 @@ async function handleRequest(event, requestId) { // that registered the worker. It's with the latter the worker should // communicate with during the response resolving phase. async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) + const client = await self.clients.get(event.clientId); - if (client?.frameType === 'top-level') { - return client + if (client?.frameType === "top-level") { + return client; } const allClients = await self.clients.matchAll({ - type: 'window', - }) + type: "window", + }); return allClients .filter((client) => { // Get only those clients that are currently visible. - return client.visibilityState === 'visible' + return client.visibilityState === "visible"; }) .find((client) => { // Find the client ID that's recorded in the // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) + return activeClientIds.has(client.id); + }); } async function getResponse(event, client, requestId) { - const { request } = event + const { request } = event; // Clone the request because it might've been already used // (i.e. its body has been read and sent to the client). - const requestClone = request.clone() + const requestClone = request.clone(); function passthrough() { - const headers = Object.fromEntries(requestClone.headers.entries()) + const headers = Object.fromEntries(requestClone.headers.entries()); // Remove internal MSW request header so the passthrough request // complies with any potential CORS preflight checks on the server. // Some servers forbid unknown request headers. - delete headers['x-msw-intention'] + delete headers["x-msw-intention"]; - return fetch(requestClone, { headers }) + return fetch(requestClone, { headers }); } // Bypass mocking when the client is not active. if (!client) { - return passthrough() + return passthrough(); } // Bypass initial page load requests (i.e. static assets). @@ -199,22 +199,22 @@ async function getResponse(event, client, requestId) { // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet // and is not ready to handle requests. if (!activeClientIds.has(client.id)) { - return passthrough() + return passthrough(); } // Bypass requests with the explicit bypass header. // Such requests can be issued by "ctx.fetch()". - const mswIntention = request.headers.get('x-msw-intention') - if (['bypass', 'passthrough'].includes(mswIntention)) { - return passthrough() + const mswIntention = request.headers.get("x-msw-intention"); + if (["bypass", "passthrough"].includes(mswIntention)) { + return passthrough(); } // Notify the client that a request has been intercepted. - const requestBuffer = await request.arrayBuffer() + const requestBuffer = await request.arrayBuffer(); const clientMessage = await sendToClient( client, { - type: 'REQUEST', + type: "REQUEST", payload: { id: requestId, url: request.url, @@ -233,38 +233,38 @@ async function getResponse(event, client, requestId) { }, }, [requestBuffer], - ) + ); switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) + case "MOCK_RESPONSE": { + return respondWithMock(clientMessage.data); } - case 'MOCK_NOT_FOUND': { - return passthrough() + case "MOCK_NOT_FOUND": { + return passthrough(); } } - return passthrough() + return passthrough(); } function sendToClient(client, message, transferrables = []) { return new Promise((resolve, reject) => { - const channel = new MessageChannel() + const channel = new MessageChannel(); channel.port1.onmessage = (event) => { if (event.data && event.data.error) { - return reject(event.data.error) + return reject(event.data.error); } - resolve(event.data) - } + resolve(event.data); + }; client.postMessage( message, [channel.port2].concat(transferrables.filter(Boolean)), - ) - }) + ); + }); } async function respondWithMock(response) { @@ -273,15 +273,15 @@ async function respondWithMock(response) { // instance will have status code set to 0. Since it's not possible to create // a Response instance with status code 0, handle that use-case separately. if (response.status === 0) { - return Response.error() + return Response.error(); } - const mockedResponse = new Response(response.body, response) + const mockedResponse = new Response(response.body, response); Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { value: true, enumerable: true, - }) + }); - return mockedResponse + return mockedResponse; } diff --git a/src/api/authentication.ts b/src/api/authentication.ts index 587c747..17160a4 100644 --- a/src/api/authentication.ts +++ b/src/api/authentication.ts @@ -1,37 +1,38 @@ import resolveURL from "./fetch"; import Cookies from "js-cookie"; -const LOGIN_URL = resolveURL('/auth/login'); -const LOGOUT_URL = resolveURL('/auth/logout'); -const GET_CSRF_URL = resolveURL('/auth/csrf'); +const LOGIN_URL = resolveURL("/auth/login"); +const LOGOUT_URL = resolveURL("/auth/logout"); +const GET_CSRF_URL = resolveURL("/auth/csrf"); export const signIn = async (username: string, password: string) => { const csrfmiddlewaretoken = Cookies.get("csrftoken"); - if (csrfmiddlewaretoken === undefined) throw new Error("No CSRF Token Present") + if (csrfmiddlewaretoken === undefined) + throw new Error("No CSRF Token Present"); return await fetch(LOGIN_URL, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json", }, - credentials: 'include', - body: JSON.stringify({username, password, csrfmiddlewaretoken}), + credentials: "include", + body: JSON.stringify({ username, password, csrfmiddlewaretoken }), }); }; - export const signOut = async (): Promise => { const csrfmiddlewaretoken = Cookies.get("csrftoken"); - if (csrfmiddlewaretoken === undefined) throw new Error("No CSRF Token Present") + if (csrfmiddlewaretoken === undefined) + throw new Error("No CSRF Token Present"); const response = await fetch(LOGOUT_URL, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json", }, - credentials: 'include', - body: JSON.stringify({ csrfmiddlewaretoken }) + credentials: "include", + body: JSON.stringify({ csrfmiddlewaretoken }), }); - Cookies.remove("csrftoken") + Cookies.remove("csrftoken"); return response; }; @@ -41,7 +42,7 @@ export const getCSRF = async (): Promise => { method: "POST", credentials: "include", headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", }, }); -}; \ No newline at end of file +}; diff --git a/src/api/fetch.ts b/src/api/fetch.ts index 250dd63..191ee9f 100644 --- a/src/api/fetch.ts +++ b/src/api/fetch.ts @@ -1,13 +1,14 @@ - const setApiURL = () => { if (import.meta.env.DEV) { - return import.meta.env.VITE_IGNORE_MSW.toLowerCase() === "true" ? "http://localhost:8000" : "http://localhost:5173" + return import.meta.env.VITE_IGNORE_MSW.toLowerCase() === "true" + ? "http://localhost:8000" + : "http://localhost:5173"; } - return import.meta.env.VITE_API_URL -} + return import.meta.env.VITE_API_URL; +}; -export const apiURL = setApiURL() +export const apiURL = setApiURL(); export default function resolveURL(path: string): string { - return `${apiURL}${path}` -} \ No newline at end of file + return `${apiURL}${path}`; +} diff --git a/src/assets/css/authentication/MobileLoginContainer.module.css b/src/assets/css/authentication/MobileLoginContainer.module.css index 5c8c3c9..e1358f9 100644 --- a/src/assets/css/authentication/MobileLoginContainer.module.css +++ b/src/assets/css/authentication/MobileLoginContainer.module.css @@ -1,50 +1,47 @@ /* MobileLoginContainer.module.css */ .container { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; /* Example to fill the screen */ - padding: 20px; /* Padding for smaller screens */ - background: linear-gradient(180deg, #0C1747 0%, rgba(37, 60, 92, 0.62) 100%); - border: 1px black solid; - } - - + display: flex; + justify-content: center; + align-items: center; + height: 100vh; /* Example to fill the screen */ + padding: 20px; /* Padding for smaller screens */ + background: linear-gradient(180deg, #0c1747 0%, rgba(37, 60, 92, 0.62) 100%); + border: 1px black solid; +} /* For small mobile phones */ @media (max-width: 480px) { - /* styles for screens smaller than 600px */ - .loginForm { - width: 100%; /* Full width on mobile */ - min-width: 320px; /* But not too small */ - padding: 20px; - background-color: white; - border-radius: 4px; - /* Add more styling as needed */ - } + /* styles for screens smaller than 600px */ + .loginForm { + width: 100%; /* Full width on mobile */ + min-width: 320px; /* But not too small */ + padding: 20px; + background-color: white; + border-radius: 4px; + /* Add more styling as needed */ + } } /* For large mobile phones */ @media (min-width: 481px) and (max-width: 768px) { - /* styles for screens smaller than 600px */ - .loginForm { - width: 100%; /* Full width on mobile */ - padding: 20px; - background-color: white; - border-radius: 4px; - /* Add more styling as needed */ - } + /* styles for screens smaller than 600px */ + .loginForm { + width: 100%; /* Full width on mobile */ + padding: 20px; + background-color: white; + border-radius: 4px; + /* Add more styling as needed */ + } } /* For tablets */ @media (min-width: 768px) and (max-width: 1024px) { - /* styles for screens between 601px and 1024px */ - .loginForm { - width: 100%; /* Full width on mobile */ - padding: 20px; - background-color: white; - border-radius: 4px; - /* Add more styling as needed */ - } + /* styles for screens between 601px and 1024px */ + .loginForm { + width: 100%; /* Full width on mobile */ + padding: 20px; + background-color: white; + border-radius: 4px; + /* Add more styling as needed */ + } } - \ No newline at end of file diff --git a/src/assets/css/authentication/WebLoginContainer.module.css b/src/assets/css/authentication/WebLoginContainer.module.css index 46104b8..b0d0059 100644 --- a/src/assets/css/authentication/WebLoginContainer.module.css +++ b/src/assets/css/authentication/WebLoginContainer.module.css @@ -4,19 +4,19 @@ justify-content: center; align-items: center; height: 100vh; /* Example to fill the screen */ - background: linear-gradient(180deg, #0C1747 0%, rgba(37, 60, 92, 0.62) 100%); + background: linear-gradient(180deg, #0c1747 0%, rgba(37, 60, 92, 0.62) 100%); border: 1px black solid; - } - - /* For desktops */ +} + +/* For desktops */ @media (min-width: 1025px) { /* styles for screens larger than 1024px */ } - .loginForm { - width: 100%; /* Full width on mobile */ - max-width: 1280px; /* But not too wide */ - padding: 20px; - background-color: white; - border-radius: 4px; - /* Add more styling as needed */ - } \ No newline at end of file +.loginForm { + width: 100%; /* Full width on mobile */ + max-width: 1280px; /* But not too wide */ + padding: 20px; + background-color: white; + border-radius: 4px; + /* Add more styling as needed */ +} diff --git a/src/assets/css/reset.css b/src/assets/css/reset.css index e304ecb..1843f13 100644 --- a/src/assets/css/reset.css +++ b/src/assets/css/reset.css @@ -8,102 +8,109 @@ - The "symbol *" part is to solve Firefox SVG sprite bug - The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36) */ -*:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) { - all: unset; - display: revert; +*:where( + :not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *) + ) { + all: unset; + display: revert; } /* Preferred box-sizing value */ *, *::before, *::after { - box-sizing: border-box; + box-sizing: border-box; } /* Fix mobile Safari increase font-size on landscape mode */ html { - -moz-text-size-adjust: none; - -webkit-text-size-adjust: none; - text-size-adjust: none; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + text-size-adjust: none; } /* Reapply the pointer cursor for anchor tags */ -a, button { - cursor: revert; +a, +button { + cursor: revert; } /* Remove list styles (bullets/numbers) */ -ol, ul, menu, summary { - list-style: none; +ol, +ul, +menu, +summary { + list-style: none; } /* For images to not be able to exceed their container */ img { - max-inline-size: 100%; - max-block-size: 100%; + max-inline-size: 100%; + max-block-size: 100%; } /* removes spacing between cells in tables */ table { - border-collapse: collapse; + border-collapse: collapse; } /* Safari - solving issue when using user-select:none on the text input doesn't working */ -input, textarea { - -webkit-user-select: auto; +input, +textarea { + -webkit-user-select: auto; } /* revert the 'white-space' property for textarea elements on Safari */ textarea { - white-space: revert; + white-space: revert; } /* minimum style to allow to style meter element */ meter { - -webkit-appearance: revert; - appearance: revert; + -webkit-appearance: revert; + appearance: revert; } /* preformatted text - use only for this feature */ :where(pre) { - all: revert; - box-sizing: border-box; + all: revert; + box-sizing: border-box; } /* reset default text opacity of input placeholder */ ::placeholder { - color: unset; + color: unset; } /* fix the feature of 'hidden' attribute. display:revert; revert to element instead of attribute */ :where([hidden]) { - display: none; + display: none; } /* revert for bug in Chromium browsers - fix for the content editable attribute will work properly. - webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/ :where([contenteditable]:not([contenteditable="false"])) { - -moz-user-modify: read-write; - -webkit-user-modify: read-write; - overflow-wrap: break-word; - -webkit-line-break: after-white-space; - -webkit-user-select: auto; + -moz-user-modify: read-write; + -webkit-user-modify: read-write; + overflow-wrap: break-word; + -webkit-line-break: after-white-space; + -webkit-user-select: auto; } /* apply back the draggable feature - exist only in Chromium and Safari */ :where([draggable="true"]) { - -webkit-user-drag: element; + -webkit-user-drag: element; } /* Revert Modal native behavior */ :where(dialog:modal) { - all: revert; - box-sizing: border-box; + all: revert; + box-sizing: border-box; } /* Remove details summary webkit styles */ ::-webkit-details-marker { - display: none; -} \ No newline at end of file + display: none; +} diff --git a/src/components/authentication/MobileLoginContainer.tsx b/src/components/authentication/MobileLoginContainer.tsx index 46b43d9..2021696 100644 --- a/src/components/authentication/MobileLoginContainer.tsx +++ b/src/components/authentication/MobileLoginContainer.tsx @@ -1,13 +1,11 @@ -import styles from '../../assets/css/authentication/MobileLoginContainer.module.css'; // Import mobile-specific CSS +import styles from "../../assets/css/authentication/MobileLoginContainer.module.css"; // Import mobile-specific CSS const MobileLoginContainer = () => { return (
-
- {/* Your login form goes here */} -
+
{/* Your login form goes here */}
); -} +}; -export default MobileLoginContainer; \ No newline at end of file +export default MobileLoginContainer; diff --git a/src/components/authentication/WebLoginContainer.tsx b/src/components/authentication/WebLoginContainer.tsx index 8bbc0a9..d4974e1 100644 --- a/src/components/authentication/WebLoginContainer.tsx +++ b/src/components/authentication/WebLoginContainer.tsx @@ -1,13 +1,11 @@ -import styles from '../../assets/css/authentication/WebLoginContainer.module.css'; // Import web-specific CSS +import styles from "../../assets/css/authentication/WebLoginContainer.module.css"; // Import web-specific CSS const WebLoginContainer = () => { return (
-
- {/* Your login form goes here */} -
+
{/* Your login form goes here */}
); -} +}; -export default WebLoginContainer; \ No newline at end of file +export default WebLoginContainer; diff --git a/src/components/constants.tsx b/src/components/constants.tsx index 83fc9ed..0ba2208 100644 --- a/src/components/constants.tsx +++ b/src/components/constants.tsx @@ -1,8 +1,9 @@ +// Breakpoints based on Bootstrap 5 export const BREAKPOINTS = { XS: 0, SM: 576, MD: 768, LG: 992, XL: 1200, - XXL: 1400 -} + XXL: 1400, +}; diff --git a/src/main.tsx b/src/main.tsx index a6fc1c1..aa82700 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,31 +1,34 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import React from "react"; +import ReactDOM from "react-dom/client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { RouterProvider } from "react-router-dom"; import router from "routes/router.tsx"; -import 'css/reset.css' +import "css/reset.css"; -const queryClient = new QueryClient() +const queryClient = new QueryClient(); async function enableMocking() { - if (!import.meta.env.DEV || import.meta.env.VITE_IGNORE_MSW.toLowerCase() === "true") { - return + if ( + !import.meta.env.DEV || + import.meta.env.VITE_IGNORE_MSW.toLowerCase() === "true" + ) { + return; } - const { worker } = await import('./mocks/worker') + const { worker } = await import("@/mocks/worker"); - return worker.start({ onUnhandledRequest: "bypass" }) + return worker.start({ onUnhandledRequest: "bypass" }); } enableMocking().then(() => { - ReactDOM.createRoot(document.getElementById('root')!).render( + ReactDOM.createRoot(document.getElementById("root")!).render( - - + + , - ) -}) + ); +}); diff --git a/src/mocks/authentication/auth_handlers.ts b/src/mocks/authentication/auth_handlers.ts index 1aaf295..7b25d6e 100644 --- a/src/mocks/authentication/auth_handlers.ts +++ b/src/mocks/authentication/auth_handlers.ts @@ -1,46 +1,53 @@ //src/mocks/authentication/auth_handlers.ts -import { http, HttpResponse } from "msw" +import { http, HttpResponse } from "msw"; import resolveURL from "../../api/fetch"; -export const mockCSRFToken: string = "SUPERSECRETCSRFTOKEN" -export const mockUsername: string = "testuser" -export const mockPassword: string = "mockpassword" - +export const mockCSRFToken: string = "SUPERSECRETCSRFTOKEN"; +export const mockUsername: string = "testuser"; +export const mockPassword: string = "mockpassword"; type responseData = { - username: string | undefined - password: string | undefined - csrfmiddlewaretoken: string | undefined -} - + username: string | undefined; + password: string | undefined; + csrfmiddlewaretoken: string | undefined; +}; export const auth_handlers = [ http.post(resolveURL("/auth/csrf"), () => { return new HttpResponse(null, { headers: { - 'Set-Cookie': `csrftoken=${mockCSRFToken}; expires=Tue, 31 Dec 2024 23:59:59 GMT; Max-Age=31449600; Path=/; SameSite=Lax` - } - }) + "Set-Cookie": `csrftoken=${mockCSRFToken}; expires=Tue, 31 Dec 2024 23:59:59 GMT; Max-Age=31449600; Path=/; SameSite=Lax`, + }, + }); }), - http.post(resolveURL("/auth/login"), async ({ request }) => { - const data = await request.json() - if (data.csrfmiddlewaretoken !== mockCSRFToken) return new HttpResponse(null, {status: 403}) - - if (data.username !== mockUsername || data.password !== mockPassword) return new HttpResponse(null, {status: 403}) - - return new HttpResponse(null, { - headers: { - 'Set-Cookie': `csrftoken=${mockCSRFToken}; expires=Tue, 31 Dec 2024 23:59:59 GMT; Max-Age=31449600; Path=/; SameSite=Lax` - } - }) - }), - - http.post(resolveURL("/auth/logout"), async ({ request }) => { - const data = await request.json() - if (data.csrfmiddlewaretoken !== mockCSRFToken) return new HttpResponse(null, {status: 403}) - - return new HttpResponse(null, { status: 200 }) - }) -] \ No newline at end of file + http.post( + resolveURL("/auth/login"), + async ({ request }) => { + const data = await request.json(); + if (data.csrfmiddlewaretoken !== mockCSRFToken) + return new HttpResponse(null, { status: 403 }); + + if (data.username !== mockUsername || data.password !== mockPassword) + return new HttpResponse(null, { status: 403 }); + + return new HttpResponse(null, { + headers: { + "Set-Cookie": `csrftoken=${mockCSRFToken}; expires=Tue, 31 Dec 2024 23:59:59 GMT; Max-Age=31449600; Path=/; SameSite=Lax`, + }, + }); + }, + ), + + http.post( + resolveURL("/auth/logout"), + async ({ request }) => { + const data = await request.json(); + if (data.csrfmiddlewaretoken !== mockCSRFToken) + return new HttpResponse(null, { status: 403 }); + + return new HttpResponse(null, { status: 200 }); + }, + ), +]; diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 342fd44..eb90603 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,21 +1,16 @@ //src/mocks/handlers.ts -import { http, HttpResponse } from "msw" +import { http, HttpResponse } from "msw"; import resolveURL from "../api/fetch.ts"; import { auth_handlers } from "./authentication/auth_handlers.ts"; - const default_handlers = [ - http.get(resolveURL('/resource'), () => { - return HttpResponse.json({ - result: "Hello World!" - }) - }) -] - + http.get(resolveURL("/resource"), () => { + return HttpResponse.json({ + result: "Hello World!", + }); + }), +]; -export const handlers = [ - ...default_handlers, - ...auth_handlers -] +export const handlers = [...default_handlers, ...auth_handlers]; diff --git a/src/mocks/server.ts b/src/mocks/server.ts index 756034d..7b37f2a 100644 --- a/src/mocks/server.ts +++ b/src/mocks/server.ts @@ -1,5 +1,4 @@ -import { setupServer } from 'msw/node' +import { setupServer } from "msw/node"; import { handlers } from "./handlers"; - -export const server = setupServer(...handlers) \ No newline at end of file +export const server = setupServer(...handlers); diff --git a/src/mocks/worker.ts b/src/mocks/worker.ts index 508bd3f..5d1df39 100644 --- a/src/mocks/worker.ts +++ b/src/mocks/worker.ts @@ -1,8 +1,8 @@ -import { setupWorker, SetupWorker } from "msw/browser" -import { handlers } from "./handlers.ts" +import { setupWorker, SetupWorker } from "msw/browser"; +import { handlers } from "./handlers.ts"; -export const worker: SetupWorker = setupWorker(...handlers) +export const worker: SetupWorker = setupWorker(...handlers); worker.events.on("request:start", ({ request }) => { - console.log('MSW intercepted: ', request.method, request.url) -}) \ No newline at end of file + console.log("MSW intercepted: ", request.method, request.url); +}); diff --git a/src/routes/App.tsx b/src/routes/App.tsx index 4f61d4c..e2e2003 100644 --- a/src/routes/App.tsx +++ b/src/routes/App.tsx @@ -4,10 +4,13 @@ import MobileLoginContainer from "components/authentication/MobileLoginContainer import WebLoginContainer from "components/authentication/WebLoginContainer.tsx"; import { BREAKPOINTS } from "components/constants.tsx"; - const App = () => { - const size = useWindowSize(); - return size.width && size.width <= BREAKPOINTS.MD ? : ; + const size = useWindowSize(); + return size.width && size.width <= BREAKPOINTS.MD ? ( + + ) : ( + + ); }; -export default App \ No newline at end of file +export default App; diff --git a/src/routes/router.tsx b/src/routes/router.tsx index cbecbc3..2af295e 100644 --- a/src/routes/router.tsx +++ b/src/routes/router.tsx @@ -1,17 +1,16 @@ import { createBrowserRouter } from "react-router-dom"; import TestPage from "./shared/TestPage.tsx"; -import App from "./App.tsx" - +import App from "./App.tsx"; const router = createBrowserRouter([ { path: "/", - Component: App + Component: App, }, { path: "/test", Component: TestPage, - } -]) + }, +]); -export default router \ No newline at end of file +export default router; diff --git a/src/routes/shared/TestPage.tsx b/src/routes/shared/TestPage.tsx index 37fdf92..b153aa4 100644 --- a/src/routes/shared/TestPage.tsx +++ b/src/routes/shared/TestPage.tsx @@ -5,17 +5,15 @@ import viteLogo from "/vite.svg"; import reactLogo from "/react.svg"; import resolveURL from "@/api/fetch.ts"; - const getResourceOptions = { - queryKey: ['resourceData'], - queryFn: () => fetch(resolveURL('/resource')).then((res) => res.json()) -} - + queryKey: ["resourceData"], + queryFn: () => fetch(resolveURL("/resource")).then((res) => res.json()), +}; function TestPage() { - const [count, setCount] = useState(0) + const [count, setCount] = useState(0); - const { isPending, data } = useQuery(getResourceOptions) + const { isPending, data } = useQuery(getResourceOptions); return ( <> @@ -37,7 +35,7 @@ function TestPage() { Click on the Vite and React logos to learn more

- ) + ); } -export default TestPage \ No newline at end of file +export default TestPage; diff --git a/tests/authentication/auth.test.tsx b/tests/authentication/auth.test.tsx index 387656e..2f879c8 100644 --- a/tests/authentication/auth.test.tsx +++ b/tests/authentication/auth.test.tsx @@ -1,27 +1,30 @@ -import { describe, expect, test } from "vitest" +import { describe, expect, test } from "vitest"; import Cookies from "js-cookie"; -import {getCSRF, signIn, signOut} from "@/api/authentication.ts"; -import { mockCSRFToken, mockUsername, mockPassword } from "@/mocks/authentication/auth_handlers"; +import { getCSRF, signIn, signOut } from "@/api/authentication.ts"; +import { + mockCSRFToken, + mockUsername, + mockPassword, +} from "@/mocks/authentication/auth_handlers"; describe("authentication helper functions", () => { - test("should get csrf token", async () => { - const response = await getCSRF() - expect(response.status).toBe(200) - expect(response.headers.has('Set-Cookie')) - expect(response.headers.get('Set-Cookie')).contains(mockCSRFToken) - expect(Cookies.get("csrftoken")).toBe(mockCSRFToken) - }) + const response = await getCSRF(); + expect(response.status).toBe(200); + expect(response.headers.has("Set-Cookie")); + expect(response.headers.get("Set-Cookie")).contains(mockCSRFToken); + expect(Cookies.get("csrftoken")).toBe(mockCSRFToken); + }); test("should be able to signIn", async () => { - const response = await signIn(mockUsername, mockPassword) - expect(response.status).toBe(200) - }) + const response = await signIn(mockUsername, mockPassword); + expect(response.status).toBe(200); + }); test("should be able to signOut and csrftoken cookie cleared", async () => { - const response = await signOut() - expect(response.status).toBe(200) - expect(Cookies.get("csrftoken")).toBeUndefined() - }) -}) \ No newline at end of file + const response = await signOut(); + expect(response.status).toBe(200); + expect(Cookies.get("csrftoken")).toBeUndefined(); + }); +}); diff --git a/tests/test.test.tsx b/tests/test.test.tsx index cc901f0..d8dea35 100644 --- a/tests/test.test.tsx +++ b/tests/test.test.tsx @@ -1,17 +1,20 @@ -import {beforeEach, describe, expect, test } from "vitest" -import {render, screen} from "@testing-library/react" -import { QueryClient, QueryClientProvider } from "@tanstack/react-query" -import TestPage from "routes/shared/TestPage.tsx" +import { beforeEach, describe, expect, test } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import TestPage from "routes/shared/TestPage.tsx"; -const queryClient = new QueryClient() +const queryClient = new QueryClient(); describe("Test Page", () => { - beforeEach(() => { - render() - }) + render( + + + , + ); + }); test("Should show Hello World!", async () => { - expect(await screen.findByText(/Hello World!/)).toBeDefined() - }) -}) \ No newline at end of file + expect(await screen.findByText(/Hello World!/)).toBeDefined(); + }); +}); diff --git a/vite.config.ts b/vite.config.ts index 5f093a1..4d360df 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,8 @@ /// /// -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ export default defineConfig({ @@ -10,16 +10,16 @@ export default defineConfig({ test: { globals: true, environment: "jsdom", - setupFiles: ["./vitest.setup.ts"] + setupFiles: ["./vitest.setup.ts"], }, resolve: { alias: [ + // path aliases to simplify imports { find: "@", replacement: "/src" }, { find: "assets", replacement: "/src/assets" }, { find: "css", replacement: "/src/assets/css" }, - { find: "img", replacement: "/src/assets/img" }, { find: "components", replacement: "/src/components" }, - { find: "routes", replacement: "/src/routes" } - ] - } -}) + { find: "routes", replacement: "/src/routes" }, + ], + }, +}); diff --git a/vitest.setup.ts b/vitest.setup.ts index 68e58dc..a25a1a7 100644 --- a/vitest.setup.ts +++ b/vitest.setup.ts @@ -1,7 +1,7 @@ -import { afterAll, afterEach, beforeAll } from 'vitest' -import { server } from "./src/mocks/server" -import "@testing-library/jest-dom" +import { afterAll, afterEach, beforeAll } from "vitest"; +import { server } from "./src/mocks/server"; +import "@testing-library/jest-dom"; -beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) // test would throw error if calls to undefined endpoints are made -afterAll(() => server.close()) -afterEach(() => server.resetHandlers()) +beforeAll(() => server.listen({ onUnhandledRequest: "error" })); // test would throw error if calls to undefined endpoints are made +afterAll(() => server.close()); +afterEach(() => server.resetHandlers()); From 725d90b995e95b9b8f7ad8eab3659eaf0a2f6968 Mon Sep 17 00:00:00 2001 From: markgcera <> Date: Tue, 19 Mar 2024 21:33:26 +0800 Subject: [PATCH 3/3] add trial code for layout container --- src/assets/css/layout.tsx | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/assets/css/layout.tsx diff --git a/src/assets/css/layout.tsx b/src/assets/css/layout.tsx new file mode 100644 index 0000000..9aea721 --- /dev/null +++ b/src/assets/css/layout.tsx @@ -0,0 +1,38 @@ +// import React from 'react'; +// import Navbar from './Navbar'; // Import the Navbar component. + +// interface LayoutProps { +// children: React.ReactNode; +// } + +// const Layout: React.FC = ({ children }) => { +// return ( +// <> +// {/* The Navbar will always be rendered at the top */} +//
{children}
{/* This is where the page-specific content will go */} +// +// ); +// }; + +// export default Layout; + + + +// An example usage of this layout.tsx container is shown below as HomePage.tsx + +// HomePage.tsx +// import React from 'react'; +// import Layout from './Layout'; // Import the Layout component + +// const HomePage = () => { +// return ( +// +// {/* Everything here will be passed as children to the Layout */} +//

Welcome to the Home Page

+//

This is some introductory text on the home page.

+// {/* More content can follow */} +//
+// ); +// }; + +// export default HomePage; \ No newline at end of file