You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-31 09:24:31 +03:00
Frontend cleanups
Mainly: - better handling of GraphQL errors - better logout state - dependencies update - a way to end browser sessions in the GraphQL API
This commit is contained in:
103
crates/graphql/src/mutations/browser_session.rs
Normal file
103
crates/graphql/src/mutations/browser_session.rs
Normal file
@ -0,0 +1,103 @@
|
||||
// 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.
|
||||
|
||||
use anyhow::Context as _;
|
||||
use async_graphql::{Context, Enum, InputObject, Object, ID};
|
||||
use mas_storage::RepositoryAccess;
|
||||
|
||||
use crate::{
|
||||
model::{BrowserSession, NodeType},
|
||||
state::ContextExt,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BrowserSessionMutations {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
/// The input of the `endBrowserSession` mutation.
|
||||
#[derive(InputObject)]
|
||||
pub struct EndBrowserSessionInput {
|
||||
/// The ID of the session to end.
|
||||
browser_session_id: ID,
|
||||
}
|
||||
|
||||
/// The payload of the `endBrowserSession` mutation.
|
||||
pub enum EndBrowserSessionPayload {
|
||||
NotFound,
|
||||
Ended(mas_data_model::BrowserSession),
|
||||
}
|
||||
|
||||
/// The status of the `endBrowserSession` mutation.
|
||||
#[derive(Enum, Copy, Clone, PartialEq, Eq, Debug)]
|
||||
enum EndBrowserSessionStatus {
|
||||
/// The session was ended.
|
||||
Ended,
|
||||
|
||||
/// The session was not found.
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl EndBrowserSessionPayload {
|
||||
/// The status of the mutation.
|
||||
async fn status(&self) -> EndBrowserSessionStatus {
|
||||
match self {
|
||||
Self::Ended(_) => EndBrowserSessionStatus::Ended,
|
||||
Self::NotFound => EndBrowserSessionStatus::NotFound,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the ended session.
|
||||
async fn browser_session(&self) -> Option<BrowserSession> {
|
||||
match self {
|
||||
Self::Ended(session) => Some(BrowserSession(session.clone())),
|
||||
Self::NotFound => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl BrowserSessionMutations {
|
||||
async fn end_browser_session(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
input: EndBrowserSessionInput,
|
||||
) -> Result<EndBrowserSessionPayload, async_graphql::Error> {
|
||||
let state = ctx.state();
|
||||
let browser_session_id =
|
||||
NodeType::BrowserSession.extract_ulid(&input.browser_session_id)?;
|
||||
let requester = ctx.requester();
|
||||
|
||||
let user = requester.user().context("Unauthorized")?;
|
||||
|
||||
let mut repo = state.repository().await?;
|
||||
let clock = state.clock();
|
||||
|
||||
let session = repo.browser_session().lookup(browser_session_id).await?;
|
||||
let Some(session) = session else {
|
||||
return Ok(EndBrowserSessionPayload::NotFound);
|
||||
};
|
||||
|
||||
if session.user.id != user.id {
|
||||
return Err(async_graphql::Error::new("Unauthorized"));
|
||||
}
|
||||
|
||||
let session = repo.browser_session().finish(&clock, session).await?;
|
||||
|
||||
repo.save().await?;
|
||||
|
||||
Ok(EndBrowserSessionPayload::Ended(session))
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod browser_session;
|
||||
mod compat_session;
|
||||
mod oauth2_session;
|
||||
mod user_email;
|
||||
@ -24,6 +25,7 @@ pub struct Mutation(
|
||||
user_email::UserEmailMutations,
|
||||
oauth2_session::OAuth2SessionMutations,
|
||||
compat_session::CompatSessionMutations,
|
||||
browser_session::BrowserSessionMutations,
|
||||
);
|
||||
|
||||
impl Mutation {
|
||||
|
355
frontend/package-lock.json
generated
355
frontend/package-lock.json
generated
@ -22,7 +22,8 @@
|
||||
"jotai-location": "^0.5.1",
|
||||
"jotai-urql": "^0.7.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"vite-plugin-svgr": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^4.0.1",
|
||||
@ -95,7 +96,6 @@
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
|
||||
"integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
@ -344,7 +344,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz",
|
||||
"integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -353,7 +352,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz",
|
||||
"integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
@ -418,7 +416,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz",
|
||||
"integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
@ -457,7 +454,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz",
|
||||
"integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.5",
|
||||
"@babel/helper-validator-option": "^7.22.5",
|
||||
@ -533,7 +529,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
|
||||
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -542,7 +537,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
|
||||
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/types": "^7.22.5"
|
||||
@ -555,7 +549,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
@ -590,7 +583,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz",
|
||||
"integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
"@babel/helper-module-imports": "^7.22.5",
|
||||
@ -665,7 +657,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
|
||||
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
@ -689,7 +680,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz",
|
||||
"integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
},
|
||||
@ -717,7 +707,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
|
||||
"integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -741,7 +730,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz",
|
||||
"integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/traverse": "^7.22.5",
|
||||
@ -768,7 +756,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz",
|
||||
"integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@ -2610,7 +2597,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
|
||||
"integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/parser": "^7.22.5",
|
||||
@ -2624,7 +2610,6 @@
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz",
|
||||
"integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/generator": "^7.22.5",
|
||||
@ -2795,7 +2780,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@ -2811,7 +2795,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@ -2827,7 +2810,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@ -2843,7 +2825,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@ -2859,7 +2840,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@ -2875,7 +2855,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
@ -2891,7 +2870,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
@ -2907,7 +2885,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -2923,7 +2900,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -2939,7 +2915,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -2955,7 +2930,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -2971,7 +2945,6 @@
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -2987,7 +2960,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -3003,7 +2975,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -3019,7 +2990,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -3035,7 +3005,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -3051,7 +3020,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
@ -3067,7 +3035,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
@ -3083,7 +3050,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
@ -3099,7 +3065,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@ -3115,7 +3080,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@ -3131,7 +3095,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@ -5170,7 +5133,6 @@
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
@ -5184,7 +5146,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@ -5193,7 +5154,6 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@ -5201,14 +5161,12 @@
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
|
||||
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
@ -5217,8 +5175,7 @@
|
||||
"node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
||||
},
|
||||
"node_modules/@juggle/resize-observer": {
|
||||
"version": "3.4.0",
|
||||
@ -7572,6 +7529,225 @@
|
||||
"url": "https://opencollective.com/storybook"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-7.0.0.tgz",
|
||||
"integrity": "sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-7.0.0.tgz",
|
||||
"integrity": "sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-7.0.0.tgz",
|
||||
"integrity": "sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-7.0.0.tgz",
|
||||
"integrity": "sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-svg-dynamic-title": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-7.0.0.tgz",
|
||||
"integrity": "sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-svg-em-dimensions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-7.0.0.tgz",
|
||||
"integrity": "sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-transform-react-native-svg": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-7.0.0.tgz",
|
||||
"integrity": "sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-transform-svg-component": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-7.0.0.tgz",
|
||||
"integrity": "sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-preset": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-7.0.0.tgz",
|
||||
"integrity": "sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==",
|
||||
"dependencies": {
|
||||
"@svgr/babel-plugin-add-jsx-attribute": "^7.0.0",
|
||||
"@svgr/babel-plugin-remove-jsx-attribute": "^7.0.0",
|
||||
"@svgr/babel-plugin-remove-jsx-empty-expression": "^7.0.0",
|
||||
"@svgr/babel-plugin-replace-jsx-attribute-value": "^7.0.0",
|
||||
"@svgr/babel-plugin-svg-dynamic-title": "^7.0.0",
|
||||
"@svgr/babel-plugin-svg-em-dimensions": "^7.0.0",
|
||||
"@svgr/babel-plugin-transform-react-native-svg": "^7.0.0",
|
||||
"@svgr/babel-plugin-transform-svg-component": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/core": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-7.0.0.tgz",
|
||||
"integrity": "sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@svgr/babel-preset": "^7.0.0",
|
||||
"camelcase": "^6.2.0",
|
||||
"cosmiconfig": "^8.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/core/node_modules/camelcase": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
||||
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/hast-util-to-babel-ast": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-7.0.0.tgz",
|
||||
"integrity": "sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.21.3",
|
||||
"entities": "^4.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/plugin-jsx": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-7.0.0.tgz",
|
||||
"integrity": "sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw==",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@svgr/babel-preset": "^7.0.0",
|
||||
"@svgr/hast-util-to-babel-ast": "^7.0.0",
|
||||
"svg-parser": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
}
|
||||
},
|
||||
"node_modules/@tabler/icons": {
|
||||
"version": "2.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.22.0.tgz",
|
||||
@ -7845,7 +8021,7 @@
|
||||
"version": "20.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz",
|
||||
"integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.4",
|
||||
@ -8804,8 +8980,7 @@
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/aria-hidden": {
|
||||
"version": "1.2.3",
|
||||
@ -9474,7 +9649,6 @@
|
||||
"version": "4.21.9",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
|
||||
"integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -9716,7 +9890,6 @@
|
||||
"version": "1.0.30001504",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz",
|
||||
"integrity": "sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -10415,7 +10588,6 @@
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz",
|
||||
"integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
@ -10589,7 +10761,6 @@
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
@ -11132,8 +11303,7 @@
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.433",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz",
|
||||
"integrity": "sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ=="
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
@ -11304,7 +11474,6 @@
|
||||
"version": "0.17.19",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
|
||||
"integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
@ -11359,7 +11528,6 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@ -12267,8 +12435,7 @@
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||
},
|
||||
"node_modules/esutils": {
|
||||
"version": "2.0.3",
|
||||
@ -12953,7 +13120,6 @@
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
@ -13019,7 +13185,6 @@
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -13262,7 +13427,6 @@
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@ -14858,7 +15022,6 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
@ -15002,7 +15165,6 @@
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
@ -15056,7 +15218,6 @@
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
@ -15449,7 +15610,6 @@
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
@ -15801,8 +15961,7 @@
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/mute-stream": {
|
||||
"version": "0.0.8",
|
||||
@ -15825,7 +15984,6 @@
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -15940,8 +16098,7 @@
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz",
|
||||
"integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ=="
|
||||
},
|
||||
"node_modules/normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
@ -16612,7 +16769,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
@ -16687,7 +16843,6 @@
|
||||
"version": "8.4.24",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
|
||||
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -17984,7 +18139,6 @@
|
||||
"version": "3.25.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
|
||||
"integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
@ -18120,7 +18274,6 @@
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@ -18433,7 +18586,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -18854,6 +19006,11 @@
|
||||
"resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz",
|
||||
"integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g=="
|
||||
},
|
||||
"node_modules/svg-parser": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
|
||||
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="
|
||||
},
|
||||
"node_modules/svg-path-bounds": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svg-path-bounds/-/svg-path-bounds-1.0.2.tgz",
|
||||
@ -19742,7 +19899,6 @@
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
||||
"integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -19984,7 +20140,6 @@
|
||||
"version": "4.3.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
|
||||
"integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.17.5",
|
||||
"postcss": "^8.4.23",
|
||||
@ -20062,6 +20217,45 @@
|
||||
"vite": "^2.7.0 || ^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-svgr": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-3.2.0.tgz",
|
||||
"integrity": "sha512-Uvq6niTvhqJU6ga78qLKBFJSDvxWhOnyfQSoKpDPMAGxJPo5S3+9hyjExE5YDj6Lpa4uaLkGc1cBgxXov+LjSw==",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"@svgr/core": "^7.0.0",
|
||||
"@svgr/plugin-jsx": "^7.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^2.6.0 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-svgr/node_modules/@rollup/pluginutils": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
|
||||
"integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-svgr/node_modules/@types/estree": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
|
||||
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "0.32.2",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.32.2.tgz",
|
||||
@ -20461,8 +20655,7 @@
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
|
@ -63,7 +63,6 @@
|
||||
"eslint-plugin-matrix-org": "^1.2.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-prune-var": "^1.1.1",
|
||||
"prettier": "2.8.0",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"storybook": "^7.0.22",
|
||||
@ -71,6 +70,7 @@
|
||||
"typescript": "5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-graphql-codegen": "^3.2.2",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vitest": "^0.32.2"
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,5 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require("tailwindcss"),
|
||||
require("autoprefixer"),
|
||||
require("postcss-prune-var"),
|
||||
],
|
||||
plugins: [require("tailwindcss"), require("autoprefixer")],
|
||||
};
|
||||
|
@ -223,6 +223,41 @@ The input/output is a string in RFC3339 format.
|
||||
"""
|
||||
scalar DateTime
|
||||
|
||||
"""
|
||||
The input of the `endBrowserSession` mutation.
|
||||
"""
|
||||
input EndBrowserSessionInput {
|
||||
"""
|
||||
The ID of the session to end.
|
||||
"""
|
||||
browserSessionId: ID!
|
||||
}
|
||||
|
||||
type EndBrowserSessionPayload {
|
||||
"""
|
||||
The status of the mutation.
|
||||
"""
|
||||
status: EndBrowserSessionStatus!
|
||||
"""
|
||||
Returns the ended session.
|
||||
"""
|
||||
browserSession: BrowserSession
|
||||
}
|
||||
|
||||
"""
|
||||
The status of the `endBrowserSession` mutation.
|
||||
"""
|
||||
enum EndBrowserSessionStatus {
|
||||
"""
|
||||
The session was ended.
|
||||
"""
|
||||
ENDED
|
||||
"""
|
||||
The session was not found.
|
||||
"""
|
||||
NOT_FOUND
|
||||
}
|
||||
|
||||
"""
|
||||
The input of the `endCompatSession` mutation.
|
||||
"""
|
||||
@ -336,6 +371,7 @@ type Mutation {
|
||||
setPrimaryEmail(input: SetPrimaryEmailInput!): SetPrimaryEmailPayload!
|
||||
endOauth2Session(input: EndOAuth2SessionInput!): EndOAuth2SessionPayload!
|
||||
endCompatSession(input: EndCompatSessionInput!): EndCompatSessionPayload!
|
||||
endBrowserSession(input: EndBrowserSessionInput!): EndBrowserSessionPayload!
|
||||
}
|
||||
|
||||
"""
|
||||
|
@ -12,13 +12,52 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { atom } from "jotai";
|
||||
import { AnyVariables, CombinedError, OperationContext } from "@urql/core";
|
||||
import { atom, WritableAtom } from "jotai";
|
||||
import { useHydrateAtoms } from "jotai/utils";
|
||||
import { atomWithQuery, clientAtom } from "jotai-urql";
|
||||
import { AtomWithQuery, atomWithQuery, clientAtom } from "jotai-urql";
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
import { graphql } from "./gql";
|
||||
import { client } from "./graphql";
|
||||
import { err, ok, Result } from "./result";
|
||||
|
||||
export type GqlResult<T> = Result<T, CombinedError>;
|
||||
export type GqlAtom<T> = WritableAtom<
|
||||
Promise<GqlResult<T>>,
|
||||
[context?: Partial<OperationContext>],
|
||||
void
|
||||
>;
|
||||
|
||||
/**
|
||||
* Map the result of a query atom to a new value, making it a GqlResult
|
||||
*
|
||||
* @param queryAtom: An atom got from atomWithQuery
|
||||
* @param mapper: A function that takes the data from the query and returns a new value
|
||||
*/
|
||||
export const mapQueryAtom = <Data, Variables extends AnyVariables, NewData>(
|
||||
queryAtom: AtomWithQuery<Data, Variables>,
|
||||
mapper: (data: Data) => NewData
|
||||
): GqlAtom<NewData> => {
|
||||
return atom(
|
||||
async (get): Promise<GqlResult<NewData>> => {
|
||||
const result = await get(queryAtom);
|
||||
if (result.error) {
|
||||
return err(result.error);
|
||||
}
|
||||
|
||||
if (result.data === undefined) {
|
||||
throw new Error("Query result is undefined");
|
||||
}
|
||||
|
||||
return ok(mapper(result.data));
|
||||
},
|
||||
|
||||
(_get, set, context) => {
|
||||
set(queryAtom, context);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const HydrateAtoms: React.FC<{ children: ReactElement }> = ({
|
||||
children,
|
||||
@ -44,13 +83,15 @@ const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ `
|
||||
|
||||
const currentViewerAtom = atomWithQuery({ query: CURRENT_VIEWER_QUERY });
|
||||
|
||||
export const currentUserIdAtom = atom(async (get) => {
|
||||
const result = await get(currentViewerAtom);
|
||||
if (result.data?.viewer.__typename === "User") {
|
||||
return result.data.viewer.id;
|
||||
export const currentUserIdAtom: GqlAtom<string | null> = mapQueryAtom(
|
||||
currentViewerAtom,
|
||||
(data) => {
|
||||
if (data.viewer.__typename === "User") {
|
||||
return data.viewer.id;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ `
|
||||
query CurrentViewerSessionQuery {
|
||||
@ -71,11 +112,11 @@ const currentViewerSessionAtom = atomWithQuery({
|
||||
query: CURRENT_VIEWER_SESSION_QUERY,
|
||||
});
|
||||
|
||||
export const currentBrowserSessionIdAtom = atom(
|
||||
async (get): Promise<string | null> => {
|
||||
const result = await get(currentViewerSessionAtom);
|
||||
if (result.data?.viewerSession.__typename === "BrowserSession") {
|
||||
return result.data.viewerSession.id;
|
||||
export const currentBrowserSessionIdAtom: GqlAtom<string | null> = mapQueryAtom(
|
||||
currentViewerSessionAtom,
|
||||
(data) => {
|
||||
if (data.viewerSession.__typename === "BrowserSession") {
|
||||
return data.viewerSession.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -13,8 +13,13 @@
|
||||
// limitations under the License.
|
||||
|
||||
import IconWebBrowser from "@vector-im/compound-design-tokens/icons/web-browser.svg";
|
||||
import { Body } from "@vector-im/compound-web";
|
||||
import { Body, Button } from "@vector-im/compound-web";
|
||||
import { atom, useSetAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useTransition } from "react";
|
||||
|
||||
import { currentBrowserSessionIdAtom, currentUserIdAtom } from "../atoms";
|
||||
import { FragmentType, graphql, useFragment } from "../gql";
|
||||
|
||||
import Block from "./Block";
|
||||
@ -31,6 +36,30 @@ const FRAGMENT = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation EndBrowserSession($id: ID!) {
|
||||
endBrowserSession(input: { browserSessionId: $id }) {
|
||||
status
|
||||
browserSession {
|
||||
id
|
||||
...BrowserSession_session
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const endSessionFamily = atomFamily((id: string) => {
|
||||
const endSession = atomWithMutation(END_SESSION_MUTATION);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const endSessionAtom = atom(
|
||||
(get) => get(endSession),
|
||||
(get, set) => set(endSession, { id })
|
||||
);
|
||||
|
||||
return endSessionAtom;
|
||||
});
|
||||
|
||||
type Props = {
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
isCurrent: boolean;
|
||||
@ -38,10 +67,30 @@ type Props = {
|
||||
|
||||
const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const [pending, startTransition] = useTransition();
|
||||
const endSession = useSetAtom(endSessionFamily(data.id));
|
||||
|
||||
// Pull those atoms to reset them when the current session is ended
|
||||
const currentUserId = useSetAtom(currentUserIdAtom);
|
||||
const currentBrowserSessionId = useSetAtom(currentBrowserSessionIdAtom);
|
||||
|
||||
// const lastAuthentication = data.lastAuthentication?.createdAt;
|
||||
const createdAt = data.createdAt;
|
||||
|
||||
const onSessionEnd = () => {
|
||||
startTransition(() => {
|
||||
endSession().then(() => {
|
||||
if (isCurrent) {
|
||||
currentBrowserSessionId({
|
||||
requestPolicy: "network-only",
|
||||
});
|
||||
currentUserId({
|
||||
requestPolicy: "network-only",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Block className="my-4 flex items-center">
|
||||
<IconWebBrowser className="mr-4 session-icon" />
|
||||
@ -59,15 +108,16 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
|
||||
Signed in <DateTime datetime={createdAt} />
|
||||
</Body>
|
||||
</div>
|
||||
<Body
|
||||
as="a"
|
||||
|
||||
<Button
|
||||
kind="destructive"
|
||||
size="sm"
|
||||
weight="medium"
|
||||
href="#"
|
||||
className="text-critical underline hover:no-underline"
|
||||
className="mt-2"
|
||||
onClick={onSessionEnd}
|
||||
disabled={pending}
|
||||
>
|
||||
Sign out
|
||||
</Body>
|
||||
</Button>
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ import { atomFamily } from "jotai/utils";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
import { useTransition } from "react";
|
||||
|
||||
import { currentBrowserSessionIdAtom } from "../atoms";
|
||||
import { currentBrowserSessionIdAtom, mapQueryAtom } from "../atoms";
|
||||
import { graphql } from "../gql";
|
||||
import { PageInfo } from "../gql/graphql";
|
||||
import {
|
||||
@ -25,9 +25,11 @@ import {
|
||||
atomWithPagination,
|
||||
Pagination,
|
||||
} from "../pagination";
|
||||
import { isErr, isOk, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
import BlockList from "./BlockList";
|
||||
import BrowserSession from "./BrowserSession";
|
||||
import GraphQLError from "./GraphQLError";
|
||||
import PaginationControls from "./PaginationControls";
|
||||
import { Title } from "./Typography";
|
||||
|
||||
@ -69,17 +71,23 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
const currentPaginationAtom = atomForCurrentPagination();
|
||||
|
||||
const browserSessionListFamily = atomFamily((userId: string) => {
|
||||
const browserSessionList = atomWithQuery({
|
||||
const browserSessionListQuery = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }),
|
||||
});
|
||||
|
||||
const browserSessionList = mapQueryAtom(
|
||||
browserSessionListQuery,
|
||||
(data) => data.user?.browserSessions || null
|
||||
);
|
||||
|
||||
return browserSessionList;
|
||||
});
|
||||
|
||||
const pageInfoFamily = atomFamily((userId: string) => {
|
||||
const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => {
|
||||
const result = await get(browserSessionListFamily(userId));
|
||||
return result.data?.user?.browserSessions?.pageInfo ?? null;
|
||||
return (isOk(result) && unwrapOk(result)?.pageInfo) || null;
|
||||
});
|
||||
return pageInfoAtom;
|
||||
});
|
||||
@ -94,20 +102,26 @@ const paginationFamily = atomFamily((userId: string) => {
|
||||
});
|
||||
|
||||
const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const currentSessionId = useAtomValue(currentBrowserSessionIdAtom);
|
||||
const currentSessionIdResult = useAtomValue(currentBrowserSessionIdAtom);
|
||||
const [pending, startTransition] = useTransition();
|
||||
const result = useAtomValue(browserSessionListFamily(userId));
|
||||
const setPagination = useSetAtom(currentPaginationAtom);
|
||||
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
|
||||
|
||||
if (isErr(currentSessionIdResult))
|
||||
return <GraphQLError error={unwrapErr(currentSessionIdResult)} />;
|
||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||
|
||||
const browserSessions = unwrapOk(result);
|
||||
if (browserSessions === null) return <>Failed to load browser sessions</>;
|
||||
const currentSessionId = unwrapOk(currentSessionIdResult);
|
||||
|
||||
const paginate = (pagination: Pagination): void => {
|
||||
startTransition(() => {
|
||||
setPagination(pagination);
|
||||
});
|
||||
};
|
||||
|
||||
if (result.data?.user?.browserSessions) {
|
||||
const data = result.data.user.browserSessions;
|
||||
return (
|
||||
<BlockList>
|
||||
<Title>List of browser sessions:</Title>
|
||||
@ -116,7 +130,7 @@ const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
onNext={nextPage ? (): void => paginate(nextPage) : null}
|
||||
disabled={pending}
|
||||
/>
|
||||
{data.edges.map((n) => (
|
||||
{browserSessions.edges.map((n) => (
|
||||
<BrowserSession
|
||||
key={n.cursor}
|
||||
session={n.node}
|
||||
@ -125,9 +139,6 @@ const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
}
|
||||
|
||||
return <>Failed to load browser sessions</>;
|
||||
};
|
||||
|
||||
export default BrowserSessionList;
|
||||
|
@ -17,6 +17,7 @@ import { atomFamily } from "jotai/utils";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
import { useTransition } from "react";
|
||||
|
||||
import { mapQueryAtom } from "../atoms";
|
||||
import { graphql } from "../gql";
|
||||
import { PageInfo } from "../gql/graphql";
|
||||
import {
|
||||
@ -24,9 +25,11 @@ import {
|
||||
atomWithPagination,
|
||||
Pagination,
|
||||
} from "../pagination";
|
||||
import { isErr, isOk, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
import BlockList from "./BlockList";
|
||||
import CompatSsoLogin from "./CompatSsoLogin";
|
||||
import GraphQLError from "./GraphQLError";
|
||||
import PaginationControls from "./PaginationControls";
|
||||
import { Title } from "./Typography";
|
||||
|
||||
@ -67,18 +70,23 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
const currentPaginationAtom = atomForCurrentPagination();
|
||||
|
||||
const compatSsoLoginListFamily = atomFamily((userId: string) => {
|
||||
const compatSsoLoginList = atomWithQuery({
|
||||
const compatSsoLoginListQuery = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }),
|
||||
});
|
||||
|
||||
const compatSsoLoginList = mapQueryAtom(
|
||||
compatSsoLoginListQuery,
|
||||
(data) => data.user?.compatSsoLogins || null
|
||||
);
|
||||
|
||||
return compatSsoLoginList;
|
||||
});
|
||||
|
||||
const pageInfoFamily = atomFamily((userId: string) => {
|
||||
const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => {
|
||||
const result = await get(compatSsoLoginListFamily(userId));
|
||||
return result.data?.user?.compatSsoLogins?.pageInfo ?? null;
|
||||
return (isOk(result) && unwrapOk(result)?.pageInfo) || null;
|
||||
});
|
||||
|
||||
return pageInfoAtom;
|
||||
@ -98,14 +106,17 @@ const CompatSsoLoginList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const setPagination = useSetAtom(currentPaginationAtom);
|
||||
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
|
||||
|
||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||
const compatSsoLoginList = unwrapOk(result);
|
||||
if (compatSsoLoginList === null)
|
||||
return <>Failed to load list of compatibility sessions.</>;
|
||||
|
||||
const paginate = (pagination: Pagination): void => {
|
||||
startTransition(() => {
|
||||
setPagination(pagination);
|
||||
});
|
||||
};
|
||||
|
||||
if (result.data?.user?.compatSsoLogins) {
|
||||
const data = result.data.user.compatSsoLogins;
|
||||
return (
|
||||
<BlockList>
|
||||
<Title>List of compatibility sessions:</Title>
|
||||
@ -114,14 +125,11 @@ const CompatSsoLoginList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
onNext={nextPage ? (): void => paginate(nextPage) : null}
|
||||
disabled={pending}
|
||||
/>
|
||||
{data.edges.map((n) => (
|
||||
{compatSsoLoginList.edges.map((n) => (
|
||||
<CompatSsoLogin login={n.node} key={n.node.id} />
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
}
|
||||
|
||||
return <>Failed to load list of compatibility sessions.</>;
|
||||
};
|
||||
|
||||
export default CompatSsoLoginList;
|
||||
|
24
frontend/src/components/GraphQLError.tsx
Normal file
24
frontend/src/components/GraphQLError.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
// 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 { CombinedError } from "@urql/core";
|
||||
import { Alert } from "@vector-im/compound-web";
|
||||
|
||||
const GraphQLError: React.FC<{ error: CombinedError }> = ({ error }) => (
|
||||
<Alert type="critical" title={error.message}>
|
||||
{error.toString()}
|
||||
</Alert>
|
||||
);
|
||||
|
||||
export default GraphQLError;
|
19
frontend/src/components/NotFound.tsx
Normal file
19
frontend/src/components/NotFound.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
// 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 { Alert } from "@vector-im/compound-web";
|
||||
|
||||
const NotFound: React.FC = () => <Alert type="critical" title="Not found." />;
|
||||
|
||||
export default NotFound;
|
21
frontend/src/components/NotLoggedIn.tsx
Normal file
21
frontend/src/components/NotLoggedIn.tsx
Normal file
@ -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.
|
||||
|
||||
import { Alert } from "@vector-im/compound-web";
|
||||
|
||||
const NotLoggedIn: React.FC = () => (
|
||||
<Alert type="critical" title="You're not logged in." />
|
||||
);
|
||||
|
||||
export default NotLoggedIn;
|
@ -41,7 +41,7 @@ const FRAGMENT = graphql(/* GraphQL */ `
|
||||
`);
|
||||
|
||||
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation EndSession($id: ID!) {
|
||||
mutation EndOAuth2Session($id: ID!) {
|
||||
endOauth2Session(input: { oauth2SessionId: $id }) {
|
||||
status
|
||||
oauth2Session {
|
||||
|
@ -17,6 +17,7 @@ import { atomFamily } from "jotai/utils";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
import { useTransition } from "react";
|
||||
|
||||
import { mapQueryAtom } from "../atoms";
|
||||
import { graphql } from "../gql";
|
||||
import { PageInfo } from "../gql/graphql";
|
||||
import {
|
||||
@ -24,8 +25,10 @@ import {
|
||||
atomWithPagination,
|
||||
Pagination,
|
||||
} from "../pagination";
|
||||
import { isErr, isOk, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
import BlockList from "./BlockList";
|
||||
import GraphQLError from "./GraphQLError";
|
||||
import OAuth2Session from "./OAuth2Session";
|
||||
import PaginationControls from "./PaginationControls";
|
||||
import { Title } from "./Typography";
|
||||
@ -68,18 +71,23 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
const currentPaginationAtom = atomForCurrentPagination();
|
||||
|
||||
const oauth2SessionListFamily = atomFamily((userId: string) => {
|
||||
const oauth2SessionList = atomWithQuery({
|
||||
const oauth2SessionListQuery = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }),
|
||||
});
|
||||
|
||||
const oauth2SessionList = mapQueryAtom(
|
||||
oauth2SessionListQuery,
|
||||
(data) => data.user?.oauth2Sessions || null
|
||||
);
|
||||
|
||||
return oauth2SessionList;
|
||||
});
|
||||
|
||||
const pageInfoFamily = atomFamily((userId: string) => {
|
||||
const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => {
|
||||
const result = await get(oauth2SessionListFamily(userId));
|
||||
return result.data?.user?.oauth2Sessions?.pageInfo ?? null;
|
||||
return (isOk(result) && unwrapOk(result)?.pageInfo) || null;
|
||||
});
|
||||
|
||||
return pageInfoAtom;
|
||||
@ -103,14 +111,17 @@ const OAuth2SessionList: React.FC<Props> = ({ userId }) => {
|
||||
const setPagination = useSetAtom(currentPaginationAtom);
|
||||
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
|
||||
|
||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||
const oauth2Sessions = unwrapOk(result);
|
||||
if (oauth2Sessions === null)
|
||||
return <>Failed to load OAuth 2.0 session list</>;
|
||||
|
||||
const paginate = (pagination: Pagination): void => {
|
||||
startTransition(() => {
|
||||
setPagination(pagination);
|
||||
});
|
||||
};
|
||||
|
||||
if (result.data?.user?.oauth2Sessions) {
|
||||
const data = result.data.user.oauth2Sessions;
|
||||
return (
|
||||
<BlockList>
|
||||
<Title>List of OAuth 2.0 sessions:</Title>
|
||||
@ -119,14 +130,11 @@ const OAuth2SessionList: React.FC<Props> = ({ userId }) => {
|
||||
onNext={nextPage ? (): void => paginate(nextPage) : null}
|
||||
disabled={pending}
|
||||
/>
|
||||
{data.edges.map((n) => (
|
||||
{oauth2Sessions.edges.map((n) => (
|
||||
<OAuth2Session key={n.cursor} session={n.node} />
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
} else {
|
||||
return <>Failed to load OAuth 2.0 session list</>;
|
||||
}
|
||||
};
|
||||
|
||||
export default OAuth2SessionList;
|
||||
|
@ -21,6 +21,8 @@ const documents = {
|
||||
types.AddEmailDocument,
|
||||
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n":
|
||||
types.BrowserSession_SessionFragmentDoc,
|
||||
"\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n":
|
||||
types.EndBrowserSessionDocument,
|
||||
"\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 ) {\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":
|
||||
types.BrowserSessionListDocument,
|
||||
"\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n ...CompatSsoLogin_session\n createdAt\n deviceId\n finishedAt\n }\n }\n":
|
||||
@ -33,8 +35,8 @@ const documents = {
|
||||
types.CompatSsoLoginListDocument,
|
||||
"\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":
|
||||
types.OAuth2Session_SessionFragmentDoc,
|
||||
"\n mutation EndSession($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n":
|
||||
types.EndSessionDocument,
|
||||
"\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n":
|
||||
types.EndOAuth2SessionDocument,
|
||||
"\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 pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.OAuth2SessionListQueryDocument,
|
||||
"\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n":
|
||||
@ -97,6 +99,12 @@ export function graphql(
|
||||
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"];
|
||||
/**
|
||||
* 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"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@ -137,8 +145,8 @@ export function graphql(
|
||||
* 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 EndSession($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n"
|
||||
): typeof documents["\n mutation EndSession($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.
|
||||
*/
|
||||
|
@ -184,6 +184,28 @@ export type CreationEvent = {
|
||||
createdAt: Scalars["DateTime"]["output"];
|
||||
};
|
||||
|
||||
/** The input of the `endBrowserSession` mutation. */
|
||||
export type EndBrowserSessionInput = {
|
||||
/** The ID of the session to end. */
|
||||
browserSessionId: Scalars["ID"]["input"];
|
||||
};
|
||||
|
||||
export type EndBrowserSessionPayload = {
|
||||
__typename?: "EndBrowserSessionPayload";
|
||||
/** Returns the ended session. */
|
||||
browserSession?: Maybe<BrowserSession>;
|
||||
/** The status of the mutation. */
|
||||
status: EndBrowserSessionStatus;
|
||||
};
|
||||
|
||||
/** The status of the `endBrowserSession` mutation. */
|
||||
export enum EndBrowserSessionStatus {
|
||||
/** The session was ended. */
|
||||
Ended = "ENDED",
|
||||
/** The session was not found. */
|
||||
NotFound = "NOT_FOUND",
|
||||
}
|
||||
|
||||
/** The input of the `endCompatSession` mutation. */
|
||||
export type EndCompatSessionInput = {
|
||||
/** The ID of the session to end. */
|
||||
@ -243,6 +265,7 @@ export type Mutation = {
|
||||
__typename?: "Mutation";
|
||||
/** Add an email address to the specified user */
|
||||
addEmail: AddEmailPayload;
|
||||
endBrowserSession: EndBrowserSessionPayload;
|
||||
endCompatSession: EndCompatSessionPayload;
|
||||
endOauth2Session: EndOAuth2SessionPayload;
|
||||
/** Remove an email address */
|
||||
@ -260,6 +283,11 @@ export type MutationAddEmailArgs = {
|
||||
input: AddEmailInput;
|
||||
};
|
||||
|
||||
/** The mutations root of the GraphQL interface. */
|
||||
export type MutationEndBrowserSessionArgs = {
|
||||
input: EndBrowserSessionInput;
|
||||
};
|
||||
|
||||
/** The mutations root of the GraphQL interface. */
|
||||
export type MutationEndCompatSessionArgs = {
|
||||
input: EndCompatSessionInput;
|
||||
@ -777,6 +805,25 @@ export type BrowserSession_SessionFragment = {
|
||||
} | null;
|
||||
} & { " $fragmentName"?: "BrowserSession_SessionFragment" };
|
||||
|
||||
export type EndBrowserSessionMutationVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
}>;
|
||||
|
||||
export type EndBrowserSessionMutation = {
|
||||
__typename?: "Mutation";
|
||||
endBrowserSession: {
|
||||
__typename?: "EndBrowserSessionPayload";
|
||||
status: EndBrowserSessionStatus;
|
||||
browserSession?:
|
||||
| ({ __typename?: "BrowserSession"; id: string } & {
|
||||
" $fragmentRefs"?: {
|
||||
BrowserSession_SessionFragment: BrowserSession_SessionFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
};
|
||||
};
|
||||
|
||||
export type BrowserSessionListQueryVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
first?: InputMaybe<Scalars["Int"]["input"]>;
|
||||
@ -908,11 +955,11 @@ export type OAuth2Session_SessionFragment = {
|
||||
};
|
||||
} & { " $fragmentName"?: "OAuth2Session_SessionFragment" };
|
||||
|
||||
export type EndSessionMutationVariables = Exact<{
|
||||
export type EndOAuth2SessionMutationVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
}>;
|
||||
|
||||
export type EndSessionMutation = {
|
||||
export type EndOAuth2SessionMutation = {
|
||||
__typename?: "Mutation";
|
||||
endOauth2Session: {
|
||||
__typename?: "EndOAuth2SessionPayload";
|
||||
@ -1532,6 +1579,103 @@ export const AddEmailDocument = {
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<AddEmailMutation, AddEmailMutationVariables>;
|
||||
export const EndBrowserSessionDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "EndBrowserSession" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "endBrowserSession" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: {
|
||||
kind: "ObjectValue",
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "browserSessionId" },
|
||||
value: {
|
||||
kind: "Variable",
|
||||
name: { kind: "Name", value: "id" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "status" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "browserSession" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "BrowserSession_session" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "BrowserSession_session" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "BrowserSession" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "lastAuthentication" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
EndBrowserSessionMutation,
|
||||
EndBrowserSessionMutationVariables
|
||||
>;
|
||||
export const BrowserSessionListDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@ -2054,13 +2198,13 @@ export const CompatSsoLoginListDocument = {
|
||||
CompatSsoLoginListQuery,
|
||||
CompatSsoLoginListQueryVariables
|
||||
>;
|
||||
export const EndSessionDocument = {
|
||||
export const EndOAuth2SessionDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "EndSession" },
|
||||
name: { kind: "Name", value: "EndOAuth2Session" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
@ -2151,7 +2295,10 @@ export const EndSessionDocument = {
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<EndSessionMutation, EndSessionMutationVariables>;
|
||||
} as unknown as DocumentNode<
|
||||
EndOAuth2SessionMutation,
|
||||
EndOAuth2SessionMutationVariables
|
||||
>;
|
||||
export const OAuth2SessionListQueryDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
|
@ -522,6 +522,33 @@ export default {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "OBJECT",
|
||||
name: "EndBrowserSessionPayload",
|
||||
fields: [
|
||||
{
|
||||
name: "browserSession",
|
||||
type: {
|
||||
kind: "OBJECT",
|
||||
name: "BrowserSession",
|
||||
ofType: null,
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
type: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "SCALAR",
|
||||
name: "Any",
|
||||
},
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
],
|
||||
interfaces: [],
|
||||
},
|
||||
{
|
||||
kind: "OBJECT",
|
||||
name: "EndCompatSessionPayload",
|
||||
@ -637,6 +664,29 @@ export default {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "endBrowserSession",
|
||||
type: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "OBJECT",
|
||||
name: "EndBrowserSessionPayload",
|
||||
ofType: null,
|
||||
},
|
||||
},
|
||||
args: [
|
||||
{
|
||||
name: "input",
|
||||
type: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "SCALAR",
|
||||
name: "Any",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "endCompatSession",
|
||||
type: {
|
||||
|
@ -15,8 +15,11 @@
|
||||
import { useAtomValue } from "jotai";
|
||||
|
||||
import { currentUserIdAtom } from "../atoms";
|
||||
import GraphQLError from "../components/GraphQLError";
|
||||
import NotLoggedIn from "../components/NotLoggedIn";
|
||||
import UserEmailList from "../components/UserEmailList";
|
||||
import UserGreeting from "../components/UserGreeting";
|
||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
const UserAccount: React.FC<{ id: string }> = ({ id }) => {
|
||||
return (
|
||||
@ -28,16 +31,17 @@ const UserAccount: React.FC<{ id: string }> = ({ id }) => {
|
||||
};
|
||||
|
||||
const CurrentUserAccount: React.FC = () => {
|
||||
const userId = useAtomValue(currentUserIdAtom);
|
||||
if (userId !== null) {
|
||||
const result = useAtomValue(currentUserIdAtom);
|
||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||
|
||||
const userId = unwrapOk(result);
|
||||
if (userId === null) return <NotLoggedIn />;
|
||||
|
||||
return (
|
||||
<div className="w-96 mx-auto">
|
||||
<UserAccount id={userId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="w-96 mx-auto">Not logged in.</div>;
|
||||
};
|
||||
|
||||
export default CurrentUserAccount;
|
||||
|
@ -16,7 +16,11 @@ import { useAtomValue } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
|
||||
import { mapQueryAtom } from "../atoms";
|
||||
import GraphQLError from "../components/GraphQLError";
|
||||
import NotFound from "../components/NotFound";
|
||||
import { graphql } from "../gql";
|
||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query BrowserSessionQuery($id: ID!) {
|
||||
@ -36,26 +40,31 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
`);
|
||||
|
||||
const browserSessionFamily = atomFamily((id: string) => {
|
||||
const browserSessionAtom = atomWithQuery({
|
||||
const browserSessionQueryAtom = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: () => ({ id }),
|
||||
});
|
||||
|
||||
const browserSessionAtom = mapQueryAtom(
|
||||
browserSessionQueryAtom,
|
||||
(data) => data?.browserSession
|
||||
);
|
||||
|
||||
return browserSessionAtom;
|
||||
});
|
||||
|
||||
const BrowserSession: React.FC<{ id: string }> = ({ id }) => {
|
||||
const result = useAtomValue(browserSessionFamily(id));
|
||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||
|
||||
const browserSession = unwrapOk(result);
|
||||
if (browserSession === null) return <NotFound />;
|
||||
|
||||
if (result.data?.browserSession) {
|
||||
return (
|
||||
<pre>
|
||||
<code>{JSON.stringify(result.data.browserSession, null, 2)}</code>
|
||||
<code>{JSON.stringify(browserSession, null, 2)}</code>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
return <>Failed to load browser session</>;
|
||||
};
|
||||
|
||||
export default BrowserSession;
|
||||
|
@ -12,19 +12,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Alert } from "@vector-im/compound-web";
|
||||
import { useAtomValue } from "jotai";
|
||||
|
||||
import { currentUserIdAtom } from "../atoms";
|
||||
import BrowserSessionList from "../components/BrowserSessionList";
|
||||
import CompatSsoLoginList from "../components/CompatSsoLoginList";
|
||||
import GraphQLError from "../components/GraphQLError";
|
||||
import NotLoggedIn from "../components/NotLoggedIn";
|
||||
import OAuth2SessionList from "../components/OAuth2SessionList";
|
||||
import UserGreeting from "../components/UserGreeting";
|
||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
const Home: React.FC = () => {
|
||||
const currentUserId = useAtomValue(currentUserIdAtom);
|
||||
const result = useAtomValue(currentUserIdAtom);
|
||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||
|
||||
const currentUserId = unwrapOk(result);
|
||||
if (currentUserId === null) return <NotLoggedIn />;
|
||||
|
||||
if (currentUserId) {
|
||||
return (
|
||||
<>
|
||||
<UserGreeting userId={currentUserId} />
|
||||
@ -35,9 +40,6 @@ const Home: React.FC = () => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return <Alert type="critical" title="You're not logged in." />;
|
||||
}
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
@ -16,7 +16,11 @@ import { useAtomValue } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
|
||||
import { mapQueryAtom } from "../atoms";
|
||||
import GraphQLError from "../components/GraphQLError";
|
||||
import NotFound from "../components/NotFound";
|
||||
import { graphql } from "../gql";
|
||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query OAuth2ClientQuery($id: ID!) {
|
||||
@ -33,26 +37,31 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
`);
|
||||
|
||||
const oauth2ClientFamily = atomFamily((id: string) => {
|
||||
const oauth2ClientAtom = atomWithQuery({
|
||||
const oauth2ClientQueryAtom = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: () => ({ id }),
|
||||
});
|
||||
|
||||
const oauth2ClientAtom = mapQueryAtom(
|
||||
oauth2ClientQueryAtom,
|
||||
(data) => data?.oauth2Client
|
||||
);
|
||||
|
||||
return oauth2ClientAtom;
|
||||
});
|
||||
|
||||
const OAuth2Client: React.FC<{ id: string }> = ({ id }) => {
|
||||
const result = useAtomValue(oauth2ClientFamily(id));
|
||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||
|
||||
const oauth2Client = unwrapOk(result);
|
||||
if (oauth2Client === null) return <NotFound />;
|
||||
|
||||
if (result.data?.oauth2Client) {
|
||||
return (
|
||||
<pre>
|
||||
<code>{JSON.stringify(result.data.oauth2Client, null, 2)}</code>
|
||||
<code>{JSON.stringify(oauth2Client, null, 2)}</code>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
return <>Failed to load OAuth2 client</>;
|
||||
};
|
||||
|
||||
export default OAuth2Client;
|
||||
|
61
frontend/src/result.ts
Normal file
61
frontend/src/result.ts
Normal file
@ -0,0 +1,61 @@
|
||||
// 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.
|
||||
|
||||
const RESULT = Symbol("Result");
|
||||
const ERR = Symbol("Err");
|
||||
const OK = Symbol("Ok");
|
||||
|
||||
/**
|
||||
* An `Ok` is a type that represents a successful result.
|
||||
*/
|
||||
export type Ok<T> = {
|
||||
[RESULT]: typeof OK;
|
||||
[OK]: T;
|
||||
};
|
||||
|
||||
/**
|
||||
* An `Err` is a type that represents an error.
|
||||
*/
|
||||
export type Err<E> = {
|
||||
[RESULT]: typeof ERR;
|
||||
[ERR]: E;
|
||||
};
|
||||
|
||||
/**
|
||||
* A `Result` is a type that represents either an `Ok` or an `Err`.
|
||||
*/
|
||||
export type Result<T, E> = Ok<T> | Err<E>;
|
||||
|
||||
// Construct an `Ok`
|
||||
export const ok = <T>(data: T): Ok<T> => ({ [RESULT]: OK, [OK]: data });
|
||||
|
||||
// Construct an `Err`
|
||||
export const err = <E>(error: E): Err<E> => ({
|
||||
[RESULT]: ERR,
|
||||
[ERR]: error,
|
||||
});
|
||||
|
||||
// Check if a `Result` is an `Ok`
|
||||
export const isOk = <T, E>(result: Result<T, E>): result is Ok<T> =>
|
||||
result[RESULT] === OK;
|
||||
|
||||
// Check if a `Result` is an `Err`
|
||||
export const isErr = <T, E>(result: Result<T, E>): result is Err<E> =>
|
||||
result[RESULT] === ERR;
|
||||
|
||||
// Extract the data from an `Ok`
|
||||
export const unwrapOk = <T>(result: Ok<T>): T => result[OK];
|
||||
|
||||
// Extract the error from an `Err`
|
||||
export const unwrapErr = <E>(result: Err<E>): E => result[ERR];
|
@ -16,8 +16,9 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
import codegen from "vite-plugin-graphql-codegen";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
|
||||
export default defineConfig({
|
||||
export default defineConfig((env) => ({
|
||||
base: "/app/",
|
||||
build: {
|
||||
manifest: true,
|
||||
@ -29,10 +30,49 @@ export default defineConfig({
|
||||
react({
|
||||
babel: {
|
||||
plugins: [
|
||||
[
|
||||
"jotai/babel/plugin-react-refresh",
|
||||
"jotai/babel/plugin-debug-label",
|
||||
{
|
||||
customAtomNames: [
|
||||
"mapQueryAtom",
|
||||
"atomWithPagination",
|
||||
"atomWithCurrentPagination",
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
"jotai/babel/plugin-debug-label",
|
||||
{
|
||||
customAtomNames: [
|
||||
"mapQueryAtom",
|
||||
"atomWithPagination",
|
||||
"atomWithCurrentPagination",
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
svgr({
|
||||
exportAsDefault: true,
|
||||
|
||||
esbuildOptions: {
|
||||
// This makes sure we're using the same JSX runtime as React itself
|
||||
jsx: "automatic",
|
||||
jsxDev: env.mode === "development",
|
||||
},
|
||||
|
||||
svgrOptions: {
|
||||
// Using 1em in order to make SVG size inherits from text size.
|
||||
icon: "1em",
|
||||
|
||||
svgProps: {
|
||||
// Adding a class in case we want to add global overrides, but one
|
||||
// should probably stick to using CSS modules most of the time
|
||||
className: "cpd-icon",
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
@ -50,4 +90,4 @@ export default defineConfig({
|
||||
all: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
Reference in New Issue
Block a user