From f00146358580f60e45c6fd5fae4a723dc9917140 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 4 Aug 2023 16:46:51 +0200 Subject: [PATCH] frontend: add tests & stories for many components Also more compound adoption Also prettier upgrade --- frontend/package-lock.json | 550 ++++++++++++++++-- frontend/package.json | 23 +- frontend/src/Router.tsx | 28 +- frontend/src/atoms.ts | 8 +- .../components/{ => Block}/Block.module.css | 0 .../components/{ => Block}/Block.stories.tsx | 13 +- frontend/src/components/Block/Block.test.tsx | 45 ++ frontend/src/components/{ => Block}/Block.tsx | 14 +- .../Block/__snapshots__/Block.test.tsx.snap | 33 ++ frontend/src/components/Block/index.ts | 15 + .../{ => BlockList}/BlockList.module.css | 4 +- .../BlockList/BlockList.stories.tsx | 44 ++ .../components/BlockList/BlockList.test.tsx | 42 ++ .../components/{ => BlockList}/BlockList.tsx | 10 +- .../__snapshots__/BlockList.test.tsx.snap | 30 + frontend/src/components/BlockList/index.ts | 15 + frontend/src/components/BrowserSession.tsx | 2 +- .../src/components/BrowserSessionList.tsx | 4 +- frontend/src/components/CompatSession.tsx | 2 +- frontend/src/components/CompatSessionList.tsx | 4 +- .../LoadingScreen/LoadingScreen.module.css | 21 + .../LoadingScreen.stories.tsx | 3 + .../LoadingScreen.test.tsx | 12 +- .../{ => LoadingScreen}/LoadingScreen.tsx | 6 +- .../__snapshots__/LoadingScreen.test.tsx.snap | 28 + .../src/components/LoadingScreen/index.ts | 15 + .../LoadingSpinner/LoadingSpinner.module.css | 28 + .../LoadingSpinner.stories.tsx | 0 .../{ => LoadingSpinner}/LoadingSpinner.tsx | 4 +- .../src/components/LoadingSpinner/index.ts | 15 + .../components/{ => NavBar}/NavBar.module.css | 0 .../src/components/NavBar/NavBar.stories.tsx | 52 ++ .../src/components/{ => NavBar}/NavBar.tsx | 0 frontend/src/components/NavBar/index.ts | 15 + .../{ => NavItem}/NavItem.module.css | 0 .../components/NavItem/NavItem.stories.tsx | 59 ++ .../src/components/NavItem/NavItem.test.tsx | 86 +++ .../src/components/{ => NavItem}/NavItem.tsx | 2 +- .../__snapshots__/NavItem.test.tsx.snap | 38 ++ frontend/src/components/NavItem/index.ts | 15 + frontend/src/components/OAuth2Session.tsx | 2 +- frontend/src/components/OAuth2SessionList.tsx | 4 +- frontend/src/components/UserEmail.tsx | 12 +- frontend/src/components/UserEmailList.tsx | 6 +- frontend/src/gql/fragment-masking.ts | 16 +- frontend/src/gql/gql.ts | 92 +-- frontend/src/gql/graphql.ts | 2 +- frontend/src/graphql.ts | 2 +- frontend/src/main.tsx | 2 +- frontend/src/pages/BrowserSession.tsx | 2 +- frontend/src/pages/OAuth2Client.tsx | 2 +- frontend/src/pagination.ts | 12 +- 52 files changed, 1246 insertions(+), 193 deletions(-) rename frontend/src/components/{ => Block}/Block.module.css (100%) rename frontend/src/components/{ => Block}/Block.stories.tsx (86%) create mode 100644 frontend/src/components/Block/Block.test.tsx rename frontend/src/components/{ => Block}/Block.tsx (75%) create mode 100644 frontend/src/components/Block/__snapshots__/Block.test.tsx.snap create mode 100644 frontend/src/components/Block/index.ts rename frontend/src/components/{ => BlockList}/BlockList.module.css (93%) create mode 100644 frontend/src/components/BlockList/BlockList.stories.tsx create mode 100644 frontend/src/components/BlockList/BlockList.test.tsx rename frontend/src/components/{ => BlockList}/BlockList.tsx (74%) create mode 100644 frontend/src/components/BlockList/__snapshots__/BlockList.test.tsx.snap create mode 100644 frontend/src/components/BlockList/index.ts create mode 100644 frontend/src/components/LoadingScreen/LoadingScreen.module.css rename frontend/src/components/{ => LoadingScreen}/LoadingScreen.stories.tsx (95%) rename frontend/src/components/{ => LoadingScreen}/LoadingScreen.test.tsx (73%) rename frontend/src/components/{ => LoadingScreen}/LoadingScreen.tsx (83%) create mode 100644 frontend/src/components/LoadingScreen/__snapshots__/LoadingScreen.test.tsx.snap create mode 100644 frontend/src/components/LoadingScreen/index.ts create mode 100644 frontend/src/components/LoadingSpinner/LoadingSpinner.module.css rename frontend/src/components/{ => LoadingSpinner}/LoadingSpinner.stories.tsx (100%) rename frontend/src/components/{ => LoadingSpinner}/LoadingSpinner.tsx (94%) create mode 100644 frontend/src/components/LoadingSpinner/index.ts rename frontend/src/components/{ => NavBar}/NavBar.module.css (100%) create mode 100644 frontend/src/components/NavBar/NavBar.stories.tsx rename frontend/src/components/{ => NavBar}/NavBar.tsx (100%) create mode 100644 frontend/src/components/NavBar/index.ts rename frontend/src/components/{ => NavItem}/NavItem.module.css (100%) create mode 100644 frontend/src/components/NavItem/NavItem.stories.tsx create mode 100644 frontend/src/components/NavItem/NavItem.test.tsx rename frontend/src/components/{ => NavItem}/NavItem.tsx (96%) create mode 100644 frontend/src/components/NavItem/__snapshots__/NavItem.test.tsx.snap create mode 100644 frontend/src/components/NavItem/index.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index aa12324a..42294a0b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,7 +16,8 @@ "@urql/exchange-refocus": "^1.0.2", "@urql/exchange-request-policy": "^1.0.2", "@vector-im/compound-design-tokens": "^0.0.3", - "@vector-im/compound-web": "^0.2.3", + "@vector-im/compound-web": "^0.2.4", + "classnames": "^2.3.2", "date-fns": "^2.30.0", "graphql": "^16.7.1", "jotai": "^2.2.3", @@ -36,13 +37,14 @@ "@storybook/addon-controls": "^7.2.1", "@storybook/addon-docs": "^7.2.1", "@storybook/addon-essentials": "^7.2.1", - "@storybook/addon-measure": "^7.2.0", - "@storybook/addon-outline": "^7.2.0", - "@storybook/addon-toolbars": "^7.2.0", - "@storybook/addon-viewport": "^7.2.0", + "@storybook/addon-measure": "^7.2.1", + "@storybook/addon-outline": "^7.2.1", + "@storybook/addon-toolbars": "^7.2.1", + "@storybook/addon-viewport": "^7.2.1", "@storybook/react": "^7.2.1", "@storybook/react-vite": "^7.2.1", - "@types/node": "^20.4.6", + "@testing-library/react": "^14.0.0", + "@types/node": "^20.4.7", "@types/react": "^18.2.18", "@types/react-dom": "^18.2.7", "@types/react-test-renderer": "^18.0.0", @@ -50,14 +52,15 @@ "@vitest/coverage-v8": "^0.34.1", "autoprefixer": "^10.4.14", "eslint": "^8.46.0", - "eslint-config-prettier": "^8.9.0", + "eslint-config-prettier": "^8.10.0", "eslint-config-react-app": "^7.0.1", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.28.0", - "eslint-plugin-matrix-org": "^1.2.0", - "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-matrix-org": "^1.2.1", + "eslint-plugin-prettier": "^5.0.0", + "happy-dom": "^10.7.0", "postcss": "^8.4.27", - "prettier": "2.8.0", + "prettier": "3.0.1", "react-test-renderer": "^18.2.0", "rimraf": "^5.0.1", "storybook": "^7.2.1", @@ -6977,6 +6980,21 @@ "node": ">=10" } }, + "node_modules/@storybook/cli/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@storybook/cli/node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -7049,6 +7067,21 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/@storybook/codemod/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@storybook/components": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.2.1.tgz", @@ -7455,6 +7488,21 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/@storybook/csf-tools/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@storybook/docs-mdx": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@storybook/docs-mdx/-/docs-mdx-0.1.0.tgz", @@ -8418,6 +8466,21 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@svgr/plugin-prettier/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/@svgr/plugin-svgo": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", @@ -8614,6 +8677,136 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@testing-library/dom": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", + "integrity": "sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", + "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -8622,6 +8815,12 @@ "node": ">=10.13.0" } }, + "node_modules/@types/aria-query": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.20.1", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", @@ -8880,9 +9079,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.4.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.6.tgz", - "integrity": "sha512-q0RkvNgMweWWIvSMDiXhflGUKMdIxBo2M2tYM/0kEGDueQByFzK4KZAgu5YHGFNxziTlppNpTIBcqHQAxlfHdA==", + "version": "20.4.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.7.tgz", + "integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==", "dev": true }, "node_modules/@types/node-fetch": { @@ -9409,13 +9608,14 @@ } }, "node_modules/@vector-im/compound-web": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@vector-im/compound-web/-/compound-web-0.2.3.tgz", - "integrity": "sha512-7FI6Q1LN8dXur2sarP7UeMtAKcejuFw6AppM9Lu9fFjwLlbuIX2ZEprw1qa+EzgzUTysTU1TTdo7fxNqQwAQcA==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@vector-im/compound-web/-/compound-web-0.2.4.tgz", + "integrity": "sha512-CRBZAYVQWbF/C+921KQ+DiimEHdd+UcOygHCU78qkeglOmZ0hZdKddGtOuIeZztYps2xSZAhG1RDVWvwmUHAnA==", "dependencies": { "@radix-ui/react-form": "^0.0.3", "classnames": "^2.3.2", - "lodash": "^4.17.21" + "graphemer": "^1.4.0", + "rimraf": "^3.0.1" }, "peerDependencies": { "@fontsource/inter": "^5", @@ -9434,6 +9634,20 @@ } } }, + "node_modules/@vector-im/compound-web/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz", @@ -10403,8 +10617,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base16": { "version": "1.0.0", @@ -10547,7 +10760,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11344,8 +11556,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -11571,6 +11782,12 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -11675,6 +11892,41 @@ "node": ">=6" } }, + "node_modules/deep-equal": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", + "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.1", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -12045,6 +12297,12 @@ "node": ">=6.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -12330,6 +12588,32 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -12563,9 +12847,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.9.0.tgz", - "integrity": "sha512-+sbni7NfVXnOpnRadUA8S28AUlsZt9GjgFvABIRL9Hkn8KqNzOp+7Lw4QWtrwn20KzU3wqu1QoOj2m+7rKRqkA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -12867,9 +13151,9 @@ } }, "node_modules/eslint-plugin-matrix-org": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-1.2.0.tgz", - "integrity": "sha512-Wp5CeLnyEwGBn8ZfVbSuO2y0Fs51IWonPJ1QRQTntaRxOkEQnnky3gOPwpfGJ8JB0CxYr1zXfeHh8LcYHW4wcg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-1.2.1.tgz", + "integrity": "sha512-A3cDjhG7RHwfCS8o3bOip8hSCsxtmgk2ahvqE5v/Ic2kPEZxixY6w8zLj7hFGsrRmPSEpLWqkVLt8uvQBapiQA==", "dev": true, "peerDependencies": { "@babel/core": "*", @@ -12887,26 +13171,34 @@ "eslint-plugin-react": "*", "eslint-plugin-react-hooks": "*", "eslint-plugin-unicorn": "*", - "prettier": "2.8.0", + "prettier": "*", "typescript": "*" } }, "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", + "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", "dev": true, "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" }, "engines": { - "node": ">=12.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } @@ -14169,8 +14461,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", @@ -14408,7 +14699,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -14532,8 +14822,7 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/graphql": { "version": "16.7.1", @@ -14688,6 +14977,53 @@ "node": ">=0.10.0" } }, + "node_modules/happy-dom": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-10.7.0.tgz", + "integrity": "sha512-dJFPbA7KW4kkH3MFJBPTX73ywOfcrJcu6CAUw0CnCH0aEDcnPNG8QVmOAxsJSvTZFzLCnmSx/jMzAp00hdm0gw==", + "dev": true, + "dependencies": { + "css.escape": "^1.5.1", + "entities": "^4.5.0", + "iconv-lite": "^0.6.3", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + } + }, + "node_modules/happy-dom/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/happy-dom/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/happy-dom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -14967,7 +15303,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -14976,8 +15311,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/inquirer": { "version": "8.2.5", @@ -15371,6 +15705,15 @@ "tslib": "^2.0.3" } }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-nan": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", @@ -15478,6 +15821,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", @@ -15589,6 +15941,15 @@ "tslib": "^2.0.3" } }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -15601,6 +15962,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -16508,7 +16882,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.curry": { "version": "4.1.1", @@ -16718,6 +17093,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", @@ -16942,7 +17326,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -17461,7 +17844,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -17792,7 +18174,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -18133,15 +18514,15 @@ } }, "node_modules/prettier": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz", - "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz", + "integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -19919,6 +20300,18 @@ "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==", "dev": true }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/store2": { "version": "2.14.2", "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.2.tgz", @@ -21779,6 +22172,39 @@ "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", "dev": true }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -21820,6 +22246,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", @@ -21950,8 +22391,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/frontend/package.json b/frontend/package.json index 761d96c9..f72016c8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,7 +23,8 @@ "@urql/exchange-refocus": "^1.0.2", "@urql/exchange-request-policy": "^1.0.2", "@vector-im/compound-design-tokens": "^0.0.3", - "@vector-im/compound-web": "^0.2.3", + "@vector-im/compound-web": "^0.2.4", + "classnames": "^2.3.2", "date-fns": "^2.30.0", "graphql": "^16.7.1", "jotai": "^2.2.3", @@ -43,13 +44,14 @@ "@storybook/addon-controls": "^7.2.1", "@storybook/addon-docs": "^7.2.1", "@storybook/addon-essentials": "^7.2.1", - "@storybook/addon-measure": "^7.2.0", - "@storybook/addon-outline": "^7.2.0", - "@storybook/addon-toolbars": "^7.2.0", - "@storybook/addon-viewport": "^7.2.0", + "@storybook/addon-measure": "^7.2.1", + "@storybook/addon-outline": "^7.2.1", + "@storybook/addon-toolbars": "^7.2.1", + "@storybook/addon-viewport": "^7.2.1", "@storybook/react": "^7.2.1", "@storybook/react-vite": "^7.2.1", - "@types/node": "^20.4.6", + "@testing-library/react": "^14.0.0", + "@types/node": "^20.4.7", "@types/react": "^18.2.18", "@types/react-dom": "^18.2.7", "@types/react-test-renderer": "^18.0.0", @@ -57,14 +59,15 @@ "@vitest/coverage-v8": "^0.34.1", "autoprefixer": "^10.4.14", "eslint": "^8.46.0", - "eslint-config-prettier": "^8.9.0", + "eslint-config-prettier": "^8.10.0", "eslint-config-react-app": "^7.0.1", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.28.0", - "eslint-plugin-matrix-org": "^1.2.0", - "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-matrix-org": "^1.2.1", + "eslint-plugin-prettier": "^5.0.0", + "happy-dom": "^10.7.0", "postcss": "^8.4.27", - "prettier": "2.8.0", + "prettier": "3.0.1", "react-test-renderer": "^18.2.0", "rimraf": "^5.0.1", "storybook": "^7.2.1", diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx index 09365a96..a9557ec8 100644 --- a/frontend/src/Router.tsx +++ b/frontend/src/Router.tsx @@ -77,31 +77,35 @@ const routeToPath = (route: Route): string => .map((part) => encodeURIComponent(part)) .join("/"); +export const appConfigAtom = atom(window.APP_CONFIG); + const pathToRoute = (path: string): Route => { const segments = path.split("/").map(decodeURIComponent); return segmentsToRoute(segments); }; -const locationToRoute = (location: Location): Route => { - if ( - !location.pathname || - !location.pathname.startsWith(window.APP_CONFIG.root) - ) { - throw new Error("Invalid location"); +const locationToRoute = (root: string, location: Location): Route => { + if (!location.pathname || !location.pathname.startsWith(root)) { + throw new Error(`Invalid location ${location.pathname}`); } - const path = location.pathname.slice(window.APP_CONFIG.root.length); + const path = location.pathname.slice(root.length); return pathToRoute(path); }; -const locationAtom = atomWithLocation(); +export const locationAtom = atomWithLocation(); export const routeAtom = atom( - (get) => locationToRoute(get(locationAtom)), - (_get, set, value: Route) => { + (get) => { + const location = get(locationAtom); + const config = get(appConfigAtom); + return locationToRoute(config.root, location); + }, + (get, set, value: Route) => { + const appConfig = get(appConfigAtom); set(locationAtom, { - pathname: window.APP_CONFIG.root + routeToPath(value), + pathname: appConfig.root + routeToPath(value), }); - } + }, ); const Home = lazy(() => import("./pages/Home")); diff --git a/frontend/src/atoms.ts b/frontend/src/atoms.ts index 703626f4..7ecef58a 100644 --- a/frontend/src/atoms.ts +++ b/frontend/src/atoms.ts @@ -37,7 +37,7 @@ export type GqlAtom = WritableAtom< */ export const mapQueryAtom = ( queryAtom: AtomWithQuery, - mapper: (data: Data) => NewData + mapper: (data: Data) => NewData, ): GqlAtom => { return atom( async (get): Promise> => { @@ -55,7 +55,7 @@ export const mapQueryAtom = ( (_get, set, context) => { set(queryAtom, context); - } + }, ); }; @@ -90,7 +90,7 @@ export const currentUserIdAtom: GqlAtom = mapQueryAtom( return data.viewer.id; } return null; - } + }, ); const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ ` @@ -119,5 +119,5 @@ export const currentBrowserSessionIdAtom: GqlAtom = mapQueryAtom( return data.viewerSession.id; } return null; - } + }, ); diff --git a/frontend/src/components/Block.module.css b/frontend/src/components/Block/Block.module.css similarity index 100% rename from frontend/src/components/Block.module.css rename to frontend/src/components/Block/Block.module.css diff --git a/frontend/src/components/Block.stories.tsx b/frontend/src/components/Block/Block.stories.tsx similarity index 86% rename from frontend/src/components/Block.stories.tsx rename to frontend/src/components/Block/Block.stories.tsx index 9458da44..3e5d49f6 100644 --- a/frontend/src/components/Block.stories.tsx +++ b/frontend/src/components/Block/Block.stories.tsx @@ -1,4 +1,4 @@ -// Copyright 2022 The Matrix.org Foundation C.I.C. +// Copyright 2023 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,18 +13,13 @@ // limitations under the License. import type { Meta, StoryObj } from "@storybook/react"; +import { H1, H5, Body } from "@vector-im/compound-web"; import Block from "./Block"; -import { Title, Subtitle, Body } from "./Typography"; const meta = { title: "UI/Block", component: Block, - subcomponents: { - Title, - Subtitle, - Body, - } as Record>, tags: ["autodocs"], } satisfies Meta; @@ -34,8 +29,8 @@ type Story = StoryObj; export const Basic: Story = { render: (args) => ( - Title - Subtitle +

