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: Show all compatibilities sessions, not just SSO logins
Also cleans up a bunch of things in the frontend
This commit is contained in:
@ -24,7 +24,10 @@ use crate::state::ContextExt;
|
||||
/// A compat session represents a client session which used the legacy Matrix
|
||||
/// login API.
|
||||
#[derive(Description)]
|
||||
pub struct CompatSession(pub mas_data_model::CompatSession);
|
||||
pub struct CompatSession(
|
||||
pub mas_data_model::CompatSession,
|
||||
pub Option<mas_data_model::CompatSsoLogin>,
|
||||
);
|
||||
|
||||
#[Object(use_type_description)]
|
||||
impl CompatSession {
|
||||
@ -61,6 +64,11 @@ impl CompatSession {
|
||||
pub async fn finished_at(&self) -> Option<DateTime<Utc>> {
|
||||
self.0.finished_at()
|
||||
}
|
||||
|
||||
/// The associated SSO login, if any.
|
||||
pub async fn sso_login(&self) -> Option<CompatSsoLogin> {
|
||||
self.1.as_ref().map(|l| CompatSsoLogin(l.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A compat SSO login represents a login done through the legacy Matrix login
|
||||
@ -114,6 +122,6 @@ impl CompatSsoLogin {
|
||||
.context("Could not load compat session")?;
|
||||
repo.cancel().await?;
|
||||
|
||||
Ok(Some(CompatSession(session)))
|
||||
Ok(Some(CompatSession(session, Some(self.0.clone()))))
|
||||
}
|
||||
}
|
||||
|
@ -22,14 +22,17 @@ use mas_storage::{
|
||||
oauth2::OAuth2SessionRepository,
|
||||
upstream_oauth2::UpstreamOAuthLinkRepository,
|
||||
user::{BrowserSessionRepository, UserEmailRepository},
|
||||
Pagination,
|
||||
Pagination, RepositoryAccess,
|
||||
};
|
||||
|
||||
use super::{
|
||||
compat_sessions::CompatSsoLogin, BrowserSession, Cursor, NodeCursor, NodeType, OAuth2Session,
|
||||
UpstreamOAuth2Link,
|
||||
};
|
||||
use crate::{model::matrix::MatrixUser, state::ContextExt};
|
||||
use crate::{
|
||||
model::{matrix::MatrixUser, CompatSession},
|
||||
state::ContextExt,
|
||||
};
|
||||
|
||||
#[derive(Description)]
|
||||
/// A user is an individual's account.
|
||||
@ -129,6 +132,58 @@ impl User {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the list of compatibility sessions, chronologically sorted
|
||||
async fn compat_sessions(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
|
||||
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
|
||||
after: Option<String>,
|
||||
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
|
||||
before: Option<String>,
|
||||
#[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
|
||||
#[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
|
||||
) -> Result<Connection<Cursor, CompatSession>, async_graphql::Error> {
|
||||
let state = ctx.state();
|
||||
let mut repo = state.repository().await?;
|
||||
|
||||
query(
|
||||
after,
|
||||
before,
|
||||
first,
|
||||
last,
|
||||
|after, before, first, last| async move {
|
||||
let after_id = after
|
||||
.map(|x: OpaqueCursor<NodeCursor>| x.extract_for_type(NodeType::CompatSsoLogin))
|
||||
.transpose()?;
|
||||
let before_id = before
|
||||
.map(|x: OpaqueCursor<NodeCursor>| x.extract_for_type(NodeType::CompatSsoLogin))
|
||||
.transpose()?;
|
||||
let pagination = Pagination::try_new(before_id, after_id, first, last)?;
|
||||
|
||||
let page = repo
|
||||
.compat_session()
|
||||
.list_paginated(&self.0, pagination)
|
||||
.await?;
|
||||
|
||||
repo.cancel().await?;
|
||||
|
||||
let mut connection = Connection::new(page.has_previous_page, page.has_next_page);
|
||||
connection
|
||||
.edges
|
||||
.extend(page.edges.into_iter().map(|(session, sso_login)| {
|
||||
Edge::new(
|
||||
OpaqueCursor(NodeCursor(NodeType::CompatSession, session.id)),
|
||||
CompatSession(session, sso_login),
|
||||
)
|
||||
}));
|
||||
|
||||
Ok::<_, async_graphql::Error>(connection)
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the list of active browser sessions, chronologically sorted
|
||||
async fn browser_sessions(
|
||||
&self,
|
||||
|
@ -66,7 +66,8 @@ impl EndCompatSessionPayload {
|
||||
/// Returns the ended session.
|
||||
async fn compat_session(&self) -> Option<CompatSession> {
|
||||
match self {
|
||||
Self::Ended(session) => Some(CompatSession(session.clone())),
|
||||
// XXX: the SSO login is not returned here.
|
||||
Self::Ended(session) => Some(CompatSession(session.clone(), None)),
|
||||
Self::NotFound => None,
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ use axum::{
|
||||
Router,
|
||||
};
|
||||
use headers::HeaderName;
|
||||
use hyper::header::{ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, CONTENT_LANGUAGE, CONTENT_TYPE};
|
||||
use hyper::header::{
|
||||
ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_TYPE,
|
||||
};
|
||||
use mas_http::CorsLayerExt;
|
||||
use mas_keystore::{Encrypter, Keystore};
|
||||
use mas_policy::PolicyFactory;
|
||||
@ -268,7 +270,8 @@ where
|
||||
BoxRng: FromRequestParts<S>,
|
||||
{
|
||||
Router::new()
|
||||
// TODO: mount this route somewhere else?
|
||||
// XXX: hard-coded redirect from /account to /account/
|
||||
.route("/account", get(|| async { mas_router::Account.go() }))
|
||||
.route(mas_router::Account::route(), get(self::views::app::get))
|
||||
.route(
|
||||
mas_router::AccountWildcard::route(),
|
||||
@ -351,6 +354,7 @@ where
|
||||
if let Ok(res) = templates.render_error(ctx).await {
|
||||
let (mut parts, _original_body) = response.into_parts();
|
||||
parts.headers.remove(CONTENT_TYPE);
|
||||
parts.headers.remove(CONTENT_LENGTH);
|
||||
return Ok((parts, Html(res)).into_response());
|
||||
}
|
||||
}
|
||||
|
@ -14,14 +14,20 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_data_model::{CompatSession, CompatSessionState, Device, User};
|
||||
use mas_storage::{compat::CompatSessionRepository, Clock};
|
||||
use mas_data_model::{
|
||||
CompatSession, CompatSessionState, CompatSsoLogin, CompatSsoLoginState, Device, User,
|
||||
};
|
||||
use mas_storage::{compat::CompatSessionRepository, Clock, Page, Pagination};
|
||||
use rand::RngCore;
|
||||
use sqlx::PgConnection;
|
||||
use sqlx::{PgConnection, QueryBuilder};
|
||||
use ulid::Ulid;
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{tracing::ExecuteExt, DatabaseError, DatabaseInconsistencyError, LookupResultExt};
|
||||
use crate::{
|
||||
pagination::QueryBuilderExt, tracing::ExecuteExt, DatabaseError, DatabaseInconsistencyError,
|
||||
LookupResultExt,
|
||||
};
|
||||
|
||||
/// An implementation of [`CompatSessionRepository`] for a PostgreSQL connection
|
||||
pub struct PgCompatSessionRepository<'c> {
|
||||
@ -75,6 +81,101 @@ impl TryFrom<CompatSessionLookup> for CompatSession {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct CompatSessionAndSsoLoginLookup {
|
||||
compat_session_id: Uuid,
|
||||
device_id: String,
|
||||
user_id: Uuid,
|
||||
created_at: DateTime<Utc>,
|
||||
finished_at: Option<DateTime<Utc>>,
|
||||
is_synapse_admin: bool,
|
||||
compat_sso_login_id: Option<Uuid>,
|
||||
compat_sso_login_token: Option<String>,
|
||||
compat_sso_login_redirect_uri: Option<String>,
|
||||
compat_sso_login_created_at: Option<DateTime<Utc>>,
|
||||
compat_sso_login_fulfilled_at: Option<DateTime<Utc>>,
|
||||
compat_sso_login_exchanged_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl TryFrom<CompatSessionAndSsoLoginLookup> for (CompatSession, Option<CompatSsoLogin>) {
|
||||
type Error = DatabaseInconsistencyError;
|
||||
|
||||
fn try_from(value: CompatSessionAndSsoLoginLookup) -> Result<Self, Self::Error> {
|
||||
let id = value.compat_session_id.into();
|
||||
let device = Device::try_from(value.device_id).map_err(|e| {
|
||||
DatabaseInconsistencyError::on("compat_sessions")
|
||||
.column("device_id")
|
||||
.row(id)
|
||||
.source(e)
|
||||
})?;
|
||||
|
||||
let state = match value.finished_at {
|
||||
None => CompatSessionState::Valid,
|
||||
Some(finished_at) => CompatSessionState::Finished { finished_at },
|
||||
};
|
||||
|
||||
let session = CompatSession {
|
||||
id,
|
||||
state,
|
||||
user_id: value.user_id.into(),
|
||||
device,
|
||||
created_at: value.created_at,
|
||||
is_synapse_admin: value.is_synapse_admin,
|
||||
};
|
||||
|
||||
match (
|
||||
value.compat_sso_login_id,
|
||||
value.compat_sso_login_token,
|
||||
value.compat_sso_login_redirect_uri,
|
||||
value.compat_sso_login_created_at,
|
||||
value.compat_sso_login_fulfilled_at,
|
||||
value.compat_sso_login_exchanged_at,
|
||||
) {
|
||||
(None, None, None, None, None, None) => Ok((session, None)),
|
||||
(
|
||||
Some(id),
|
||||
Some(login_token),
|
||||
Some(redirect_uri),
|
||||
Some(created_at),
|
||||
fulfilled_at,
|
||||
exchanged_at,
|
||||
) => {
|
||||
let id = id.into();
|
||||
let redirect_uri = Url::parse(&redirect_uri).map_err(|e| {
|
||||
DatabaseInconsistencyError::on("compat_sso_logins")
|
||||
.column("redirect_uri")
|
||||
.row(id)
|
||||
.source(e)
|
||||
})?;
|
||||
|
||||
let state = match (fulfilled_at, exchanged_at) {
|
||||
(Some(fulfilled_at), None) => CompatSsoLoginState::Fulfilled {
|
||||
fulfilled_at,
|
||||
session_id: session.id,
|
||||
},
|
||||
(Some(fulfilled_at), Some(exchanged_at)) => CompatSsoLoginState::Exchanged {
|
||||
fulfilled_at,
|
||||
exchanged_at,
|
||||
session_id: session.id,
|
||||
},
|
||||
_ => return Err(DatabaseInconsistencyError::on("compat_sso_logins").row(id)),
|
||||
};
|
||||
|
||||
let login = CompatSsoLogin {
|
||||
id,
|
||||
redirect_uri,
|
||||
login_token,
|
||||
created_at,
|
||||
state,
|
||||
};
|
||||
|
||||
Ok((session, Some(login)))
|
||||
}
|
||||
_ => Err(DatabaseInconsistencyError::on("compat_sso_logins").row(id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'c> CompatSessionRepository for PgCompatSessionRepository<'c> {
|
||||
type Error = DatabaseError;
|
||||
@ -201,4 +302,53 @@ impl<'c> CompatSessionRepository for PgCompatSessionRepository<'c> {
|
||||
|
||||
Ok(compat_session)
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "db.compat_session.list_paginated",
|
||||
skip_all,
|
||||
fields(
|
||||
db.statement,
|
||||
%user.id,
|
||||
),
|
||||
err,
|
||||
)]
|
||||
async fn list_paginated(
|
||||
&mut self,
|
||||
user: &User,
|
||||
pagination: Pagination,
|
||||
) -> Result<Page<(CompatSession, Option<CompatSsoLogin>)>, Self::Error> {
|
||||
let mut query = QueryBuilder::new(
|
||||
r#"
|
||||
SELECT cs.compat_session_id
|
||||
, cs.device_id
|
||||
, cs.user_id
|
||||
, cs.created_at
|
||||
, cs.finished_at
|
||||
, cs.is_synapse_admin
|
||||
, cl.compat_sso_login_id
|
||||
, cl.login_token as compat_sso_login_token
|
||||
, cl.redirect_uri as compat_sso_login_redirect_uri
|
||||
, cl.created_at as compat_sso_login_created_at
|
||||
, cl.fulfilled_at as compat_sso_login_fulfilled_at
|
||||
, cl.exchanged_at as compat_sso_login_exchanged_at
|
||||
|
||||
FROM compat_sessions cs
|
||||
LEFT JOIN compat_sso_logins cl USING (compat_session_id)
|
||||
"#,
|
||||
);
|
||||
|
||||
query
|
||||
.push(" WHERE cs.user_id = ")
|
||||
.push_bind(Uuid::from(user.id))
|
||||
.generate_pagination("cs.compat_session_id", pagination);
|
||||
|
||||
let edges: Vec<CompatSessionAndSsoLoginLookup> = query
|
||||
.build_query_as()
|
||||
.traced()
|
||||
.fetch_all(&mut *self.conn)
|
||||
.await?;
|
||||
|
||||
let page = pagination.process(edges).try_map(TryFrom::try_from)?;
|
||||
Ok(page)
|
||||
}
|
||||
}
|
||||
|
@ -13,11 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mas_data_model::{CompatSession, Device, User};
|
||||
use mas_data_model::{CompatSession, CompatSsoLogin, Device, User};
|
||||
use rand_core::RngCore;
|
||||
use ulid::Ulid;
|
||||
|
||||
use crate::{repository_impl, Clock};
|
||||
use crate::{repository_impl, Clock, Page, Pagination};
|
||||
|
||||
/// A [`CompatSessionRepository`] helps interacting with
|
||||
/// [`CompatSessionRepository`] saved in the storage backend
|
||||
@ -80,6 +80,24 @@ pub trait CompatSessionRepository: Send + Sync {
|
||||
clock: &dyn Clock,
|
||||
compat_session: CompatSession,
|
||||
) -> Result<CompatSession, Self::Error>;
|
||||
|
||||
/// Get a paginated list of compat sessions for a user
|
||||
///
|
||||
/// Returns a page of compat sessions, with the associated SSO logins if any
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `user`: The user to get the compat sessions for
|
||||
/// * `pagination`: The pagination parameters
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Self::Error`] if the underlying repository fails
|
||||
async fn list_paginated(
|
||||
&mut self,
|
||||
user: &User,
|
||||
pagination: Pagination,
|
||||
) -> Result<Page<(CompatSession, Option<CompatSsoLogin>)>, Self::Error>;
|
||||
}
|
||||
|
||||
repository_impl!(CompatSessionRepository:
|
||||
@ -99,4 +117,10 @@ repository_impl!(CompatSessionRepository:
|
||||
clock: &dyn Clock,
|
||||
compat_session: CompatSession,
|
||||
) -> Result<CompatSession, Self::Error>;
|
||||
|
||||
async fn list_paginated(
|
||||
&mut self,
|
||||
user: &User,
|
||||
pagination: Pagination,
|
||||
) -> Result<Page<(CompatSession, Option<CompatSsoLogin>)>, Self::Error>;
|
||||
);
|
||||
|
@ -22,15 +22,15 @@ limitations under the License.
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>matrix-authentication-service</title>
|
||||
<script>
|
||||
window.APP_CONFIG = JSON.parse('{root: "/app/"}');
|
||||
<script type="application/javascript">
|
||||
window.APP_CONFIG = JSON.parse('{"root": "/account/"}');
|
||||
(function () {
|
||||
const query = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
function handleChange(list) {
|
||||
if (list.matches) {
|
||||
document.documentElement.classList.add("dark");
|
||||
document.documentElement.classList.add("cpd-theme-dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
document.documentElement.classList.remove("cpd-theme-dark");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,6 +143,39 @@ type CompatSession implements Node & CreationEvent {
|
||||
When the session ended.
|
||||
"""
|
||||
finishedAt: DateTime
|
||||
"""
|
||||
The associated SSO login, if any.
|
||||
"""
|
||||
ssoLogin: CompatSsoLogin
|
||||
}
|
||||
|
||||
type CompatSessionConnection {
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [CompatSessionEdge!]!
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [CompatSession!]!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type CompatSessionEdge {
|
||||
"""
|
||||
The item at the end of the edge
|
||||
"""
|
||||
node: CompatSession!
|
||||
"""
|
||||
A cursor for use in pagination
|
||||
"""
|
||||
cursor: String!
|
||||
}
|
||||
|
||||
"""
|
||||
@ -826,6 +859,15 @@ type User implements Node {
|
||||
last: Int
|
||||
): CompatSsoLoginConnection!
|
||||
"""
|
||||
Get the list of compatibility sessions, chronologically sorted
|
||||
"""
|
||||
compatSessions(
|
||||
after: String
|
||||
before: String
|
||||
first: Int
|
||||
last: Int
|
||||
): CompatSessionConnection!
|
||||
"""
|
||||
Get the list of active browser sessions, chronologically sorted
|
||||
"""
|
||||
browserSessions(
|
||||
|
@ -12,14 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { Control, Field, Root, Submit } from "@vector-im/compound-web";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useRef, useTransition } from "react";
|
||||
import { useTransition } from "react";
|
||||
|
||||
import { graphql } from "../gql";
|
||||
|
||||
import Input from "./Input";
|
||||
import Typography from "./Typography";
|
||||
|
||||
const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
@ -45,7 +44,8 @@ const AddEmailForm: React.FC<{ userId: string; onAdd?: () => void }> = ({
|
||||
userId,
|
||||
onAdd,
|
||||
}) => {
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
// TODO: there is a problem with refs in compound
|
||||
//const formRef = useRef<HTMLFormElement>(null);
|
||||
const [addEmailResult, addEmail] = useAtom(addUserEmailAtom);
|
||||
const [pending, startTransition] = useTransition();
|
||||
|
||||
@ -65,7 +65,7 @@ const AddEmailForm: React.FC<{ userId: string; onAdd?: () => void }> = ({
|
||||
onAdd?.();
|
||||
|
||||
// Reset the form
|
||||
formRef.current?.reset();
|
||||
//formRef.current?.reset();
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -97,18 +97,12 @@ const AddEmailForm: React.FC<{ userId: string; onAdd?: () => void }> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form className="flex" onSubmit={handleSubmit} ref={formRef}>
|
||||
<Input
|
||||
className="flex-1 mr-2"
|
||||
disabled={pending}
|
||||
type="email"
|
||||
inputMode="email"
|
||||
name="email"
|
||||
/>
|
||||
<Button kind="primary" disabled={pending} type="submit">
|
||||
Add
|
||||
</Button>
|
||||
</form>
|
||||
<Root className="flex" onSubmit={handleSubmit}>
|
||||
<Field name="email" className="flex-1 mr-2">
|
||||
<Control disabled={pending} type="email" inputMode="email" />
|
||||
</Field>
|
||||
<Submit disabled={pending}>Add</Submit>
|
||||
</Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -17,7 +17,9 @@ type Props = {
|
||||
};
|
||||
|
||||
const BlockList: React.FC<Props> = ({ children }) => {
|
||||
return <div className="my-2">{children}</div>;
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4 group content-start">{children}</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockList;
|
||||
|
@ -92,7 +92,7 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Block className="my-4 flex items-center">
|
||||
<Block className="flex items-center">
|
||||
<IconWebBrowser className="mr-4 session-icon" />
|
||||
<div className="flex-1">
|
||||
<Body size="md" weight="medium">
|
||||
|
@ -24,27 +24,23 @@ import Block from "./Block";
|
||||
import DateTime from "./DateTime";
|
||||
import { Body, Bold, Code } from "./Typography";
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment CompatSsoLogin_login on CompatSsoLogin {
|
||||
const LOGIN_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment CompatSession_sso_login on CompatSsoLogin {
|
||||
id
|
||||
redirectUri
|
||||
createdAt
|
||||
session {
|
||||
id
|
||||
...CompatSsoLogin_session
|
||||
createdAt
|
||||
deviceId
|
||||
finishedAt
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const SESSION_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment CompatSsoLogin_session on CompatSession {
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment CompatSession_session on CompatSession {
|
||||
id
|
||||
createdAt
|
||||
deviceId
|
||||
finishedAt
|
||||
ssoLogin {
|
||||
id
|
||||
...CompatSession_sso_login
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
@ -54,7 +50,7 @@ const END_SESSION_MUTATION = graphql(/* GraphQL */ `
|
||||
status
|
||||
compatSession {
|
||||
id
|
||||
...CompatSsoLogin_session
|
||||
finishedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,15 +68,11 @@ const endCompatSessionFamily = atomFamily((id: string) => {
|
||||
return endCompatSessionAtom;
|
||||
});
|
||||
|
||||
type Props = {
|
||||
login: FragmentType<typeof FRAGMENT>;
|
||||
};
|
||||
|
||||
const CompatSession: React.FC<{
|
||||
session: FragmentType<typeof SESSION_FRAGMENT>;
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
}> = ({ session }) => {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const data = useFragment(SESSION_FRAGMENT, session);
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const endCompatSession = useSetAtom(endCompatSessionFamily(data.id));
|
||||
|
||||
const onSessionEnd = (): void => {
|
||||
@ -90,7 +82,7 @@ const CompatSession: React.FC<{
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Block className="p-4 bg-grey-25 dark:bg-grey-450 rounded-lg">
|
||||
<Body>
|
||||
Started: <DateTime datetime={data.createdAt} />
|
||||
</Body>
|
||||
@ -102,6 +94,7 @@ const CompatSession: React.FC<{
|
||||
<Body>
|
||||
Device ID: <Code>{data.deviceId}</Code>
|
||||
</Body>
|
||||
{data.ssoLogin && <CompatSsoLogin login={data.ssoLogin} />}
|
||||
{data.finishedAt ? null : (
|
||||
<Button
|
||||
className="mt-2"
|
||||
@ -113,24 +106,22 @@ const CompatSession: React.FC<{
|
||||
End session
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CompatSsoLogin: React.FC<Props> = ({ login }) => {
|
||||
const data = useFragment(FRAGMENT, login);
|
||||
|
||||
return (
|
||||
<Block>
|
||||
<Body>
|
||||
Requested: <DateTime datetime={data.createdAt} />
|
||||
</Body>
|
||||
<Body>
|
||||
Redirect URI: <Bold>{data.redirectUri}</Bold>
|
||||
</Body>
|
||||
{data.session && <CompatSession session={data.session} />}
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompatSsoLogin;
|
||||
const CompatSsoLogin: React.FC<{
|
||||
login: FragmentType<typeof LOGIN_FRAGMENT>;
|
||||
}> = ({ login }) => {
|
||||
const data = useFragment(LOGIN_FRAGMENT, login);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Body>
|
||||
Redirect URI: <Bold>{data.redirectUri}</Bold>
|
||||
</Body>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompatSession;
|
@ -28,13 +28,13 @@ import {
|
||||
import { isErr, isOk, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
import BlockList from "./BlockList";
|
||||
import CompatSsoLogin from "./CompatSsoLogin";
|
||||
import CompatSession from "./CompatSession";
|
||||
import GraphQLError from "./GraphQLError";
|
||||
import PaginationControls from "./PaginationControls";
|
||||
import { Title } from "./Typography";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query CompatSsoLoginList(
|
||||
query CompatSessionList(
|
||||
$userId: ID!
|
||||
$first: Int
|
||||
$after: String
|
||||
@ -43,7 +43,7 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
) {
|
||||
user(id: $userId) {
|
||||
id
|
||||
compatSsoLogins(
|
||||
compatSessions(
|
||||
first: $first
|
||||
after: $after
|
||||
last: $last
|
||||
@ -52,7 +52,7 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CompatSsoLogin_login
|
||||
...CompatSession_session
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,23 +69,23 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
|
||||
const currentPaginationAtom = atomForCurrentPagination();
|
||||
|
||||
const compatSsoLoginListFamily = atomFamily((userId: string) => {
|
||||
const compatSsoLoginListQuery = atomWithQuery({
|
||||
const compatSessionListFamily = atomFamily((userId: string) => {
|
||||
const compatSessionListQuery = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }),
|
||||
});
|
||||
|
||||
const compatSsoLoginList = mapQueryAtom(
|
||||
compatSsoLoginListQuery,
|
||||
(data) => data.user?.compatSsoLogins || null
|
||||
const compatSessionList = mapQueryAtom(
|
||||
compatSessionListQuery,
|
||||
(data) => data.user?.compatSessions || null
|
||||
);
|
||||
|
||||
return compatSsoLoginList;
|
||||
return compatSessionList;
|
||||
});
|
||||
|
||||
const pageInfoFamily = atomFamily((userId: string) => {
|
||||
const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => {
|
||||
const result = await get(compatSsoLoginListFamily(userId));
|
||||
const result = await get(compatSessionListFamily(userId));
|
||||
return (isOk(result) && unwrapOk(result)?.pageInfo) || null;
|
||||
});
|
||||
|
||||
@ -100,15 +100,15 @@ const paginationFamily = atomFamily((userId: string) => {
|
||||
return paginationAtom;
|
||||
});
|
||||
|
||||
const CompatSsoLoginList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const CompatSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const result = useAtomValue(compatSsoLoginListFamily(userId));
|
||||
const result = useAtomValue(compatSessionListFamily(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)
|
||||
const compatSessionList = unwrapOk(result);
|
||||
if (compatSessionList === null)
|
||||
return <>Failed to load list of compatibility sessions.</>;
|
||||
|
||||
const paginate = (pagination: Pagination): void => {
|
||||
@ -125,11 +125,11 @@ const CompatSsoLoginList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
onNext={nextPage ? (): void => paginate(nextPage) : null}
|
||||
disabled={pending}
|
||||
/>
|
||||
{compatSsoLoginList.edges.map((n) => (
|
||||
<CompatSsoLogin login={n.node} key={n.node.id} />
|
||||
{compatSessionList.edges.map((n) => (
|
||||
<CompatSession session={n.node} key={n.node.id} />
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompatSsoLoginList;
|
||||
export default CompatSessionList;
|
@ -1,50 +0,0 @@
|
||||
// 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 type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
import Input from "./Input";
|
||||
|
||||
const meta = {
|
||||
title: "UI/Input",
|
||||
component: Input,
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
value: {
|
||||
control: "text",
|
||||
},
|
||||
placeholder: {
|
||||
control: "text",
|
||||
},
|
||||
disabled: {
|
||||
control: "boolean",
|
||||
},
|
||||
},
|
||||
args: {
|
||||
value: "",
|
||||
placeholder: "Placeholder",
|
||||
disabled: false,
|
||||
},
|
||||
} satisfies Meta<typeof Input>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Input>;
|
||||
|
||||
export const Basic: Story = {};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabled: true,
|
||||
},
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
// 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.
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
} & React.HTMLProps<HTMLInputElement>;
|
||||
|
||||
const Input: React.FC<Props> = ({ disabled, className, ...props }) => {
|
||||
const disabledClass = disabled
|
||||
? "bg-grey-100 dark:bg-grey-400"
|
||||
: "bg-white dark:bg-grey-450";
|
||||
const fullClassName = `${className} px-2 py-1 border-2 border-grey-100 dark:border-grey-400 dark:text-white placeholder-grey-100 dark:placeholder-grey-150 rounded-lg ${disabledClass}`;
|
||||
return <input disabled={disabled} className={fullClassName} {...props} />;
|
||||
};
|
||||
|
||||
export default Input;
|
@ -19,8 +19,8 @@ const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<NavBar className="nav-bar container">
|
||||
<NavItem route={{ type: "home" }}>Home</NavItem>
|
||||
<NavItem route={{ type: "account" }}>My Account</NavItem>
|
||||
<NavItem route={{ type: "home" }}>Sessions</NavItem>
|
||||
<NavItem route={{ type: "account" }}>Emails</NavItem>
|
||||
</NavBar>
|
||||
|
||||
<hr className="my-2" />
|
||||
@ -33,6 +33,8 @@ const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
|
||||
<img
|
||||
className="inline my-2"
|
||||
height={32}
|
||||
width={75}
|
||||
src="https://matrix.org/images/matrix-logo.svg"
|
||||
alt="Matrix.org"
|
||||
/>
|
||||
|
@ -100,10 +100,11 @@ const OAuth2Session: React.FC<Props> = ({ session }) => {
|
||||
|
||||
return (
|
||||
<Block
|
||||
className={
|
||||
data.finishedAt &&
|
||||
"opacity-50 group-hover:opacity-100 transition-opacity"
|
||||
}
|
||||
className={`p-4 bg-grey-25 dark:bg-grey-450 rounded-lg ${
|
||||
data.finishedAt
|
||||
? "opacity-50 group-hover:opacity-100 transition-opacity"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<Typography variant="body" bold>
|
||||
<Link
|
||||
|
@ -28,7 +28,7 @@ const PaginationControls: React.FC<Props> = ({
|
||||
disabled,
|
||||
}) => {
|
||||
return (
|
||||
<div className="grid items-center grid-cols-3 gap-2 my-2">
|
||||
<div className="grid items-center grid-cols-3 gap-2">
|
||||
<Button
|
||||
kind="secondary"
|
||||
size="sm"
|
||||
|
@ -12,17 +12,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import {
|
||||
Button,
|
||||
Control,
|
||||
Field,
|
||||
Label,
|
||||
Message,
|
||||
Root as Form,
|
||||
Submit,
|
||||
} from "@vector-im/compound-web";
|
||||
import { atom, useAtom, useSetAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useRef, useTransition } from "react";
|
||||
import { useTransition } from "react";
|
||||
|
||||
import { FragmentType, graphql, useFragment } from "../gql";
|
||||
|
||||
import Block from "./Block";
|
||||
import DateTime from "./DateTime";
|
||||
import Input from "./Input";
|
||||
import Typography from "./Typography";
|
||||
|
||||
// This component shows a single user email address, with controls to verify it,
|
||||
@ -168,7 +175,8 @@ const UserEmail: React.FC<{
|
||||
);
|
||||
const setPrimaryEmail = useSetAtom(setPrimaryEmailFamily(data.id));
|
||||
const removeEmail = useSetAtom(removeEmailFamily(data.id));
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
// TODO: compound doesn't forward the refs properly
|
||||
// const fieldRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
e.preventDefault();
|
||||
@ -177,7 +185,7 @@ const UserEmail: React.FC<{
|
||||
startTransition(() => {
|
||||
verifyEmail(code).then((result) => {
|
||||
// Clear the form
|
||||
formRef.current?.reset();
|
||||
e.currentTarget?.reset();
|
||||
|
||||
if (result.data?.verifyEmail.status === "VERIFIED") {
|
||||
// Call the onSetPrimary callback if provided
|
||||
@ -191,7 +199,7 @@ const UserEmail: React.FC<{
|
||||
const onResendClick = (): void => {
|
||||
startTransition(() => {
|
||||
resendVerificationEmail().then(() => {
|
||||
formRef.current?.code.focus();
|
||||
// TODO: fieldRef.current?.focus();
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -220,61 +228,70 @@ const UserEmail: React.FC<{
|
||||
verifyEmailResult.data?.verifyEmail.status === "INVALID_CODE";
|
||||
|
||||
return (
|
||||
<Block highlight={highlight}>
|
||||
<Block
|
||||
highlight={highlight}
|
||||
className="grid grid-col-1 gap-2 pb-4 border-b-2 border-b-grey-200"
|
||||
>
|
||||
{isPrimary && (
|
||||
<Typography variant="body" bold>
|
||||
Primary
|
||||
</Typography>
|
||||
)}
|
||||
<div className="flex justify-between items-center">
|
||||
<Typography variant="caption" bold className="flex-1">
|
||||
{data.email}
|
||||
</Typography>
|
||||
{!isPrimary && (
|
||||
<>
|
||||
{/* The primary email can only be set if the email was verified */}
|
||||
{data.confirmedAt && (
|
||||
<Button
|
||||
disabled={pending}
|
||||
onClick={onSetPrimaryClick}
|
||||
className="ml-2"
|
||||
>
|
||||
Set primary
|
||||
</Button>
|
||||
)}
|
||||
<Button disabled={pending} onClick={onRemoveClick} className="ml-2">
|
||||
Remove
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Typography variant="caption" bold className="flex-1">
|
||||
{data.email}
|
||||
</Typography>
|
||||
{data.confirmedAt ? (
|
||||
<Typography variant="micro">
|
||||
Verified <DateTime datetime={data.confirmedAt} />
|
||||
</Typography>
|
||||
) : (
|
||||
<form
|
||||
onSubmit={onFormSubmit}
|
||||
className="mt-2 grid grid-cols-2 gap-2"
|
||||
ref={formRef}
|
||||
>
|
||||
<Input
|
||||
className="col-span-2"
|
||||
name="code"
|
||||
placeholder="Code"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
/>
|
||||
<Form onSubmit={onFormSubmit} className="grid grid-cols-2 gap-2">
|
||||
<Field name="code" className="col-span-2">
|
||||
<Label>Code</Label>
|
||||
<Control
|
||||
// ref={fieldRef}
|
||||
placeholder="xxxxxx"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
/>
|
||||
</Field>
|
||||
{invalidCode && (
|
||||
<div className="col-span-2 text-alert font-bold">Invalid code</div>
|
||||
<Message className="col-span-2 text-alert font-bold">
|
||||
Invalid code
|
||||
</Message>
|
||||
)}
|
||||
<Button type="submit" disabled={pending}>
|
||||
<Submit size="sm" type="submit" disabled={pending}>
|
||||
Submit
|
||||
</Button>
|
||||
<Button disabled={pending || emailSent} onClick={onResendClick}>
|
||||
</Submit>
|
||||
<Button
|
||||
size="sm"
|
||||
kind="secondary"
|
||||
disabled={pending || emailSent}
|
||||
onClick={onResendClick}
|
||||
>
|
||||
{emailSent ? "Sent!" : "Resend"}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
{!isPrimary && (
|
||||
<div className="flex justify-between items-center">
|
||||
{/* The primary email can only be set if the email was verified */}
|
||||
{data.confirmedAt ? (
|
||||
<Button size="sm" disabled={pending} onClick={onSetPrimaryClick}>
|
||||
Set primary
|
||||
</Button>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<Button
|
||||
kind="destructive"
|
||||
size="sm"
|
||||
disabled={pending}
|
||||
onClick={onRemoveClick}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Block>
|
||||
);
|
||||
|
@ -25,14 +25,14 @@ const documents = {
|
||||
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":
|
||||
types.CompatSsoLogin_LoginFragmentDoc,
|
||||
"\n fragment CompatSsoLogin_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n }\n":
|
||||
types.CompatSsoLogin_SessionFragmentDoc,
|
||||
"\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n ...CompatSsoLogin_session\n }\n }\n }\n":
|
||||
"\n fragment CompatSession_sso_login on CompatSsoLogin {\n id\n redirectUri\n }\n":
|
||||
types.CompatSession_Sso_LoginFragmentDoc,
|
||||
"\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":
|
||||
types.CompatSession_SessionFragmentDoc,
|
||||
"\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n finishedAt\n }\n }\n }\n":
|
||||
types.EndCompatSessionDocument,
|
||||
"\n query CompatSsoLoginList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSsoLogins(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.CompatSsoLoginListDocument,
|
||||
"\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 pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.CompatSessionListDocument,
|
||||
"\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 EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n":
|
||||
@ -115,26 +115,26 @@ 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 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"
|
||||
): typeof documents["\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"];
|
||||
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 CompatSsoLogin_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n }\n"
|
||||
): typeof documents["\n fragment CompatSsoLogin_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\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 ...CompatSsoLogin_session\n }\n }\n }\n"
|
||||
): typeof documents["\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n ...CompatSsoLogin_session\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 CompatSsoLoginList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSsoLogins(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"
|
||||
): typeof documents["\n query CompatSsoLoginList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSsoLogins(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n\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 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 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.
|
||||
*/
|
||||
|
@ -132,10 +132,31 @@ export type CompatSession = CreationEvent &
|
||||
finishedAt?: Maybe<Scalars["DateTime"]["output"]>;
|
||||
/** ID of the object. */
|
||||
id: Scalars["ID"]["output"];
|
||||
/** The associated SSO login, if any. */
|
||||
ssoLogin?: Maybe<CompatSsoLogin>;
|
||||
/** The user authorized for this session. */
|
||||
user: User;
|
||||
};
|
||||
|
||||
export type CompatSessionConnection = {
|
||||
__typename?: "CompatSessionConnection";
|
||||
/** A list of edges. */
|
||||
edges: Array<CompatSessionEdge>;
|
||||
/** A list of nodes. */
|
||||
nodes: Array<CompatSession>;
|
||||
/** Information to aid in pagination. */
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
/** An edge in a connection. */
|
||||
export type CompatSessionEdge = {
|
||||
__typename?: "CompatSessionEdge";
|
||||
/** A cursor for use in pagination */
|
||||
cursor: Scalars["String"]["output"];
|
||||
/** The item at the end of the edge */
|
||||
node: CompatSession;
|
||||
};
|
||||
|
||||
/**
|
||||
* A compat SSO login represents a login done through the legacy Matrix login
|
||||
* API, via the `m.login.sso` login method.
|
||||
@ -623,6 +644,8 @@ export type User = Node & {
|
||||
__typename?: "User";
|
||||
/** Get the list of active browser sessions, chronologically sorted */
|
||||
browserSessions: BrowserSessionConnection;
|
||||
/** Get the list of compatibility sessions, chronologically sorted */
|
||||
compatSessions: CompatSessionConnection;
|
||||
/** Get the list of compatibility SSO logins, chronologically sorted */
|
||||
compatSsoLogins: CompatSsoLoginConnection;
|
||||
/** Get the list of emails, chronologically sorted */
|
||||
@ -649,6 +672,14 @@ export type UserBrowserSessionsArgs = {
|
||||
last?: InputMaybe<Scalars["Int"]["input"]>;
|
||||
};
|
||||
|
||||
/** A user is an individual's account. */
|
||||
export type UserCompatSessionsArgs = {
|
||||
after?: InputMaybe<Scalars["String"]["input"]>;
|
||||
before?: InputMaybe<Scalars["String"]["input"]>;
|
||||
first?: InputMaybe<Scalars["Int"]["input"]>;
|
||||
last?: InputMaybe<Scalars["Int"]["input"]>;
|
||||
};
|
||||
|
||||
/** A user is an individual's account. */
|
||||
export type UserCompatSsoLoginsArgs = {
|
||||
after?: InputMaybe<Scalars["String"]["input"]>;
|
||||
@ -859,33 +890,26 @@ export type BrowserSessionListQuery = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type CompatSsoLogin_LoginFragment = {
|
||||
export type CompatSession_Sso_LoginFragment = {
|
||||
__typename?: "CompatSsoLogin";
|
||||
id: string;
|
||||
redirectUri: any;
|
||||
createdAt: any;
|
||||
session?:
|
||||
| ({
|
||||
__typename?: "CompatSession";
|
||||
id: string;
|
||||
createdAt: any;
|
||||
deviceId: string;
|
||||
finishedAt?: any | null;
|
||||
} & {
|
||||
" $fragmentRefs"?: {
|
||||
CompatSsoLogin_SessionFragment: CompatSsoLogin_SessionFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
} & { " $fragmentName"?: "CompatSsoLogin_LoginFragment" };
|
||||
} & { " $fragmentName"?: "CompatSession_Sso_LoginFragment" };
|
||||
|
||||
export type CompatSsoLogin_SessionFragment = {
|
||||
export type CompatSession_SessionFragment = {
|
||||
__typename?: "CompatSession";
|
||||
id: string;
|
||||
createdAt: any;
|
||||
deviceId: string;
|
||||
finishedAt?: any | null;
|
||||
} & { " $fragmentName"?: "CompatSsoLogin_SessionFragment" };
|
||||
ssoLogin?:
|
||||
| ({ __typename?: "CompatSsoLogin"; id: string } & {
|
||||
" $fragmentRefs"?: {
|
||||
CompatSession_Sso_LoginFragment: CompatSession_Sso_LoginFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
} & { " $fragmentName"?: "CompatSession_SessionFragment" };
|
||||
|
||||
export type EndCompatSessionMutationVariables = Exact<{
|
||||
id: Scalars["ID"]["input"];
|
||||
@ -896,17 +920,15 @@ export type EndCompatSessionMutation = {
|
||||
endCompatSession: {
|
||||
__typename?: "EndCompatSessionPayload";
|
||||
status: EndCompatSessionStatus;
|
||||
compatSession?:
|
||||
| ({ __typename?: "CompatSession"; id: string } & {
|
||||
" $fragmentRefs"?: {
|
||||
CompatSsoLogin_SessionFragment: CompatSsoLogin_SessionFragment;
|
||||
};
|
||||
})
|
||||
| null;
|
||||
compatSession?: {
|
||||
__typename?: "CompatSession";
|
||||
id: string;
|
||||
finishedAt?: any | null;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type CompatSsoLoginListQueryVariables = Exact<{
|
||||
export type CompatSessionListQueryVariables = Exact<{
|
||||
userId: Scalars["ID"]["input"];
|
||||
first?: InputMaybe<Scalars["Int"]["input"]>;
|
||||
after?: InputMaybe<Scalars["String"]["input"]>;
|
||||
@ -914,18 +936,18 @@ export type CompatSsoLoginListQueryVariables = Exact<{
|
||||
before?: InputMaybe<Scalars["String"]["input"]>;
|
||||
}>;
|
||||
|
||||
export type CompatSsoLoginListQuery = {
|
||||
export type CompatSessionListQuery = {
|
||||
__typename?: "Query";
|
||||
user?: {
|
||||
__typename?: "User";
|
||||
id: string;
|
||||
compatSsoLogins: {
|
||||
__typename?: "CompatSsoLoginConnection";
|
||||
compatSessions: {
|
||||
__typename?: "CompatSessionConnection";
|
||||
edges: Array<{
|
||||
__typename?: "CompatSsoLoginEdge";
|
||||
node: { __typename?: "CompatSsoLogin"; id: string } & {
|
||||
__typename?: "CompatSessionEdge";
|
||||
node: { __typename?: "CompatSession"; id: string } & {
|
||||
" $fragmentRefs"?: {
|
||||
CompatSsoLogin_LoginFragment: CompatSsoLogin_LoginFragment;
|
||||
CompatSession_SessionFragment: CompatSession_SessionFragment;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
@ -1227,34 +1249,12 @@ export const BrowserSession_SessionFragmentDoc = {
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<BrowserSession_SessionFragment, unknown>;
|
||||
export const CompatSsoLogin_SessionFragmentDoc = {
|
||||
export const CompatSession_Sso_LoginFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSsoLogin_session" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSession" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<CompatSsoLogin_SessionFragment, unknown>;
|
||||
export const CompatSsoLogin_LoginFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSsoLogin_login" },
|
||||
name: { kind: "Name", value: "CompatSession_sso_login" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSsoLogin" },
|
||||
@ -1264,30 +1264,17 @@ export const CompatSsoLogin_LoginFragmentDoc = {
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "redirectUri" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "session" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "CompatSsoLogin_session" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<CompatSession_Sso_LoginFragment, unknown>;
|
||||
export const CompatSession_SessionFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSsoLogin_session" },
|
||||
name: { kind: "Name", value: "CompatSession_session" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSession" },
|
||||
@ -1299,11 +1286,40 @@ export const CompatSsoLogin_LoginFragmentDoc = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "ssoLogin" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "CompatSession_sso_login" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSession_sso_login" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSsoLogin" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "redirectUri" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<CompatSsoLogin_LoginFragment, unknown>;
|
||||
} as unknown as DocumentNode<CompatSession_SessionFragment, unknown>;
|
||||
export const OAuth2Session_SessionFragmentDoc = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
@ -1937,8 +1953,8 @@ export const EndCompatSessionDocument = {
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "CompatSsoLogin_session" },
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "finishedAt" },
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -1949,35 +1965,18 @@ export const EndCompatSessionDocument = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSsoLogin_session" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSession" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
EndCompatSessionMutation,
|
||||
EndCompatSessionMutationVariables
|
||||
>;
|
||||
export const CompatSsoLoginListDocument = {
|
||||
export const CompatSessionListDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "query",
|
||||
name: { kind: "Name", value: "CompatSsoLoginList" },
|
||||
name: { kind: "Name", value: "CompatSessionList" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
@ -2042,7 +2041,7 @@ export const CompatSsoLoginListDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "compatSsoLogins" },
|
||||
name: { kind: "Name", value: "compatSessions" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
@ -2100,7 +2099,7 @@ export const CompatSsoLoginListDocument = {
|
||||
kind: "FragmentSpread",
|
||||
name: {
|
||||
kind: "Name",
|
||||
value: "CompatSsoLogin_login",
|
||||
value: "CompatSession_session",
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -2145,7 +2144,22 @@ export const CompatSsoLoginListDocument = {
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSsoLogin_session" },
|
||||
name: { kind: "Name", value: "CompatSession_sso_login" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSsoLogin" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "redirectUri" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSession_session" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSession" },
|
||||
@ -2157,36 +2171,17 @@ export const CompatSsoLoginListDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "FragmentDefinition",
|
||||
name: { kind: "Name", value: "CompatSsoLogin_login" },
|
||||
typeCondition: {
|
||||
kind: "NamedType",
|
||||
name: { kind: "Name", value: "CompatSsoLogin" },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "redirectUri" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "session" },
|
||||
name: { kind: "Name", value: "ssoLogin" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{
|
||||
kind: "FragmentSpread",
|
||||
name: { kind: "Name", value: "CompatSsoLogin_session" },
|
||||
name: { kind: "Name", value: "CompatSession_sso_login" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceId" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@ -2195,8 +2190,8 @@ export const CompatSsoLoginListDocument = {
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
CompatSsoLoginListQuery,
|
||||
CompatSsoLoginListQueryVariables
|
||||
CompatSessionListQuery,
|
||||
CompatSessionListQueryVariables
|
||||
>;
|
||||
export const EndOAuth2SessionDocument = {
|
||||
kind: "Document",
|
||||
|
@ -295,6 +295,15 @@ export default {
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: "ssoLogin",
|
||||
type: {
|
||||
kind: "OBJECT",
|
||||
name: "CompatSsoLogin",
|
||||
ofType: null,
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: "user",
|
||||
type: {
|
||||
@ -319,6 +328,91 @@ export default {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "OBJECT",
|
||||
name: "CompatSessionConnection",
|
||||
fields: [
|
||||
{
|
||||
name: "edges",
|
||||
type: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "LIST",
|
||||
ofType: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "OBJECT",
|
||||
name: "CompatSessionEdge",
|
||||
ofType: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: "nodes",
|
||||
type: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "LIST",
|
||||
ofType: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "OBJECT",
|
||||
name: "CompatSession",
|
||||
ofType: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: "pageInfo",
|
||||
type: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "OBJECT",
|
||||
name: "PageInfo",
|
||||
ofType: null,
|
||||
},
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
],
|
||||
interfaces: [],
|
||||
},
|
||||
{
|
||||
kind: "OBJECT",
|
||||
name: "CompatSessionEdge",
|
||||
fields: [
|
||||
{
|
||||
name: "cursor",
|
||||
type: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "SCALAR",
|
||||
name: "Any",
|
||||
},
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
name: "node",
|
||||
type: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "OBJECT",
|
||||
name: "CompatSession",
|
||||
ofType: null,
|
||||
},
|
||||
},
|
||||
args: [],
|
||||
},
|
||||
],
|
||||
interfaces: [],
|
||||
},
|
||||
{
|
||||
kind: "OBJECT",
|
||||
name: "CompatSsoLogin",
|
||||
@ -1878,6 +1972,47 @@ export default {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "compatSessions",
|
||||
type: {
|
||||
kind: "NON_NULL",
|
||||
ofType: {
|
||||
kind: "OBJECT",
|
||||
name: "CompatSessionConnection",
|
||||
ofType: null,
|
||||
},
|
||||
},
|
||||
args: [
|
||||
{
|
||||
name: "after",
|
||||
type: {
|
||||
kind: "SCALAR",
|
||||
name: "Any",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "before",
|
||||
type: {
|
||||
kind: "SCALAR",
|
||||
name: "Any",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "first",
|
||||
type: {
|
||||
kind: "SCALAR",
|
||||
name: "Any",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "last",
|
||||
type: {
|
||||
kind: "SCALAR",
|
||||
name: "Any",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "compatSsoLogins",
|
||||
type: {
|
||||
|
@ -37,11 +37,7 @@ const CurrentUserAccount: React.FC = () => {
|
||||
const userId = unwrapOk(result);
|
||||
if (userId === null) return <NotLoggedIn />;
|
||||
|
||||
return (
|
||||
<div className="w-96 mx-auto">
|
||||
<UserAccount id={userId} />
|
||||
</div>
|
||||
);
|
||||
return <UserAccount id={userId} />;
|
||||
};
|
||||
|
||||
export default CurrentUserAccount;
|
||||
|
@ -16,7 +16,7 @@ import { useAtomValue } from "jotai";
|
||||
|
||||
import { currentUserIdAtom } from "../atoms";
|
||||
import BrowserSessionList from "../components/BrowserSessionList";
|
||||
import CompatSsoLoginList from "../components/CompatSsoLoginList";
|
||||
import CompatSessionList from "../components/CompatSessionList";
|
||||
import GraphQLError from "../components/GraphQLError";
|
||||
import NotLoggedIn from "../components/NotLoggedIn";
|
||||
import OAuth2SessionList from "../components/OAuth2SessionList";
|
||||
@ -33,9 +33,9 @@ const Home: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<UserGreeting userId={currentUserId} />
|
||||
<div className="mt-4 grid gap-1">
|
||||
<div className="mt-4 grid gap-8">
|
||||
<OAuth2SessionList userId={currentUserId} />
|
||||
<CompatSsoLoginList userId={currentUserId} />
|
||||
<CompatSessionList userId={currentUserId} />
|
||||
<BrowserSessionList userId={currentUserId} />
|
||||
</div>
|
||||
</>
|
||||
|
@ -19,7 +19,7 @@
|
||||
module.exports = {
|
||||
mode: "jit",
|
||||
content: ["./src/**/*.tsx", "./index.html"],
|
||||
darkMode: "class",
|
||||
darkMode: ["class", ".cpd-theme-dark"],
|
||||
theme: {
|
||||
colors: {
|
||||
white: "#FFFFFF",
|
||||
|
Reference in New Issue
Block a user