diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 46490d1..8d121b8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,20 +25,20 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - # e2e-tests: - # needs: [unit-tests] - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 - # - uses: actions/setup-node@v4 - # with: - # node-version: 20 - # - run: npm --prefix users/authservice install - # - run: npm --prefix users/userservice install - # - run: npm --prefix gatewayservice install - # - run: npm --prefix webapp install - # - run: CI=false npm --prefix webapp run build - # - run: CI=true npm --prefix webapp run test:e2e + e2e-tests: + needs: [unit-tests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm --prefix users/authservice install + - run: npm --prefix users/userservice install + - run: npm --prefix gatewayservice install + - run: npm --prefix webapp install + - run: CI=false npm --prefix webapp run build + - run: CI=true npm --prefix webapp run test:e2e docker-push-webapp: name: Push webapp Docker Image to GitHub Packages @@ -46,8 +46,8 @@ jobs: permissions: contents: read packages: write - # needs: [e2e-tests] - needs: [unit-tests] + needs: [e2e-tests] + # needs: [unit-tests] steps: - uses: actions/checkout@v4 - name: Publish to Registry @@ -67,8 +67,8 @@ jobs: permissions: contents: read packages: write - # needs: [e2e-tests] - needs: [unit-tests] + needs: [e2e-tests] + # needs: [unit-tests] steps: - uses: actions/checkout@v4 - name: Publish to Registry @@ -85,8 +85,8 @@ jobs: permissions: contents: read packages: write - # needs: [e2e-tests] - needs: [unit-tests] + needs: [e2e-tests] + # needs: [unit-tests] steps: - uses: actions/checkout@v4 - name: Publish to Registry @@ -103,8 +103,8 @@ jobs: permissions: contents: read packages: write - # needs: [e2e-tests] - needs: [unit-tests] + needs: [e2e-tests] + # needs: [unit-tests] steps: - uses: actions/checkout@v4 - name: Publish to Registry @@ -121,8 +121,8 @@ jobs: permissions: contents: read packages: write - # needs: [e2e-tests] - needs: [unit-tests] + needs: [e2e-tests] + # needs: [unit-tests] steps: - uses: actions/checkout@v4 - name: Publish to Registry diff --git a/docker-compose.yml b/docker-compose.yml index 82d4331..0a64eff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -85,7 +85,7 @@ services: depends_on: - gatewayservice ports: - - "80:80" + - "3000:3000" # platform: linux/arm64 diff --git a/docs/src/01_introduction_and_goals.adoc b/docs/src/01_introduction_and_goals.adoc index d1c8833..9824bde 100644 --- a/docs/src/01_introduction_and_goals.adoc +++ b/docs/src/01_introduction_and_goals.adoc @@ -15,7 +15,12 @@ These include * quality goals for the architecture and * relevant stakeholders and their expectations **** -In these points, the main goals and functional requirements will be explained. In order to give context on how the webapp will be developed. + +This project's aim, in summary, is developing a public web application which has a game consisting on answering multiple choice questions +with one correct answer generated using Wikidata. For reference of the general mechanic, see the spanish quiz program 'Saber y Ganar' and +its game mode 'Descartando' + +Next, important basic aspects of the project will be described in order to give context on how the webapp will be developed. === Requirements Overview @@ -142,10 +147,9 @@ Table with role names, person names, and their expectations with respect to the [options="header",cols="1,2,2"] |=== -|Role/Name|Contact|Expectations +|Role/Name|Members|Expectations | *Students* | Andrés Cadenas Blanco, Christian Fernandez Noriega , Adrián González Guadalupe and Luis Salvador Ferrero | Are the ones in charge of web development. They will work together to make the application. | *Teachers* | Pablo González | In charge of supervising the student's teamwork, ensuring the work accomplishes the goals in the best way possible and helping in the development and solving doubts. -| *Bussineses* | RTve has hired software development company HappySw | Emphasis the SOLID part of the web and have a high understanding of this area | *Users* | Anyone that wants to use the web | They should be able to understand how to use and move around the web with ease |=== diff --git a/docs/src/03_system_scope_and_context.adoc b/docs/src/03_system_scope_and_context.adoc index c74e0fe..7e495ec 100644 --- a/docs/src/03_system_scope_and_context.adoc +++ b/docs/src/03_system_scope_and_context.adoc @@ -57,14 +57,16 @@ the past games of all users in a ranking. ---- actor Player [Wikidata] <> +database MongoDB [WIQ Game] <> [UsersAPI] <> -[questionsAPI] <> +[QuestionsAPI] <> Player ..> (WIQ Game) : register/login -[questionsAPI] ..> Wikidata +[QuestionsAPI] ..> Wikidata [WIQ Game] ..> UsersAPI -[WIQ Game] ..> questionsAPI +[WIQ Game] ..> QuestionsAPI +[UsersAPI] <--> MongoDB ---- [cols="e,2e" options="header"] @@ -80,12 +82,12 @@ Player ..> (WIQ Game) : register/login |Wikidata |External data repository from which questions are generated -|MongoDB -|Database for storing players' info and scores - |Users Info API |Manages data of users, both registration/login data and their past scores -|questions API +|Questions API |Manages generation of questions from Wikidata + +|MongoDB +|Database for storing players' info and scores |=== \ No newline at end of file diff --git a/docs/src/05_building_block_view.adoc b/docs/src/05_building_block_view.adoc index 8c804f4..cdf5bc8 100644 --- a/docs/src/05_building_block_view.adoc +++ b/docs/src/05_building_block_view.adoc @@ -74,11 +74,11 @@ actor Player rectangle "WIQ Game (Level 1)"{ [WIQ Game GUI] [UsersAPI] -[questionsAPI] #BurlyWood +[QuestionsAPI] #BurlyWood Player ..> (WIQ Game GUI) -[questionsAPI] ..> Wikidata +[QuestionsAPI] ..> Wikidata [WIQ Game GUI] ..> UsersAPI -[WIQ Game GUI] ..> questionsAPI +[WIQ Game GUI] ..> QuestionsAPI } ---- @@ -153,7 +153,7 @@ Please prefer relevance over completeness. Specify important, surprising, risky, Leave out normal, simple, boring or standardized parts of your system **** -==== questions API (White Box) +==== Questions API (White Box) This is the Component that holds the functionallity for the main purpose of the webapp: Allowing players to see questions and answer them, getting a consequent score update. @@ -163,17 +163,15 @@ answer them, getting a consequent score update. ...describes the internal structure of _building block 1_. **** -[plantuml,"questions API (WhiteBox)",png] +[plantuml,"Questions API (WhiteBox)",png] ---- [Wikidata] [wikibase-sdk] <> [WIQ Game GUI] -database MongoDB rectangle "questionsAPI (Level 2)"{ [question-service] ..> [wikibase-sdk] [question-service] ..> [Wikidata] -[question-service] <--> MongoDB [WIQ Game GUI] ..> [question-service] : new question [WIQ Game GUI] ..> [question-service] : validate answer } @@ -195,7 +193,4 @@ Contained Black boxes:: |wikibase-sdk |External library that facilitates and simplifies the use of wikidata for the generation of questions. -|MongoDB -|Data about users and their scores is stored here - |=== \ No newline at end of file diff --git a/docs/src/06_runtime_view.adoc b/docs/src/06_runtime_view.adoc index 44d58d4..3264c92 100644 --- a/docs/src/06_runtime_view.adoc +++ b/docs/src/06_runtime_view.adoc @@ -39,9 +39,9 @@ GW -> WEBC: Send category statistics actor User entity WEBC as "Web Client" entity GW as "Gateway" -database DB as "MongoDB" entity QU as "Questions API" entity USERS as "Users Service API" +database DB as "MongoDB" User -> WEBC: Select a category to play WEBC -> GW: Get a question diff --git a/docs/src/07_deployment_view.adoc b/docs/src/07_deployment_view.adoc index 0a8008b..2951a29 100644 --- a/docs/src/07_deployment_view.adoc +++ b/docs/src/07_deployment_view.adoc @@ -121,9 +121,6 @@ MDB-[dashed]->AS US-[dashed]->MDB MDB-[dashed]->US - -QS-[dashed]->MDB -MDB-[dashed]->QS ---- The architecture of WIQ is based on microservices. Gateway service is the main entry point for the system. The web application is the main interface for the user to interact with the system. The user service is responsible for managing users. The authorization service is responsible for managing user permissions. The question service is responsible for generating questions. The mongo database is used to store data. diff --git a/docs/src/08_concepts.adoc b/docs/src/08_concepts.adoc index 09a5d54..8ab2647 100644 --- a/docs/src/08_concepts.adoc +++ b/docs/src/08_concepts.adoc @@ -55,6 +55,15 @@ image::08-Crosscutting-Concepts-Structure-EN.png["Possible topics for crosscutti See https://docs.arc42.org/section-8/[Concepts] in the arc42 documentation. **** +=== _Continuous integration and development_ + +Our way of working with github is having one developing branch towards which all pull requests are done +and have to be reviewed by at least one team member. +Sonar Cloud is set up so we know the testing coverage and whether we pass the quality gate with every pull request. +Once there is enough change in the develop branch with respect to the main one and it has enough quality we can +merge them and make a release. + + === _Microservice based system_ Different business functionallities will be developed in different independent services. diff --git a/users/authservice/auth-service.js b/users/authservice/auth-service.js index 73b9a4d..b4549e1 100644 --- a/users/authservice/auth-service.js +++ b/users/authservice/auth-service.js @@ -12,6 +12,7 @@ app.use(express.json()); // Connect to MongoDB const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/wiq-en1a-users'; + mongoose.connect(mongoUri); // Function to validate required fields in the request body diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js index 0d63028..80ef085 100644 --- a/users/userservice/user-service.js +++ b/users/userservice/user-service.js @@ -15,6 +15,7 @@ app.use(bodyParser.json()); // Connect to MongoDB const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/wiq-en1a-users'; mongoose.connect(mongoUri); +console.log(mongoUri); @@ -116,6 +117,7 @@ app.get('/ranking/user', async (req, res) => { app.post('/adduser', async (req, res) => { try { + console.log(mongoUri); // Check if required fields are present in the request body validateRequiredFields(req, ['username','email', 'password']); diff --git a/webapp/e2e/features/login-form.feature b/webapp/e2e/features/login-form.feature index 0060f6f..d8bdf51 100644 --- a/webapp/e2e/features/login-form.feature +++ b/webapp/e2e/features/login-form.feature @@ -1,6 +1,16 @@ -Feature: Registering a new user +Feature: Login a registered user Scenario: The user is registered in the site - Given A registered user - When I fill the data in the form and press submit - Then is logged \ No newline at end of file + Given An registered user + When I fill the data in the form to log in + Then is taken to the home page + +Scenario: User logs in with invalid credentials + Given a registered user with username "testUser" and password "testpass" + When I fill the login form with username "testUser" and incorrect password "wrongpass" + And I remain on the login page + +Scenario: User attempts to login without entering credentials + Given a registered user with username "testUser" and password "testpass" + When I attempt to log in without entering any credentials + And I remain on the login page \ No newline at end of file diff --git a/webapp/e2e/features/userprofile-form.feature b/webapp/e2e/features/userprofile-form.feature new file mode 100644 index 0000000..467d758 --- /dev/null +++ b/webapp/e2e/features/userprofile-form.feature @@ -0,0 +1,17 @@ +Feature: View and Change User Quiz Rankings + +Scenario: Viewing Global Rankings + Given the user navigates to their profile + When they select the "Global" category + Then they see their performance statistics for global quizzes + +Scenario: Switching Category to Flags + Given the user is on their profile page + When they click on the "Flags" category + Then they view their performance metrics for flag-related quizzes + +Scenario: Switching Category to Food + Given the user is on their profile page + When they click on the "Food" category + Then they view their performance metrics for food-related quizzes + diff --git a/webapp/e2e/steps/login-form.steps.js b/webapp/e2e/steps/login-form.steps.js index 0f30b9f..239389b 100644 --- a/webapp/e2e/steps/login-form.steps.js +++ b/webapp/e2e/steps/login-form.steps.js @@ -3,69 +3,89 @@ const { defineFeature, loadFeature }=require('jest-cucumber'); const setDefaultOptions = require('expect-puppeteer').setDefaultOptions const feature = loadFeature('./features/login-form.feature'); -const axios = require('axios'); -const MockAdapter = require('axios-mock-adapter'); -const mockAxios = new MockAdapter(axios); let page; let browser; defineFeature(feature, test => { - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo:40 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }) - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - - - }); - - beforeEach(async () => { - // Reset any state or actions before each test - await page.reload({ waitUntil: 'networkidle0' }); + beforeAll(async () => { + + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 30 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }) + + await page + .goto("http://localhost:3000/login", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test('The user is registered in the site', ({given,when,then}) => { + + let username; + let password; + + given('An registered user', async () => { + username = "testUser" + password = "testpass"; + }); - - test('The user is registered in the site', ({given,when,then}) => { - - let username; - let password; - let email - - given('A registered user', async () => { - username = "t1" - password = "t1pass" - - await expect(page).toClick("button", { text: "Create account" }); - }); - - when('I fill the data in the form and press submit', async () => { - await expect(page).toClick('a', { text: 'Already have an account? Log in here.' }); + when('I fill the data in the form to log in', async () => { await expect(page).toFill('input[name="username"]', username); await expect(page).toFill('input[name="password"]', password); - - mockAxios.onPost('http://localhost:8000/login').reply(200, { username:"t1",email:"t1email",createdAt: '2024-01-01T12:34:56Z',token: 'testToken'}); - - - await expect(page).toClick('button', { text: 'Login' }) - }); - - then('is logged', async () => { - await expect(page).toMatchElement("div", { text: "Welcome back, " + username + "!" }); - }); - }) - - afterAll(async ()=>{ - browser.close() - }) - - }); \ No newline at end of file + await expect(page).toClick("button", { text: "Log In" }); + }); + + then('is taken to the home page', async () => { + await page.waitForNavigation({ waitUntil: "networkidle0" }); + await expect(page).toMatchElement("h1", { text: "Welcome back, " + username + "!" }); + await expect(page).toClick("button", { text: "Log out" }); + }); + + }); + + test('User logs in with invalid credentials', ({ given, when, then }) => { + given('a registered user with username "testUser" and password "testpass"', async () => { + // No specific action needed since the user is already registered + }); + + when('I fill the login form with username "testUser" and incorrect password "wrongpass"', async () => { + await expect(page).toFill('input[name="username"]', 'testUser'); + await expect(page).toFill('input[name="password"]', 'wrongpass'); + await expect(page).toClick("button", { text: "Log In" }); + }); + + + then('I remain on the login page', async () => { + await expect(page).toMatchElement("h1", { text: "Access WIQ" }); + }); + }); + + test('User attempts to login without entering credentials', ({ given, when, then }) => { + given('a registered user with username "testUser" and password "testpass"', async () => { + // No specific action needed since the user is already registered + }); + + when('I attempt to log in without entering any credentials', async () => { + await expect(page).toFill('input[name="username"]', ''); + await expect(page).toFill('input[name="password"]', ''); + await expect(page).toClick("button", { text: "Log In" }); + }); + + + then('I remain on the login page', async () => { + await expect(page).toMatchElement("h1", { text: "Access WIQ" }); + }); + }); + + afterAll(async ()=>{ + browser.close() + }) + +}); \ No newline at end of file diff --git a/webapp/e2e/steps/register-form.steps.js b/webapp/e2e/steps/register-form.steps.js index ad9e64f..7e87115 100644 --- a/webapp/e2e/steps/register-form.steps.js +++ b/webapp/e2e/steps/register-form.steps.js @@ -3,9 +3,6 @@ const { defineFeature, loadFeature }=require('jest-cucumber'); const setDefaultOptions = require('expect-puppeteer').setDefaultOptions const feature = loadFeature('./features/register-form.feature'); -const axios = require('axios'); -const MockAdapter = require('axios-mock-adapter'); -const mockAxios = new MockAdapter(axios); let page; let browser; @@ -13,58 +10,47 @@ let browser; defineFeature(feature, test => { beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo:60 }); + : await puppeteer.launch({ headless: false, slowMo: 30 }); page = await browser.newPage(); //Way of setting up the timeout setDefaultOptions({ timeout: 10000 }) await page - .goto("http://localhost:3000", { + .goto("http://localhost:3000/login", { waitUntil: "networkidle0", }) .catch(() => {}); - - - }); - - beforeEach(async () => { - // Reset any state or actions before each test - await page.reload({ waitUntil: 'networkidle0' }); }); test('The user is not registered in the site', ({given,when,then}) => { let username; let password; - let email given('An unregistered user', async () => { - username = "t1" - email = "t1email" - password = "t1pass" - - await expect(page).toClick("button", { text: "Create account" }); + username = "newUser" + password = "newUser" + await expect(page).toClick("a", { text: "Create account" }); }); when('I fill the data in the form and press submit', async () => { + await expect(page).toFill('input[name="username"]', username); - await expect(page).toFill('input[name="email"]', email); + await expect(page).toFill('input[name="email"]', username + "@" + "gmail.com"); await expect(page).toFill('input[name="password"]', password); - await expect(page).toFill('input[name="cpassword"]', password); - - // mockAxios.onPost('http://localhost:8000/adduser').reply(200, { username: "t1", email: "t1email", password: "t1pass" }); - mockAxios.onPost('http://localhost:8000/adduser').reply(200, { username:"t1",email:"t1email",password: 't1pass'}); - - - await expect(page).toClick('button', { text: 'Register' }) + await expect(page).toFill('input[name="cpassword"]', password); + await expect(page).toClick("button", { text: "Register" }); }); then('is taken to login', async () => { - //await expect(page).toMatchElement("div", { text: "Login" }); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + await expect(page).toMatchElement("h1", { text: "Access WIQ" }); }); - }) + + }); afterAll(async ()=>{ browser.close() diff --git a/webapp/e2e/steps/userprofile-form.steps.js b/webapp/e2e/steps/userprofile-form.steps.js new file mode 100644 index 0000000..6232c9c --- /dev/null +++ b/webapp/e2e/steps/userprofile-form.steps.js @@ -0,0 +1,107 @@ +const puppeteer = require('puppeteer'); +const { defineFeature, loadFeature }=require('jest-cucumber'); +const setDefaultOptions = require('expect-puppeteer').setDefaultOptions +const feature = loadFeature('./features/userprofile-form.feature'); + + +let page; +let browser; + +defineFeature(feature, test => { + + beforeAll(async () => { + + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 20 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }) + + await page + .goto("http://localhost:3000/login", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test('Viewing Global Rankings', ({given,when,then}) => { + + let username; + let password; + + given('the user navigates to their profile', async () => { + username = "testUser" + password = "testpass"; + await expect(page).toClick("a", { text: "Log In" }); + await expect(page).toFill('input[name="username"]', username); + await expect(page).toFill('input[name="password"]', password); + await expect(page).toClick("button", { text: "Log In" }); + await expect(page).toClick("button", { text: "My stats" }); + }); + + when('they select the "Global" category', async () => { + + await expect(page).toMatchElement("h2", { text: "Username: " + username }); + await expect(page).toClick("button", { text: "Flags" }); + await expect(page).toClick("button", { text: "Global" }); + }); + + then('they see their performance statistics for global quizzes', async () => { + await expect(page).toMatchElement('.ranking h3', { text: "global Ranking" }); + await expect(page).toMatchElement(".ranking p:nth-child(1)", { text: "Total Answered Questions: " + 1 }); + }); + + }); + + test('Switching Category to Flags', ({given,when,then}) => { + + let username; + let password; + + given('the user is on their profile page', async () => { + username = "testUser" + password = "testpass"; + await expect(page).toMatchElement("h2", { text: "Username: " + username }); + }); + + when('they click on the "Flags" category', async () => { + + await expect(page).toClick("button", { text: "Flags" }); + }); + + then('they view their performance metrics for flag-related quizzes', async () => { + await expect(page).toMatchElement('.ranking h3', { text: "flags Ranking" }); + await expect(page).toMatchElement(".ranking p:nth-child(1)", { text: "Total Answered Questions: " + 1 }); + }); + + }); + + test('Switching Category to Food', ({given,when,then}) => { + + let username; + let password; + + given('the user is on their profile page', async () => { + username = "testUser" + password = "testpass"; + await expect(page).toMatchElement("h2", { text: "Username: " + username }); + }); + + when('they click on the "Food" category', async () => { + + await expect(page).toClick("button", { text: "Food" }); + }); + + then('they view their performance metrics for food-related quizzes', async () => { + await expect(page).toMatchElement('.ranking h3', { text: "foods Ranking" }); + await expect(page).toMatchElement(".ranking p:nth-child(1)", { text: "Total Answered Questions: " + 0 }); + }); + + }); + + afterAll(async ()=>{ + browser.close() + }) + +}); \ No newline at end of file diff --git a/webapp/e2e/test-environment-setup.js b/webapp/e2e/test-environment-setup.js index 7b7ed51..8a43a0f 100644 --- a/webapp/e2e/test-environment-setup.js +++ b/webapp/e2e/test-environment-setup.js @@ -1,4 +1,6 @@ const { MongoMemoryServer } = require('mongodb-memory-server'); +const User = require('../../users/userservice/user-model'); +const axios = require('axios'); let mongoserver; @@ -9,11 +11,37 @@ let gatewayservice; async function startServer() { console.log('Starting MongoDB memory server...'); mongoserver = await MongoMemoryServer.create(); + const mongoUri = mongoserver.getUri(); process.env.MONGODB_URI = mongoUri; userservice = await require("../../users/userservice/user-service"); authservice = await require("../../users/authservice/auth-service"); gatewayservice = await require("../../gatewayservice/gateway-service"); + + // Add test user + await addUser('testUser', 'test@email', 'testpass'); + } + async function addUser(username, email, password) { + try { + const response = await axios.post('http://localhost:8001/adduser', { + username: username, + email: email, + password: password + }); + + const pints = await axios.post('http://localhost:8001/addpoints', { + username: username, + category: "flags", + correct: "true" + }); + + } catch (error) { + console.error('Error adding user:', error.response.data); + } +} + + + startServer(); diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 337894f..6e23848 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@material-tailwind/react": "^2.1.9", "@mui/icons-material": "^5.15.15", "@mui/material": "^5.15.15", "@testing-library/jest-dom": "^5.17.0", @@ -2588,6 +2589,20 @@ "@floating-ui/utils": "^0.2.0" } }, + "node_modules/@floating-ui/react": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.19.0.tgz", + "integrity": "sha512-fgYvN4ksCi5OvmPXkyOT8o5a8PSKHMzPHt+9mR6KYWdF16IAjWRLZPAAziI2sznaWT23drRFrYw64wdvYqqaQw==", + "dependencies": { + "@floating-ui/react-dom": "^1.2.2", + "aria-hidden": "^1.1.3", + "tabbable": "^6.0.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/react-dom": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", @@ -2600,6 +2615,18 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@floating-ui/react/node_modules/@floating-ui/react-dom": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-1.3.0.tgz", + "integrity": "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==", + "dependencies": { + "@floating-ui/dom": "^1.2.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/utils": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", @@ -4518,6 +4545,34 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@material-tailwind/react": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@material-tailwind/react/-/react-2.1.9.tgz", + "integrity": "sha512-3uPlJE9yK4JF9DEQO4I1QbjR8o05+4fysLqoZ0v38TDOLE2tvDRhTBVhn6Mp9vSsq5CoJOKgemG7kbkOFAji4A==", + "dependencies": { + "@floating-ui/react": "0.19.0", + "classnames": "2.3.2", + "deepmerge": "4.2.2", + "framer-motion": "6.5.1", + "material-ripple-effects": "2.0.1", + "prop-types": "15.8.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "tailwind-merge": "1.8.1" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, + "node_modules/@material-tailwind/react/node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.3.tgz", @@ -4528,6 +4583,64 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@motionone/animation": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.17.0.tgz", + "integrity": "sha512-ANfIN9+iq1kGgsZxs+Nz96uiNcPLGTXwfNo2Xz/fcJXniPYpaz/Uyrfa+7I5BPLxCP82sh7quVDudf1GABqHbg==", + "dependencies": { + "@motionone/easing": "^10.17.0", + "@motionone/types": "^10.17.0", + "@motionone/utils": "^10.17.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@motionone/dom": { + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.12.0.tgz", + "integrity": "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==", + "dependencies": { + "@motionone/animation": "^10.12.0", + "@motionone/generators": "^10.12.0", + "@motionone/types": "^10.12.0", + "@motionone/utils": "^10.12.0", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + } + }, + "node_modules/@motionone/easing": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.17.0.tgz", + "integrity": "sha512-Bxe2wSuLu/qxqW4rBFS5m9tMLOw+QBh8v5A7Z5k4Ul4sTj5jAOfZG5R0bn5ywmk+Fs92Ij1feZ5pmC4TeXA8Tg==", + "dependencies": { + "@motionone/utils": "^10.17.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@motionone/generators": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.17.0.tgz", + "integrity": "sha512-T6Uo5bDHrZWhIfxG/2Aut7qyWQyJIWehk6OB4qNvr/jwA/SRmixwbd7SOrxZi1z5rH3LIeFFBKK1xHnSbGPZSQ==", + "dependencies": { + "@motionone/types": "^10.17.0", + "@motionone/utils": "^10.17.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@motionone/types": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.17.0.tgz", + "integrity": "sha512-EgeeqOZVdRUTEHq95Z3t8Rsirc7chN5xFAPMYFobx8TPubkEfRSm5xihmMUkbaR2ErKJTUw3347QDPTHIW12IA==" + }, + "node_modules/@motionone/utils": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.17.0.tgz", + "integrity": "sha512-bGwrki4896apMWIj9yp5rAS2m0xyhxblg6gTB/leWDPt+pb410W8lYWsxyurX+DH+gO1zsQsfx2su/c1/LtTpg==", + "dependencies": { + "@motionone/types": "^10.17.0", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + } + }, "node_modules/@mui/base": { "version": "5.0.0-beta.40", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", @@ -6861,6 +6974,17 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -8303,6 +8427,11 @@ "node": ">= 0.4" } }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -11652,6 +11781,49 @@ "node": ">=0.10.0" } }, + "node_modules/framer-motion": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-6.5.1.tgz", + "integrity": "sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==", + "dependencies": { + "@motionone/dom": "10.12.0", + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "popmotion": "11.0.3", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": ">=16.8 || ^17.0.0 || ^18.0.0", + "react-dom": ">=16.8 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/framer-motion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + }, + "node_modules/framesync": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", + "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -12197,6 +12369,11 @@ "he": "bin/he" } }, + "node_modules/hey-listen": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" + }, "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", @@ -18924,6 +19101,11 @@ "node": ">=0.10.0" } }, + "node_modules/material-ripple-effects": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/material-ripple-effects/-/material-ripple-effects-2.0.1.tgz", + "integrity": "sha512-hHlUkZAuXbP94lu02VgrPidbZ3hBtgXBtjlwR8APNqOIgDZMV8MCIcsclL8FmGJQHvnORyvoQgC965vPsiyXLQ==" + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -20441,6 +20623,17 @@ "node": ">=4" } }, + "node_modules/popmotion": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz", + "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==", + "dependencies": { + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + } + }, "node_modules/posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -25835,6 +26028,15 @@ "webpack": "^5.0.0" } }, + "node_modules/style-value-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz", + "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==", + "dependencies": { + "hey-listen": "^1.0.8", + "tslib": "^2.1.0" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -26093,6 +26295,16 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, + "node_modules/tailwind-merge": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.8.1.tgz", + "integrity": "sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww==" + }, "node_modules/tailwindcss": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", @@ -29794,6 +30006,26 @@ "@floating-ui/utils": "^0.2.0" } }, + "@floating-ui/react": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.19.0.tgz", + "integrity": "sha512-fgYvN4ksCi5OvmPXkyOT8o5a8PSKHMzPHt+9mR6KYWdF16IAjWRLZPAAziI2sznaWT23drRFrYw64wdvYqqaQw==", + "requires": { + "@floating-ui/react-dom": "^1.2.2", + "aria-hidden": "^1.1.3", + "tabbable": "^6.0.1" + }, + "dependencies": { + "@floating-ui/react-dom": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-1.3.0.tgz", + "integrity": "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==", + "requires": { + "@floating-ui/dom": "^1.2.1" + } + } + } + }, "@floating-ui/react-dom": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", @@ -31278,6 +31510,29 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "@material-tailwind/react": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@material-tailwind/react/-/react-2.1.9.tgz", + "integrity": "sha512-3uPlJE9yK4JF9DEQO4I1QbjR8o05+4fysLqoZ0v38TDOLE2tvDRhTBVhn6Mp9vSsq5CoJOKgemG7kbkOFAji4A==", + "requires": { + "@floating-ui/react": "0.19.0", + "classnames": "2.3.2", + "deepmerge": "4.2.2", + "framer-motion": "6.5.1", + "material-ripple-effects": "2.0.1", + "prop-types": "15.8.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "tailwind-merge": "1.8.1" + }, + "dependencies": { + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + } + } + }, "@mongodb-js/saslprep": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.3.tgz", @@ -31288,6 +31543,64 @@ "sparse-bitfield": "^3.0.3" } }, + "@motionone/animation": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.17.0.tgz", + "integrity": "sha512-ANfIN9+iq1kGgsZxs+Nz96uiNcPLGTXwfNo2Xz/fcJXniPYpaz/Uyrfa+7I5BPLxCP82sh7quVDudf1GABqHbg==", + "requires": { + "@motionone/easing": "^10.17.0", + "@motionone/types": "^10.17.0", + "@motionone/utils": "^10.17.0", + "tslib": "^2.3.1" + } + }, + "@motionone/dom": { + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/@motionone/dom/-/dom-10.12.0.tgz", + "integrity": "sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==", + "requires": { + "@motionone/animation": "^10.12.0", + "@motionone/generators": "^10.12.0", + "@motionone/types": "^10.12.0", + "@motionone/utils": "^10.12.0", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + } + }, + "@motionone/easing": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/easing/-/easing-10.17.0.tgz", + "integrity": "sha512-Bxe2wSuLu/qxqW4rBFS5m9tMLOw+QBh8v5A7Z5k4Ul4sTj5jAOfZG5R0bn5ywmk+Fs92Ij1feZ5pmC4TeXA8Tg==", + "requires": { + "@motionone/utils": "^10.17.0", + "tslib": "^2.3.1" + } + }, + "@motionone/generators": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/generators/-/generators-10.17.0.tgz", + "integrity": "sha512-T6Uo5bDHrZWhIfxG/2Aut7qyWQyJIWehk6OB4qNvr/jwA/SRmixwbd7SOrxZi1z5rH3LIeFFBKK1xHnSbGPZSQ==", + "requires": { + "@motionone/types": "^10.17.0", + "@motionone/utils": "^10.17.0", + "tslib": "^2.3.1" + } + }, + "@motionone/types": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/types/-/types-10.17.0.tgz", + "integrity": "sha512-EgeeqOZVdRUTEHq95Z3t8Rsirc7chN5xFAPMYFobx8TPubkEfRSm5xihmMUkbaR2ErKJTUw3347QDPTHIW12IA==" + }, + "@motionone/utils": { + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@motionone/utils/-/utils-10.17.0.tgz", + "integrity": "sha512-bGwrki4896apMWIj9yp5rAS2m0xyhxblg6gTB/leWDPt+pb410W8lYWsxyurX+DH+gO1zsQsfx2su/c1/LtTpg==", + "requires": { + "@motionone/types": "^10.17.0", + "hey-listen": "^1.0.8", + "tslib": "^2.3.1" + } + }, "@mui/base": { "version": "5.0.0-beta.40", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", @@ -32956,6 +33269,14 @@ "sprintf-js": "~1.0.2" } }, + "aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "requires": { + "tslib": "^2.0.0" + } + }, "aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -33975,6 +34296,11 @@ } } }, + "classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -36459,6 +36785,45 @@ "map-cache": "^0.2.2" } }, + "framer-motion": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-6.5.1.tgz", + "integrity": "sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==", + "requires": { + "@emotion/is-prop-valid": "^0.8.2", + "@motionone/dom": "10.12.0", + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "popmotion": "11.0.3", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "requires": { + "@emotion/memoize": "0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + } + } + }, + "framesync": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz", + "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==", + "requires": { + "tslib": "^2.1.0" + } + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -36856,6 +37221,11 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hey-listen": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" + }, "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", @@ -41972,6 +42342,11 @@ "object-visit": "^1.0.0" } }, + "material-ripple-effects": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/material-ripple-effects/-/material-ripple-effects-2.0.1.tgz", + "integrity": "sha512-hHlUkZAuXbP94lu02VgrPidbZ3hBtgXBtjlwR8APNqOIgDZMV8MCIcsclL8FmGJQHvnORyvoQgC965vPsiyXLQ==" + }, "mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -43073,6 +43448,17 @@ } } }, + "popmotion": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz", + "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==", + "requires": { + "framesync": "6.0.1", + "hey-listen": "^1.0.8", + "style-value-types": "5.0.0", + "tslib": "^2.1.0" + } + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -46946,6 +47332,15 @@ "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", "requires": {} }, + "style-value-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz", + "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==", + "requires": { + "hey-listen": "^1.0.8", + "tslib": "^2.1.0" + } + }, "styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -47136,6 +47531,16 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, + "tailwind-merge": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.8.1.tgz", + "integrity": "sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww==" + }, "tailwindcss": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", diff --git a/webapp/package.json b/webapp/package.json index be7abce..5c0587c 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -5,6 +5,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@material-tailwind/react": "^2.1.9", "@mui/icons-material": "^5.15.15", "@mui/material": "^5.15.15", "@testing-library/jest-dom": "^5.17.0", @@ -24,7 +25,7 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build ", - "prod": "serve -s build -p 80", + "prod": "serve -s build", "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!axios)/'", "test:e2e": "start-server-and-test 'node e2e/test-environment-setup.js' http://localhost:8000/health prod 3000 \"cd e2e && jest\"", "eject": "react-scripts eject" diff --git a/webapp/src/components/Navbar.jsx b/webapp/src/components/Navbar.jsx index f574a21..49c1c27 100644 --- a/webapp/src/components/Navbar.jsx +++ b/webapp/src/components/Navbar.jsx @@ -2,7 +2,14 @@ import { Link } from 'react-router-dom'; import useIsAuthenticated from 'react-auth-kit/hooks/useIsAuthenticated'; import useSignOut from 'react-auth-kit/hooks/useSignOut'; import { useNavigate } from 'react-router-dom'; -function Navbar() { +import React from 'react' +import { + Collapse, + IconButton, +} from "@material-tailwind/react"; +import MenuRoundedIcon from '@mui/icons-material/MenuRounded'; +import ClearRoundedIcon from '@mui/icons-material/ClearRounded'; +function NavbarDefault() { const isAuthenticated = useIsAuthenticated(); const signOut = useSignOut(); const navigate = useNavigate(); @@ -11,44 +18,68 @@ function Navbar() { navigate('/login'); } + const [openNav, setOpenNav] = React.useState(false); + + React.useEffect(() => { + window.addEventListener( + "resize", + () => window.innerWidth >= 960 && setOpenNav(false), + ); + }, []); + const navList = ( + // + + ); + return ( -