Title

+
Subtitle
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse diff --git a/frontend/src/components/Block/Block.test.tsx b/frontend/src/components/Block/Block.test.tsx new file mode 100644 index 00000000..8e9f6795 --- /dev/null +++ b/frontend/src/components/Block/Block.test.tsx @@ -0,0 +1,45 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { create } from "react-test-renderer"; +import { describe, expect, it } from "vitest"; + +import Block from "./Block"; + +describe("Block", () => { + it("render ", () => { + const component = create(); + expect(component.toJSON()).toMatchSnapshot(); + }); + + it("render with children", () => { + const component = create( + +

Title

+

Body

+
, + ); + expect(component.toJSON()).toMatchSnapshot(); + }); + + it("passes down the className prop", () => { + const component = create(); + expect(component.toJSON()).toMatchSnapshot(); + }); + + it("renders with highlight", () => { + const component = create(); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/frontend/src/components/Block.tsx b/frontend/src/components/Block/Block.tsx similarity index 75% rename from frontend/src/components/Block.tsx rename to frontend/src/components/Block/Block.tsx index 8292ad69..bc660248 100644 --- a/frontend/src/components/Block.tsx +++ b/frontend/src/components/Block/Block.tsx @@ -12,18 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +import cx from "classnames"; + import styles from "./Block.module.css"; -type Props = { +type Props = React.PropsWithChildren<{ + className?: string; highlight?: boolean; -}; +}>; -const Block: React.FC> = ({ - children, - highlight, -}) => { +const Block: React.FC = ({ children, className, highlight }) => { return ( -
+
{children}
); diff --git a/frontend/src/components/Block/__snapshots__/Block.test.tsx.snap b/frontend/src/components/Block/__snapshots__/Block.test.tsx.snap new file mode 100644 index 00000000..4e44ff56 --- /dev/null +++ b/frontend/src/components/Block/__snapshots__/Block.test.tsx.snap @@ -0,0 +1,33 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Block > passes down the className prop 1`] = ` +
+`; + +exports[`Block > render 1`] = ` +
+`; + +exports[`Block > render with children 1`] = ` +
+

+ Title +

+

+ Body +

+
+`; + +exports[`Block > renders with highlight 1`] = ` +
+`; diff --git a/frontend/src/components/Block/index.ts b/frontend/src/components/Block/index.ts new file mode 100644 index 00000000..8c55b5b6 --- /dev/null +++ b/frontend/src/components/Block/index.ts @@ -0,0 +1,15 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export { default } from "./Block"; diff --git a/frontend/src/components/BlockList.module.css b/frontend/src/components/BlockList/BlockList.module.css similarity index 93% rename from frontend/src/components/BlockList.module.css rename to frontend/src/components/BlockList/BlockList.module.css index 354eb3f8..dd28f6e9 100644 --- a/frontend/src/components/BlockList.module.css +++ b/frontend/src/components/BlockList/BlockList.module.css @@ -14,8 +14,8 @@ */ .block-list { - display: grid; - grid-template-columns: 1fr; + display: flex; + flex-direction: column; gap: var(--cpd-space-4x); align-content: flex-start; } \ No newline at end of file diff --git a/frontend/src/components/BlockList/BlockList.stories.tsx b/frontend/src/components/BlockList/BlockList.stories.tsx new file mode 100644 index 00000000..6316ed13 --- /dev/null +++ b/frontend/src/components/BlockList/BlockList.stories.tsx @@ -0,0 +1,44 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Meta, StoryObj } from "@storybook/react"; +import { H2, Body } from "@vector-im/compound-web"; + +import Block from "../Block"; + +import BlockList from "./BlockList"; + +const meta = { + title: "UI/Block List", + component: BlockList, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + render: (args) => ( + + +

Block 1

+ Body 1 +
+ +

Block 2

+ Body 2 +
+
+ ), +}; diff --git a/frontend/src/components/BlockList/BlockList.test.tsx b/frontend/src/components/BlockList/BlockList.test.tsx new file mode 100644 index 00000000..cc932689 --- /dev/null +++ b/frontend/src/components/BlockList/BlockList.test.tsx @@ -0,0 +1,42 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { create } from "react-test-renderer"; +import { describe, expect, it } from "vitest"; + +import Block from "../Block"; + +import BlockList from "./BlockList"; + +describe("BlockList", () => { + it("render an empty ", () => { + const component = create(); + expect(component.toJSON()).toMatchSnapshot(); + }); + + it("render with children", () => { + const component = create( + + Block 1 + Block 2 + , + ); + expect(component.toJSON()).toMatchSnapshot(); + }); + + it("passes down the className prop", () => { + const component = create(); + expect(component.toJSON()).toMatchSnapshot(); + }); +}); diff --git a/frontend/src/components/BlockList.tsx b/frontend/src/components/BlockList/BlockList.tsx similarity index 74% rename from frontend/src/components/BlockList.tsx rename to frontend/src/components/BlockList/BlockList.tsx index d458e1c3..3ab3fabf 100644 --- a/frontend/src/components/BlockList.tsx +++ b/frontend/src/components/BlockList/BlockList.tsx @@ -12,10 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +import cx from "classnames"; + import styles from "./BlockList.module.css"; -const BlockList: React.FC> = ({ children }) => { - return
{children}
; +type Props = React.PropsWithChildren<{ + className?: string; +}>; + +const BlockList: React.FC = ({ className, children }) => { + return
{children}
; }; export default BlockList; diff --git a/frontend/src/components/BlockList/__snapshots__/BlockList.test.tsx.snap b/frontend/src/components/BlockList/__snapshots__/BlockList.test.tsx.snap new file mode 100644 index 00000000..795ea558 --- /dev/null +++ b/frontend/src/components/BlockList/__snapshots__/BlockList.test.tsx.snap @@ -0,0 +1,30 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`BlockList > passes down the className prop 1`] = ` +
+`; + +exports[`BlockList > render with children 1`] = ` +
+
+ Block 1 +
+
+ Block 2 +
+
+`; + +exports[`BlockList > render an empty 1`] = ` +
+`; diff --git a/frontend/src/components/BlockList/index.ts b/frontend/src/components/BlockList/index.ts new file mode 100644 index 00000000..a29bf292 --- /dev/null +++ b/frontend/src/components/BlockList/index.ts @@ -0,0 +1,15 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export { default } from "./BlockList"; diff --git a/frontend/src/components/BrowserSession.tsx b/frontend/src/components/BrowserSession.tsx index e1f58a58..f150b887 100644 --- a/frontend/src/components/BrowserSession.tsx +++ b/frontend/src/components/BrowserSession.tsx @@ -54,7 +54,7 @@ const endSessionFamily = atomFamily((id: string) => { // A proxy atom which pre-sets the id variable in the mutation const endSessionAtom = atom( (get) => get(endSession), - (get, set) => set(endSession, { id }) + (get, set) => set(endSession, { id }), ); return endSessionAtom; diff --git a/frontend/src/components/BrowserSessionList.tsx b/frontend/src/components/BrowserSessionList.tsx index dae867af..2bb1190c 100644 --- a/frontend/src/components/BrowserSessionList.tsx +++ b/frontend/src/components/BrowserSessionList.tsx @@ -81,7 +81,7 @@ const browserSessionListFamily = atomFamily((userId: string) => { const browserSessionList = mapQueryAtom( browserSessionListQuery, - (data) => data.user?.browserSessions || null + (data) => data.user?.browserSessions || null, ); return browserSessionList; @@ -98,7 +98,7 @@ const pageInfoFamily = atomFamily((userId: string) => { const paginationFamily = atomFamily((userId: string) => { const paginationAtom = atomWithPagination( currentPaginationAtom, - pageInfoFamily(userId) + pageInfoFamily(userId), ); return paginationAtom; diff --git a/frontend/src/components/CompatSession.tsx b/frontend/src/components/CompatSession.tsx index b76c22e0..b92975c2 100644 --- a/frontend/src/components/CompatSession.tsx +++ b/frontend/src/components/CompatSession.tsx @@ -62,7 +62,7 @@ const endCompatSessionFamily = atomFamily((id: string) => { // A proxy atom which pre-sets the id variable in the mutation const endCompatSessionAtom = atom( (get) => get(endCompatSession), - (get, set) => set(endCompatSession, { id }) + (get, set) => set(endCompatSession, { id }), ); return endCompatSessionAtom; diff --git a/frontend/src/components/CompatSessionList.tsx b/frontend/src/components/CompatSessionList.tsx index 702c3a98..bff10d50 100644 --- a/frontend/src/components/CompatSessionList.tsx +++ b/frontend/src/components/CompatSessionList.tsx @@ -78,7 +78,7 @@ const compatSessionListFamily = atomFamily((userId: string) => { const compatSessionList = mapQueryAtom( compatSessionListQuery, - (data) => data.user?.compatSessions || null + (data) => data.user?.compatSessions || null, ); return compatSessionList; @@ -96,7 +96,7 @@ const pageInfoFamily = atomFamily((userId: string) => { const paginationFamily = atomFamily((userId: string) => { const paginationAtom = atomWithPagination( currentPaginationAtom, - pageInfoFamily(userId) + pageInfoFamily(userId), ); return paginationAtom; }); diff --git a/frontend/src/components/LoadingScreen/LoadingScreen.module.css b/frontend/src/components/LoadingScreen/LoadingScreen.module.css new file mode 100644 index 00000000..8ee99980 --- /dev/null +++ b/frontend/src/components/LoadingScreen/LoadingScreen.module.css @@ -0,0 +1,21 @@ +/* Copyright 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.loading-screen { + display: flex; + min-height: 100vh; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/frontend/src/components/LoadingScreen.stories.tsx b/frontend/src/components/LoadingScreen/LoadingScreen.stories.tsx similarity index 95% rename from frontend/src/components/LoadingScreen.stories.tsx rename to frontend/src/components/LoadingScreen/LoadingScreen.stories.tsx index 2ee5baa8..9c121c90 100644 --- a/frontend/src/components/LoadingScreen.stories.tsx +++ b/frontend/src/components/LoadingScreen/LoadingScreen.stories.tsx @@ -19,6 +19,9 @@ import LoadingScreen from "./LoadingScreen"; const meta = { title: "UI/Loading Screen", component: LoadingScreen, + parameters: { + layout: "fullscreen", + }, tags: ["autodocs"], } satisfies Meta; diff --git a/frontend/src/components/LoadingScreen.test.tsx b/frontend/src/components/LoadingScreen/LoadingScreen.test.tsx similarity index 73% rename from frontend/src/components/LoadingScreen.test.tsx rename to frontend/src/components/LoadingScreen/LoadingScreen.test.tsx index b6121365..1f6b4bfb 100644 --- a/frontend/src/components/LoadingScreen.test.tsx +++ b/frontend/src/components/LoadingScreen/LoadingScreen.test.tsx @@ -13,12 +13,14 @@ // limitations under the License. import { create } from "react-test-renderer"; -import { expect, it } from "vitest"; +import { describe, expect, it } from "vitest"; import LoadingScreen from "./LoadingScreen"; -it("render ", () => { - const component = create(); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); +describe("LoadingScreen", () => { + it("render ", () => { + const component = create(); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); }); diff --git a/frontend/src/components/LoadingScreen.tsx b/frontend/src/components/LoadingScreen/LoadingScreen.tsx similarity index 83% rename from frontend/src/components/LoadingScreen.tsx rename to frontend/src/components/LoadingScreen/LoadingScreen.tsx index 5503d89a..b648b5d9 100644 --- a/frontend/src/components/LoadingScreen.tsx +++ b/frontend/src/components/LoadingScreen/LoadingScreen.tsx @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import LoadingSpinner from "./LoadingSpinner"; +import LoadingSpinner from "../LoadingSpinner"; + +import styles from "./LoadingScreen.module.css"; const LoadingScreen: React.FC = () => ( -
+
); diff --git a/frontend/src/components/LoadingScreen/__snapshots__/LoadingScreen.test.tsx.snap b/frontend/src/components/LoadingScreen/__snapshots__/LoadingScreen.test.tsx.snap new file mode 100644 index 00000000..59194494 --- /dev/null +++ b/frontend/src/components/LoadingScreen/__snapshots__/LoadingScreen.test.tsx.snap @@ -0,0 +1,28 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`LoadingScreen > render 1`] = ` +
+
+ + + + + Loading... + +
+
+`; diff --git a/frontend/src/components/LoadingScreen/index.ts b/frontend/src/components/LoadingScreen/index.ts new file mode 100644 index 00000000..0945efbf --- /dev/null +++ b/frontend/src/components/LoadingScreen/index.ts @@ -0,0 +1,15 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export { default } from "./LoadingScreen"; diff --git a/frontend/src/components/LoadingSpinner/LoadingSpinner.module.css b/frontend/src/components/LoadingSpinner/LoadingSpinner.module.css new file mode 100644 index 00000000..9eb3364d --- /dev/null +++ b/frontend/src/components/LoadingSpinner/LoadingSpinner.module.css @@ -0,0 +1,28 @@ +/* Copyright 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.loading-spinner-inner { + height: var(--cpd-space-16x); + width: var(--cpd-space-16x); + display: inline; + animation: spin 1s linear infinite; + fill: var(--cpd-color-icon-primary); +} diff --git a/frontend/src/components/LoadingSpinner.stories.tsx b/frontend/src/components/LoadingSpinner/LoadingSpinner.stories.tsx similarity index 100% rename from frontend/src/components/LoadingSpinner.stories.tsx rename to frontend/src/components/LoadingSpinner/LoadingSpinner.stories.tsx diff --git a/frontend/src/components/LoadingSpinner.tsx b/frontend/src/components/LoadingSpinner/LoadingSpinner.tsx similarity index 94% rename from frontend/src/components/LoadingSpinner.tsx rename to frontend/src/components/LoadingSpinner/LoadingSpinner.tsx index 75985c89..684e36b8 100644 --- a/frontend/src/components/LoadingSpinner.tsx +++ b/frontend/src/components/LoadingSpinner/LoadingSpinner.tsx @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +import styles from "./LoadingSpinner.module.css"; + const LoadingSpinner: React.FC = () => (
( + + + + Home + Account + External + + + + ), +} satisfies Meta; + +const WithHomePage: React.FC> = ({ children }) => { + useHydrateAtoms([ + [appConfigAtom, { root: "/" }], + [locationAtom, { pathname: "/" }], + ]); + return <>{children}; +}; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = {}; diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar/NavBar.tsx similarity index 100% rename from frontend/src/components/NavBar.tsx rename to frontend/src/components/NavBar/NavBar.tsx diff --git a/frontend/src/components/NavBar/index.ts b/frontend/src/components/NavBar/index.ts new file mode 100644 index 00000000..4ae8b470 --- /dev/null +++ b/frontend/src/components/NavBar/index.ts @@ -0,0 +1,15 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export { default } from "./NavBar"; diff --git a/frontend/src/components/NavItem.module.css b/frontend/src/components/NavItem/NavItem.module.css similarity index 100% rename from frontend/src/components/NavItem.module.css rename to frontend/src/components/NavItem/NavItem.module.css diff --git a/frontend/src/components/NavItem/NavItem.stories.tsx b/frontend/src/components/NavItem/NavItem.stories.tsx new file mode 100644 index 00000000..b73beabf --- /dev/null +++ b/frontend/src/components/NavItem/NavItem.stories.tsx @@ -0,0 +1,59 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import type { Meta, StoryObj } from "@storybook/react"; +import { Provider } from "jotai"; +import { useHydrateAtoms } from "jotai/utils"; + +import { appConfigAtom, locationAtom } from "../../Router"; + +import NavItem from "./NavItem"; + +const meta = { + title: "UI/Nav Item", + component: NavItem, + tags: ["autodocs"], + render: (props): React.ReactElement => ( + + + + + + ), +} satisfies Meta; + +const WithHomePage: React.FC> = ({ children }) => { + useHydrateAtoms([ + [appConfigAtom, { root: "/" }], + [locationAtom, { pathname: "/" }], + ]); + return <>{children}; +}; + +export default meta; +type Story = StoryObj; + +export const Active: Story = { + args: { + route: { type: "home" }, + children: "Home", + }, +}; + +export const Inactive: Story = { + args: { + route: { type: "account" }, + children: "Account", + }, +}; diff --git a/frontend/src/components/NavItem/NavItem.test.tsx b/frontend/src/components/NavItem/NavItem.test.tsx new file mode 100644 index 00000000..a2da8f60 --- /dev/null +++ b/frontend/src/components/NavItem/NavItem.test.tsx @@ -0,0 +1,86 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// @vitest-environment happy-dom + +import { Provider } from "jotai"; +import { useHydrateAtoms } from "jotai/utils"; +import { create } from "react-test-renderer"; +import { beforeEach, describe, expect, it } from "vitest"; + +import { appConfigAtom, locationAtom } from "../../Router"; + +import NavItem from "./NavItem"; + +beforeEach(async () => { + // For some reason, the locationAtom gets updated with `about:black` on render, + // so we need to set a "real" location and wait for the next tick + window.location.assign("https://example.com/"); + // Wait the next tick for the location to update + await new Promise((resolve) => setTimeout(resolve, 0)); +}); + +const HydrateLocation: React.FC> = ({ + children, + path, +}) => { + useHydrateAtoms([ + [appConfigAtom, { root: "/" }], + [locationAtom, { pathname: path }], + ]); + return <>{children}; +}; + +const WithLocation: React.FC> = ({ + children, + path, +}) => { + return ( + + {children} + + ); +}; + +describe("NavItem", () => { + it("render an active ", () => { + const component = create( + + Active + , + ); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + it("render an inactive ", () => { + const component = create( + + Inactive + , + ); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + it("renders a different route", () => { + const component = create( + + Account + , + ); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/frontend/src/components/NavItem.tsx b/frontend/src/components/NavItem/NavItem.tsx similarity index 96% rename from frontend/src/components/NavItem.tsx rename to frontend/src/components/NavItem/NavItem.tsx index 806f90ca..d36f6940 100644 --- a/frontend/src/components/NavItem.tsx +++ b/frontend/src/components/NavItem/NavItem.tsx @@ -15,7 +15,7 @@ import { Link as CpdLink } from "@vector-im/compound-web"; import { useAtomValue } from "jotai"; -import { Link, Route, routeAtom } from "../Router"; +import { Link, Route, routeAtom } from "../../Router"; import styles from "./NavItem.module.css"; diff --git a/frontend/src/components/NavItem/__snapshots__/NavItem.test.tsx.snap b/frontend/src/components/NavItem/__snapshots__/NavItem.test.tsx.snap new file mode 100644 index 00000000..b987281c --- /dev/null +++ b/frontend/src/components/NavItem/__snapshots__/NavItem.test.tsx.snap @@ -0,0 +1,38 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`NavItem > render an active 1`] = ` +
  • + + Active + +
  • +`; + +exports[`NavItem > render an inactive 1`] = ` +
  • + + Inactive + +
  • +`; + +exports[`NavItem > renders a different route 1`] = ` +
  • + + Account + +
  • +`; diff --git a/frontend/src/components/NavItem/index.ts b/frontend/src/components/NavItem/index.ts new file mode 100644 index 00000000..6d7b5b6b --- /dev/null +++ b/frontend/src/components/NavItem/index.ts @@ -0,0 +1,15 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export { default, ExternalLink } from "./NavItem"; diff --git a/frontend/src/components/OAuth2Session.tsx b/frontend/src/components/OAuth2Session.tsx index ed2fb046..1e529041 100644 --- a/frontend/src/components/OAuth2Session.tsx +++ b/frontend/src/components/OAuth2Session.tsx @@ -58,7 +58,7 @@ const endSessionFamily = atomFamily((id: string) => { // A proxy atom which pre-sets the id variable in the mutation const endSessionAtom = atom( (get) => get(endSession), - (get, set) => set(endSession, { id }) + (get, set) => set(endSession, { id }), ); return endSessionAtom; diff --git a/frontend/src/components/OAuth2SessionList.tsx b/frontend/src/components/OAuth2SessionList.tsx index 5f5a43ef..b48d76e1 100644 --- a/frontend/src/components/OAuth2SessionList.tsx +++ b/frontend/src/components/OAuth2SessionList.tsx @@ -79,7 +79,7 @@ const oauth2SessionListFamily = atomFamily((userId: string) => { const oauth2SessionList = mapQueryAtom( oauth2SessionListQuery, - (data) => data.user?.oauth2Sessions || null + (data) => data.user?.oauth2Sessions || null, ); return oauth2SessionList; @@ -97,7 +97,7 @@ const pageInfoFamily = atomFamily((userId: string) => { const paginationFamily = atomFamily((userId: string) => { const paginationAtom = atomWithPagination( currentPaginationAtom, - pageInfoFamily(userId) + pageInfoFamily(userId), ); return paginationAtom; }); diff --git a/frontend/src/components/UserEmail.tsx b/frontend/src/components/UserEmail.tsx index 8979636b..1d3f8f7b 100644 --- a/frontend/src/components/UserEmail.tsx +++ b/frontend/src/components/UserEmail.tsx @@ -116,7 +116,7 @@ const verifyEmailFamily = atomFamily((id: string) => { // A proxy atom which pre-sets the id variable in the mutation const verifyEmailAtom = atom( (get) => get(verifyEmail), - (get, set, code: string) => set(verifyEmail, { id, code }) + (get, set, code: string) => set(verifyEmail, { id, code }), ); return verifyEmailAtom; @@ -124,13 +124,13 @@ const verifyEmailFamily = atomFamily((id: string) => { const resendVerificationEmailFamily = atomFamily((id: string) => { const resendVerificationEmail = atomWithMutation( - RESEND_VERIFICATION_EMAIL_MUTATION + RESEND_VERIFICATION_EMAIL_MUTATION, ); // A proxy atom which pre-sets the id variable in the mutation const resendVerificationEmailAtom = atom( (get) => get(resendVerificationEmail), - (get, set) => set(resendVerificationEmail, { id }) + (get, set) => set(resendVerificationEmail, { id }), ); return resendVerificationEmailAtom; @@ -142,7 +142,7 @@ const removeEmailFamily = atomFamily((id: string) => { // A proxy atom which pre-sets the id variable in the mutation const removeEmailAtom = atom( (get) => get(removeEmail), - (get, set) => set(removeEmail, { id }) + (get, set) => set(removeEmail, { id }), ); return removeEmailAtom; @@ -154,7 +154,7 @@ const setPrimaryEmailFamily = atomFamily((id: string) => { // A proxy atom which pre-sets the id variable in the mutation const setPrimaryEmailAtom = atom( (get) => get(setPrimaryEmail), - (get, set) => set(setPrimaryEmail, { id }) + (get, set) => set(setPrimaryEmail, { id }), ); return setPrimaryEmailAtom; @@ -171,7 +171,7 @@ const UserEmail: React.FC<{ const data = useFragment(FRAGMENT, email); const [verifyEmailResult, verifyEmail] = useAtom(verifyEmailFamily(data.id)); const [resendVerificationEmailResult, resendVerificationEmail] = useAtom( - resendVerificationEmailFamily(data.id) + resendVerificationEmailFamily(data.id), ); const setPrimaryEmail = useSetAtom(setPrimaryEmailFamily(data.id)); const removeEmail = useSetAtom(removeEmailFamily(data.id)); diff --git a/frontend/src/components/UserEmailList.tsx b/frontend/src/components/UserEmailList.tsx index 92d0c670..1b20abda 100644 --- a/frontend/src/components/UserEmailList.tsx +++ b/frontend/src/components/UserEmailList.tsx @@ -90,7 +90,7 @@ const primaryEmailIdFamily = atomFamily((userId: string) => { }, (get, set) => { set(primaryEmailResultFamily(userId)); - } + }, ); return primaryEmailIdAtom; @@ -118,7 +118,7 @@ const pageInfoFamily = atomFamily((userId: string) => { const paginationFamily = atomFamily((userId: string) => { const paginationAtom = atomWithPagination( currentPaginationAtom, - pageInfoFamily(userId) + pageInfoFamily(userId), ); return paginationAtom; }); @@ -131,7 +131,7 @@ const UserEmailList: React.FC<{ const setPagination = useSetAtom(currentPaginationAtom); const [prevPage, nextPage] = useAtomValue(paginationFamily(userId)); const [primaryEmailId, refreshPrimaryEmailId] = useAtom( - primaryEmailIdFamily(userId) + primaryEmailIdFamily(userId), ); // XXX: we may not want to directly use that atom here, but rather have a local state const latestAddedEmail = useAtomValue(latestAddedEmailAtom); diff --git a/frontend/src/gql/fragment-masking.ts b/frontend/src/gql/fragment-masking.ts index 4a5accd2..373a5ce4 100644 --- a/frontend/src/gql/fragment-masking.ts +++ b/frontend/src/gql/fragment-masking.ts @@ -7,7 +7,7 @@ import { FragmentDefinitionNode } from "graphql"; import { Incremental } from "./graphql"; export type FragmentType< - TDocumentType extends DocumentTypeDecoration + TDocumentType extends DocumentTypeDecoration, > = TDocumentType extends DocumentTypeDecoration ? [TType] extends [{ " $fragmentName"?: infer TKey }] ? TKey extends string @@ -19,7 +19,7 @@ export type FragmentType< // return non-nullable if `fragmentType` is non-nullable export function useFragment( _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> + fragmentType: FragmentType>, ): TType; // return nullable if `fragmentType` is nullable export function useFragment( @@ -27,12 +27,12 @@ export function useFragment( fragmentType: | FragmentType> | null - | undefined + | undefined, ): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable export function useFragment( _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> + fragmentType: ReadonlyArray>>, ): ReadonlyArray; // return array of nullable if `fragmentType` is array of nullable export function useFragment( @@ -40,7 +40,7 @@ export function useFragment( fragmentType: | ReadonlyArray>> | null - | undefined + | undefined, ): ReadonlyArray | null | undefined; export function useFragment( _documentNode: DocumentTypeDecoration, @@ -48,14 +48,14 @@ export function useFragment( | FragmentType> | ReadonlyArray>> | null - | undefined + | undefined, ): TType | ReadonlyArray | null | undefined { return fragmentType as any; } export function makeFragmentData< F extends DocumentTypeDecoration, - FT extends ResultOf + FT extends ResultOf, >(data: FT, _fragment: F): FragmentType { return data as FragmentType; } @@ -65,7 +65,7 @@ export function isFragmentReady( data: | FragmentType, any>> | null - | undefined + | undefined, ): data is FragmentType { const deferredFields = ( queryNode as { diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts index 97e76512..128944e3 100644 --- a/frontend/src/gql/gql.ts +++ b/frontend/src/gql/gql.ts @@ -79,140 +79,140 @@ export function graphql(source: string): unknown; * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n" -): typeof documents["\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"]; + source: "\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n", +): (typeof documents)["\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n" -): typeof documents["\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"]; + source: "\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n", +): (typeof documents)["\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n" -): typeof documents["\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"]; + source: "\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n", +): (typeof documents)["\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n" -): typeof documents["\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"]; + source: "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n", +): (typeof documents)["\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n" -): typeof documents["\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n"]; + source: "\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n", +): (typeof documents)["\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n" -): typeof documents["\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; + source: "\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n", +): (typeof documents)["\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n fragment CompatSession_sso_login on CompatSsoLogin {\n id\n redirectUri\n }\n" -): typeof documents["\n fragment CompatSession_sso_login on CompatSsoLogin {\n id\n redirectUri\n }\n"]; + source: "\n fragment CompatSession_sso_login on CompatSsoLogin {\n id\n redirectUri\n }\n", +): (typeof documents)["\n fragment CompatSession_sso_login on CompatSsoLogin {\n id\n redirectUri\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n ssoLogin {\n id\n ...CompatSession_sso_login\n }\n }\n" -): typeof documents["\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n ssoLogin {\n id\n ...CompatSession_sso_login\n }\n }\n"]; + source: "\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n ssoLogin {\n id\n ...CompatSession_sso_login\n }\n }\n", +): (typeof documents)["\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n ssoLogin {\n id\n ...CompatSession_sso_login\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n finishedAt\n }\n }\n }\n" -): typeof documents["\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n finishedAt\n }\n }\n }\n"]; + source: "\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n finishedAt\n }\n }\n }\n", +): (typeof documents)["\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n finishedAt\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query CompatSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSession_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n" -): typeof documents["\n query CompatSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSession_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; + source: "\n query CompatSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSession_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n", +): (typeof documents)["\n query CompatSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSession_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n" -): typeof documents["\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n"]; + source: "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n", +): (typeof documents)["\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n" -): typeof documents["\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n"]; + source: "\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n", +): (typeof documents)["\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n" -): typeof documents["\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; + source: "\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n", +): (typeof documents)["\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n" -): typeof documents["\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n"]; + source: "\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n", +): (typeof documents)["\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n" -): typeof documents["\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"]; + source: "\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n", +): (typeof documents)["\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n" -): typeof documents["\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"]; + source: "\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n", +): (typeof documents)["\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n mutation RemoveEmail($id: ID!) {\n removeEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n }\n }\n }\n" -): typeof documents["\n mutation RemoveEmail($id: ID!) {\n removeEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n }\n }\n }\n"]; + source: "\n mutation RemoveEmail($id: ID!) {\n removeEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n }\n }\n }\n", +): (typeof documents)["\n mutation RemoveEmail($id: ID!) {\n removeEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n mutation SetPrimaryEmail($id: ID!) {\n setPrimaryEmail(input: { userEmailId: $id }) {\n status\n user {\n id\n primaryEmail {\n id\n }\n }\n }\n }\n" -): typeof documents["\n mutation SetPrimaryEmail($id: ID!) {\n setPrimaryEmail(input: { userEmailId: $id }) {\n status\n user {\n id\n primaryEmail {\n id\n }\n }\n }\n }\n"]; + source: "\n mutation SetPrimaryEmail($id: ID!) {\n setPrimaryEmail(input: { userEmailId: $id }) {\n status\n user {\n id\n primaryEmail {\n id\n }\n }\n }\n }\n", +): (typeof documents)["\n mutation SetPrimaryEmail($id: ID!) {\n setPrimaryEmail(input: { userEmailId: $id }) {\n status\n user {\n id\n primaryEmail {\n id\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n" -): typeof documents["\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; + source: "\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n", +): (typeof documents)["\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\n id\n }\n }\n }\n" -): typeof documents["\n query UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\n id\n }\n }\n }\n"]; + source: "\n query UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\n id\n }\n }\n }\n", +): (typeof documents)["\n query UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\n id\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n matrix {\n mxid\n displayName\n }\n }\n }\n" -): typeof documents["\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n matrix {\n mxid\n displayName\n }\n }\n }\n"]; + source: "\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n matrix {\n mxid\n displayName\n }\n }\n }\n", +): (typeof documents)["\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n matrix {\n mxid\n displayName\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n" -): typeof documents["\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"]; + source: "\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n", +): (typeof documents)["\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n" -): typeof documents["\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n"]; + source: "\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n", +): (typeof documents)["\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n"]; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index f48966b2..1d27c5cb 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -13,7 +13,7 @@ export type MakeMaybe = Omit & { }; export type MakeEmpty< T extends { [key: string]: unknown }, - K extends keyof T + K extends keyof T, > = { [_ in K]?: never }; export type Incremental = | T diff --git a/frontend/src/graphql.ts b/frontend/src/graphql.ts index 01f45e48..1e796f8c 100644 --- a/frontend/src/graphql.ts +++ b/frontend/src/graphql.ts @@ -52,7 +52,7 @@ const cache = cacheExchange({ result: { removeEmail?: RemoveEmailPayload }, args: MutationRemoveEmailArgs, cache, - _info + _info, ) => { // Invalidate the email entity cache.invalidate({ diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 933aa0e1..c9bc03f7 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -32,5 +32,5 @@ createRoot(document.getElementById("root") as HTMLElement).render( - + , ); diff --git a/frontend/src/pages/BrowserSession.tsx b/frontend/src/pages/BrowserSession.tsx index 07ac03d3..2b1a958e 100644 --- a/frontend/src/pages/BrowserSession.tsx +++ b/frontend/src/pages/BrowserSession.tsx @@ -47,7 +47,7 @@ const browserSessionFamily = atomFamily((id: string) => { const browserSessionAtom = mapQueryAtom( browserSessionQueryAtom, - (data) => data?.browserSession + (data) => data?.browserSession, ); return browserSessionAtom; diff --git a/frontend/src/pages/OAuth2Client.tsx b/frontend/src/pages/OAuth2Client.tsx index 8ea92a3c..656e2681 100644 --- a/frontend/src/pages/OAuth2Client.tsx +++ b/frontend/src/pages/OAuth2Client.tsx @@ -44,7 +44,7 @@ const oauth2ClientFamily = atomFamily((id: string) => { const oauth2ClientAtom = mapQueryAtom( oauth2ClientQueryAtom, - (data) => data?.oauth2Client + (data) => data?.oauth2Client, ); return oauth2ClientAtom; diff --git a/frontend/src/pagination.ts b/frontend/src/pagination.ts index c9d415c9..090acd0d 100644 --- a/frontend/src/pagination.ts +++ b/frontend/src/pagination.ts @@ -34,14 +34,14 @@ export type Pagination = ForwardPagination | BackwardPagination; // Check if the pagination is forward pagination. export const isForwardPagination = ( - pagination: Pagination + pagination: Pagination, ): pagination is ForwardPagination => { return pagination.hasOwnProperty("first"); }; // Check if the pagination is backward pagination. export const isBackwardPagination = ( - pagination: Pagination + pagination: Pagination, ): pagination is BackwardPagination => { return pagination.hasOwnProperty("last"); }; @@ -81,7 +81,7 @@ export const atomForCurrentPagination = (): WritableAtom< } else { set(dataAtom, action); } - } + }, ); currentPaginationAtom.onMount = (setAtom): void => { @@ -95,11 +95,11 @@ export const atomForCurrentPagination = (): WritableAtom< // next pagination objects, given the current pagination and the page info. export const atomWithPagination = ( currentPaginationAtom: Atom, - pageInfoAtom: Atom> + pageInfoAtom: Atom>, ): Atom> => { const paginationAtom = atom( async ( - get + get, ): Promise<[BackwardPagination | null, ForwardPagination | null]> => { const currentPagination = get(currentPaginationAtom); const pageInfo = await get(pageInfoAtom); @@ -127,7 +127,7 @@ export const atomWithPagination = ( } return [previousPagination, nextPagination]; - } + }, ); return paginationAtom;