You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-06 06:02:40 +03:00
WIP my account page
This commit is contained in:
@@ -28,6 +28,7 @@ export const HydrateAtoms = ({ children }: { children: ReactElement }) => {
|
|||||||
const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ `
|
const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ `
|
||||||
query CurrentViewerQuery {
|
query CurrentViewerQuery {
|
||||||
viewer {
|
viewer {
|
||||||
|
__typename
|
||||||
... on User {
|
... on User {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
@@ -52,6 +53,7 @@ export const currentUserIdAtom = atom(async (get) => {
|
|||||||
const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ `
|
const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ `
|
||||||
query CurrentViewerSessionQuery {
|
query CurrentViewerSessionQuery {
|
||||||
viewerSession {
|
viewerSession {
|
||||||
|
__typename
|
||||||
... on BrowserSession {
|
... on BrowserSession {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
@@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
import React, { useRef, useTransition } from "react";
|
import React, { useRef, useTransition } from "react";
|
||||||
import { atomWithMutation } from "jotai-urql";
|
import { atomWithMutation } from "jotai-urql";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom, useSetAtom } from "jotai";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import UserEmail from "./UserEmail";
|
import UserEmail from "./UserEmail";
|
||||||
import Input from "./Input";
|
import Input from "./Input";
|
||||||
import Typography from "./Typography";
|
import Typography from "./Typography";
|
||||||
|
import { emailPageResultFamily } from "./UserEmailList";
|
||||||
|
|
||||||
const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||||
mutation AddEmail($userId: ID!, $email: String!) {
|
mutation AddEmail($userId: ID!, $email: String!) {
|
||||||
@@ -42,12 +43,15 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => {
|
|||||||
const formRef = useRef<HTMLFormElement>(null);
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
const [addEmailResult, addEmail] = useAtom(addUserEmailAtom);
|
const [addEmailResult, addEmail] = useAtom(addUserEmailAtom);
|
||||||
const [pending, startTransition] = useTransition();
|
const [pending, startTransition] = useTransition();
|
||||||
|
// XXX: is this the right way to do this?
|
||||||
|
const refetchList = useSetAtom(emailPageResultFamily(userId));
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const email = e.currentTarget.email.value;
|
const email = e.currentTarget.email.value;
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
addEmail({ userId, email }).then(() => {
|
addEmail({ userId, email }).then(() => {
|
||||||
|
refetchList();
|
||||||
if (formRef.current) {
|
if (formRef.current) {
|
||||||
formRef.current.reset();
|
formRef.current.reset();
|
||||||
}
|
}
|
||||||
@@ -59,7 +63,7 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => {
|
|||||||
<>
|
<>
|
||||||
{addEmailResult.data?.addEmail.status === "ADDED" && (
|
{addEmailResult.data?.addEmail.status === "ADDED" && (
|
||||||
<>
|
<>
|
||||||
<div className="p-4">
|
<div className="pt-4">
|
||||||
<Typography variant="subtitle">Email added!</Typography>
|
<Typography variant="subtitle">Email added!</Typography>
|
||||||
</div>
|
</div>
|
||||||
<UserEmail email={addEmailResult.data?.addEmail.email} />
|
<UserEmail email={addEmailResult.data?.addEmail.email} />
|
||||||
@@ -67,7 +71,7 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => {
|
|||||||
)}
|
)}
|
||||||
{addEmailResult.data?.addEmail.status === "EXISTS" && (
|
{addEmailResult.data?.addEmail.status === "EXISTS" && (
|
||||||
<>
|
<>
|
||||||
<div className="p-4">
|
<div className="pt-4">
|
||||||
<Typography variant="subtitle">Email already exists!</Typography>
|
<Typography variant="subtitle">Email already exists!</Typography>
|
||||||
</div>
|
</div>
|
||||||
<UserEmail email={addEmailResult.data?.addEmail.email} />
|
<UserEmail email={addEmailResult.data?.addEmail.email} />
|
||||||
|
@@ -15,42 +15,58 @@
|
|||||||
import BlockList from "./BlockList";
|
import BlockList from "./BlockList";
|
||||||
import BrowserSession from "./BrowserSession";
|
import BrowserSession from "./BrowserSession";
|
||||||
import { Title } from "./Typography";
|
import { Title } from "./Typography";
|
||||||
import { FragmentType, graphql, useFragment } from "../gql";
|
import { graphql } from "../gql";
|
||||||
|
import { atomFamily } from "jotai/utils";
|
||||||
|
import { atomWithQuery } from "jotai-urql";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
import { currentBrowserSessionIdAtom } from "../atoms";
|
||||||
|
|
||||||
const FRAGMENT = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
fragment BrowserSessionList_user on User {
|
query BrowserSessionList($userId: ID!) {
|
||||||
browserSessions(first: 10) {
|
user(id: $userId) {
|
||||||
edges {
|
id
|
||||||
cursor
|
browserSessions(first: 10) {
|
||||||
node {
|
edges {
|
||||||
id
|
cursor
|
||||||
...BrowserSession_session
|
node {
|
||||||
|
id
|
||||||
|
...BrowserSession_session
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
type Props = {
|
const browserSessionListFamily = atomFamily((userId: string) => {
|
||||||
user: FragmentType<typeof FRAGMENT>;
|
const browserSessionList = atomWithQuery({
|
||||||
currentSessionId: string;
|
query: QUERY,
|
||||||
};
|
getVariables: () => ({ userId }),
|
||||||
|
});
|
||||||
|
return browserSessionList;
|
||||||
|
});
|
||||||
|
|
||||||
const BrowserSessionList: React.FC<Props> = ({ user, currentSessionId }) => {
|
const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||||
const data = useFragment(FRAGMENT, user);
|
const result = useAtomValue(browserSessionListFamily(userId));
|
||||||
|
const currentSessionId = useAtomValue(currentBrowserSessionIdAtom);
|
||||||
|
|
||||||
return (
|
if (result.data?.user?.browserSessions) {
|
||||||
<BlockList>
|
const data = result.data.user.browserSessions;
|
||||||
<Title>List of browser sessions:</Title>
|
return (
|
||||||
{data.browserSessions.edges.map((n) => (
|
<BlockList>
|
||||||
<BrowserSession
|
<Title>List of browser sessions:</Title>
|
||||||
key={n.cursor}
|
{data.edges.map((n) => (
|
||||||
session={n.node}
|
<BrowserSession
|
||||||
isCurrent={n.node.id === currentSessionId}
|
key={n.cursor}
|
||||||
/>
|
session={n.node}
|
||||||
))}
|
isCurrent={n.node.id === currentSessionId}
|
||||||
</BlockList>
|
/>
|
||||||
);
|
))}
|
||||||
|
</BlockList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>Failed to load browser sessions</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BrowserSessionList;
|
export default BrowserSessionList;
|
||||||
|
@@ -15,36 +15,52 @@
|
|||||||
import BlockList from "./BlockList";
|
import BlockList from "./BlockList";
|
||||||
import CompatSsoLogin from "./CompatSsoLogin";
|
import CompatSsoLogin from "./CompatSsoLogin";
|
||||||
import { Title } from "./Typography";
|
import { Title } from "./Typography";
|
||||||
import { FragmentType, graphql, useFragment } from "../gql";
|
import { graphql } from "../gql";
|
||||||
|
import { atomFamily } from "jotai/utils";
|
||||||
|
import { atomWithQuery } from "jotai-urql";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
|
||||||
const FRAGMENT = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
fragment CompatSsoLoginList_user on User {
|
query CompatSsoLoginList($userId: ID!) {
|
||||||
compatSsoLogins(first: 10) {
|
user(id: $userId) {
|
||||||
edges {
|
id
|
||||||
node {
|
compatSsoLogins(first: 10) {
|
||||||
id
|
edges {
|
||||||
...CompatSsoLogin_login
|
node {
|
||||||
|
id
|
||||||
|
...CompatSsoLogin_login
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
type Props = {
|
const compatSsoLoginListFamily = atomFamily((userId: string) => {
|
||||||
user: FragmentType<typeof FRAGMENT>;
|
const compatSsoLoginList = atomWithQuery({
|
||||||
};
|
query: QUERY,
|
||||||
|
getVariables: () => ({ userId }),
|
||||||
|
});
|
||||||
|
|
||||||
const CompatSsoLoginList: React.FC<Props> = ({ user }) => {
|
return compatSsoLoginList;
|
||||||
const data = useFragment(FRAGMENT, user);
|
});
|
||||||
|
|
||||||
return (
|
const CompatSsoLoginList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||||
<BlockList>
|
const result = useAtomValue(compatSsoLoginListFamily(userId));
|
||||||
<Title>List of compatibility sessions:</Title>
|
|
||||||
{data.compatSsoLogins.edges.map((n) => (
|
if (result.data?.user?.compatSsoLogins) {
|
||||||
<CompatSsoLogin login={n.node} key={n.node.id} />
|
const data = result.data.user.compatSsoLogins;
|
||||||
))}
|
return (
|
||||||
</BlockList>
|
<BlockList>
|
||||||
);
|
<Title>List of compatibility sessions:</Title>
|
||||||
|
{data.edges.map((n) => (
|
||||||
|
<CompatSsoLogin login={n.node} key={n.node.id} />
|
||||||
|
))}
|
||||||
|
</BlockList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>Failed to load list of compatibility sessions.</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CompatSsoLoginList;
|
export default CompatSsoLoginList;
|
||||||
|
@@ -12,41 +12,62 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { atomFamily } from "jotai/utils";
|
||||||
|
import { atomWithQuery } from "jotai-urql";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
|
||||||
import BlockList from "./BlockList";
|
import BlockList from "./BlockList";
|
||||||
import OAuth2Session from "./OAuth2Session";
|
import OAuth2Session from "./OAuth2Session";
|
||||||
import { Title } from "./Typography";
|
import { Title } from "./Typography";
|
||||||
|
|
||||||
import { FragmentType, graphql, useFragment } from "../gql";
|
import { graphql } from "../gql";
|
||||||
|
|
||||||
const FRAGMENT = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
fragment OAuth2SessionList_user on User {
|
query OAuth2SessionListQuery($userId: ID!) {
|
||||||
oauth2Sessions(first: 10) {
|
user(id: $userId) {
|
||||||
edges {
|
id
|
||||||
cursor
|
oauth2Sessions(first: 10) {
|
||||||
node {
|
edges {
|
||||||
id
|
cursor
|
||||||
...OAuth2Session_session
|
node {
|
||||||
|
id
|
||||||
|
...OAuth2Session_session
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const oauth2SessionListFamily = atomFamily((userId: string) => {
|
||||||
|
const oauth2SessionList = atomWithQuery({
|
||||||
|
query: QUERY,
|
||||||
|
getVariables: () => ({ userId }),
|
||||||
|
});
|
||||||
|
|
||||||
|
return oauth2SessionList;
|
||||||
|
});
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: FragmentType<typeof FRAGMENT>;
|
userId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const OAuth2SessionList: React.FC<Props> = ({ user }) => {
|
const OAuth2SessionList: React.FC<Props> = ({ userId }) => {
|
||||||
const data = useFragment(FRAGMENT, user);
|
const result = useAtomValue(oauth2SessionListFamily(userId));
|
||||||
|
|
||||||
return (
|
if (result.data?.user?.oauth2Sessions) {
|
||||||
<BlockList>
|
const data = result.data.user.oauth2Sessions;
|
||||||
<Title>List of OAuth 2.0 sessions:</Title>
|
return (
|
||||||
{data.oauth2Sessions.edges.map((n) => (
|
<BlockList>
|
||||||
<OAuth2Session key={n.cursor} session={n.node} />
|
<Title>List of OAuth 2.0 sessions:</Title>
|
||||||
))}
|
{data.edges.map((n) => (
|
||||||
</BlockList>
|
<OAuth2Session key={n.cursor} session={n.node} />
|
||||||
);
|
))}
|
||||||
|
</BlockList>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <>Failed to load OAuth 2.0 session list</>;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OAuth2SessionList;
|
export default OAuth2SessionList;
|
||||||
|
@@ -83,7 +83,7 @@ const currentPagination = atomWithDefault<Pagination>((get) => ({
|
|||||||
after: null,
|
after: null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const emailPageResultFamily = atomFamily((userId: string) => {
|
export const emailPageResultFamily = atomFamily((userId: string) => {
|
||||||
const emailPageResult = atomWithQuery({
|
const emailPageResult = atomWithQuery({
|
||||||
query: QUERY,
|
query: QUERY,
|
||||||
getVariables: (get) => ({ userId, ...get(currentPagination) }),
|
getVariables: (get) => ({ userId, ...get(currentPagination) }),
|
||||||
|
49
frontend/src/components/UserGreeting.tsx
Normal file
49
frontend/src/components/UserGreeting.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// 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 { graphql } from "../gql";
|
||||||
|
import { atomFamily } from "jotai/utils";
|
||||||
|
import { atomWithQuery } from "jotai-urql";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
import { Title } from "./Typography";
|
||||||
|
|
||||||
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
|
query UserGreeting($userId: ID!) {
|
||||||
|
user(id: $userId) {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const userGreetingFamily = atomFamily((userId: string) => {
|
||||||
|
const userGreeting = atomWithQuery({
|
||||||
|
query: QUERY,
|
||||||
|
getVariables: () => ({ userId }),
|
||||||
|
});
|
||||||
|
|
||||||
|
return userGreeting;
|
||||||
|
});
|
||||||
|
|
||||||
|
const UserGreeting: React.FC<{ userId: string }> = ({ userId }) => {
|
||||||
|
const result = useAtomValue(userGreetingFamily(userId));
|
||||||
|
|
||||||
|
if (result.data?.user) {
|
||||||
|
return <Title>Hello, {result.data.user.username}!</Title>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>Failed to load user</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserGreeting;
|
@@ -13,34 +13,32 @@ import { TypedDocumentNode as DocumentNode } from "@graphql-typed-document-node/
|
|||||||
* Therefore it is highly recommended to use the babel or swc plugin for production.
|
* Therefore it is highly recommended to use the babel or swc plugin for production.
|
||||||
*/
|
*/
|
||||||
const documents = {
|
const documents = {
|
||||||
"\n query CurrentViewerQuery {\n viewer {\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n":
|
"\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n":
|
||||||
types.CurrentViewerQueryDocument,
|
types.CurrentViewerQueryDocument,
|
||||||
"\n query CurrentViewerSessionQuery {\n viewerSession {\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n":
|
"\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n":
|
||||||
types.CurrentViewerSessionQueryDocument,
|
types.CurrentViewerSessionQueryDocument,
|
||||||
"\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n user {\n id\n }\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
"\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n user {\n id\n }\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||||
types.AddEmailDocument,
|
types.AddEmailDocument,
|
||||||
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n":
|
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n":
|
||||||
types.BrowserSession_SessionFragmentDoc,
|
types.BrowserSession_SessionFragmentDoc,
|
||||||
"\n fragment BrowserSessionList_user on User {\n browserSessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n":
|
"\n query BrowserSessionList($userId: ID!) {\n user(id: $userId) {\n id\n browserSessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n }\n":
|
||||||
types.BrowserSessionList_UserFragmentDoc,
|
types.BrowserSessionListDocument,
|
||||||
"\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n }\n":
|
"\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n }\n":
|
||||||
types.CompatSsoLogin_LoginFragmentDoc,
|
types.CompatSsoLogin_LoginFragmentDoc,
|
||||||
"\n fragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: 10) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n":
|
"\n query CompatSsoLoginList($userId: ID!) {\n user(id: $userId) {\n id\n compatSsoLogins(first: 10) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n }\n":
|
||||||
types.CompatSsoLoginList_UserFragmentDoc,
|
types.CompatSsoLoginListDocument,
|
||||||
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n":
|
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n":
|
||||||
types.OAuth2Session_SessionFragmentDoc,
|
types.OAuth2Session_SessionFragmentDoc,
|
||||||
"\n fragment OAuth2SessionList_user on User {\n oauth2Sessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n":
|
"\n query OAuth2SessionListQuery($userId: ID!) {\n user(id: $userId) {\n id\n oauth2Sessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n }\n":
|
||||||
types.OAuth2SessionList_UserFragmentDoc,
|
types.OAuth2SessionListQueryDocument,
|
||||||
"\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n":
|
"\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n":
|
||||||
types.UserEmail_EmailFragmentDoc,
|
types.UserEmail_EmailFragmentDoc,
|
||||||
"\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
"\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||||
types.UserEmailListQueryDocument,
|
types.UserEmailListQueryDocument,
|
||||||
"\n query AccountQuery($id: ID!) {\n user(id: $id) {\n id\n username\n }\n }\n":
|
"\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n }\n }\n":
|
||||||
types.AccountQueryDocument,
|
types.UserGreetingDocument,
|
||||||
"\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n":
|
"\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n":
|
||||||
types.BrowserSessionQueryDocument,
|
types.BrowserSessionQueryDocument,
|
||||||
"\n query HomeQuery {\n # eslint-disable-next-line @graphql-eslint/no-deprecated\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n":
|
|
||||||
types.HomeQueryDocument,
|
|
||||||
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n":
|
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n":
|
||||||
types.OAuth2ClientQueryDocument,
|
types.OAuth2ClientQueryDocument,
|
||||||
};
|
};
|
||||||
@@ -63,14 +61,14 @@ export function graphql(source: string): unknown;
|
|||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n query CurrentViewerQuery {\n viewer {\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"
|
source: "\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"
|
||||||
): (typeof documents)["\n query CurrentViewerQuery {\n viewer {\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"];
|
): (typeof documents)["\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n query CurrentViewerSessionQuery {\n viewerSession {\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"
|
source: "\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"
|
||||||
): (typeof documents)["\n query CurrentViewerSessionQuery {\n viewerSession {\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"];
|
): (typeof documents)["\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
@@ -87,8 +85,8 @@ export function graphql(
|
|||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n fragment BrowserSessionList_user on User {\n browserSessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n"
|
source: "\n query BrowserSessionList($userId: ID!) {\n user(id: $userId) {\n id\n browserSessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n }\n"
|
||||||
): (typeof documents)["\n fragment BrowserSessionList_user on User {\n browserSessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n"];
|
): (typeof documents)["\n query BrowserSessionList($userId: ID!) {\n user(id: $userId) {\n id\n browserSessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
@@ -99,8 +97,8 @@ export function graphql(
|
|||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n fragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: 10) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n"
|
source: "\n query CompatSsoLoginList($userId: ID!) {\n user(id: $userId) {\n id\n compatSsoLogins(first: 10) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n }\n"
|
||||||
): (typeof documents)["\n fragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: 10) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n"];
|
): (typeof documents)["\n query CompatSsoLoginList($userId: ID!) {\n user(id: $userId) {\n id\n compatSsoLogins(first: 10) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
@@ -111,8 +109,8 @@ export function graphql(
|
|||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n fragment OAuth2SessionList_user on User {\n oauth2Sessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n"
|
source: "\n query OAuth2SessionListQuery($userId: ID!) {\n user(id: $userId) {\n id\n oauth2Sessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n }\n"
|
||||||
): (typeof documents)["\n fragment OAuth2SessionList_user on User {\n oauth2Sessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n"];
|
): (typeof documents)["\n query OAuth2SessionListQuery($userId: ID!) {\n user(id: $userId) {\n id\n oauth2Sessions(first: 10) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
@@ -129,20 +127,14 @@ export function graphql(
|
|||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n query AccountQuery($id: ID!) {\n user(id: $id) {\n id\n username\n }\n }\n"
|
source: "\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n }\n }\n"
|
||||||
): (typeof documents)["\n query AccountQuery($id: ID!) {\n user(id: $id) {\n id\n username\n }\n }\n"];
|
): (typeof documents)["\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"
|
source: "\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"
|
||||||
): (typeof documents)["\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"];
|
): (typeof documents)["\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"];
|
||||||
/**
|
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
|
||||||
*/
|
|
||||||
export function graphql(
|
|
||||||
source: "\n query HomeQuery {\n # eslint-disable-next-line @graphql-eslint/no-deprecated\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n"
|
|
||||||
): (typeof documents)["\n query HomeQuery {\n # eslint-disable-next-line @graphql-eslint/no-deprecated\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n"];
|
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -43,8 +43,6 @@ const cache = cacheExchange({
|
|||||||
|
|
||||||
export const client = createClient({
|
export const client = createClient({
|
||||||
url: "/graphql",
|
url: "/graphql",
|
||||||
// XXX: else queries don't refetch on cache invalidation for some reason
|
|
||||||
requestPolicy: "cache-and-network",
|
|
||||||
exchanges: import.meta.env.DEV
|
exchanges: import.meta.env.DEV
|
||||||
? [devtoolsExchange, cache, fetchExchange]
|
? [devtoolsExchange, cache, fetchExchange]
|
||||||
: [cache, fetchExchange],
|
: [cache, fetchExchange],
|
||||||
|
@@ -22,26 +22,12 @@ import UserEmailList from "../components/UserEmailList";
|
|||||||
import { Title } from "../components/Typography";
|
import { Title } from "../components/Typography";
|
||||||
import AddEmailForm from "../components/AddEmailForm";
|
import AddEmailForm from "../components/AddEmailForm";
|
||||||
import { currentUserIdAtom } from "../atoms";
|
import { currentUserIdAtom } from "../atoms";
|
||||||
|
import UserGreeting from "../components/UserGreeting";
|
||||||
const QUERY = graphql(/* GraphQL */ `
|
|
||||||
query AccountQuery($id: ID!) {
|
|
||||||
user(id: $id) {
|
|
||||||
id
|
|
||||||
username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const accountAtomFamily = atomFamily((id: string) =>
|
|
||||||
atomWithQuery({ query: QUERY, getVariables: () => ({ id }) })
|
|
||||||
);
|
|
||||||
|
|
||||||
const UserAccount: React.FC<{ id: string }> = ({ id }) => {
|
const UserAccount: React.FC<{ id: string }> = ({ id }) => {
|
||||||
const result = useAtomValue(accountAtomFamily(id));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-4">
|
<div className="grid grid-cols-1 gap-4">
|
||||||
<Title>Hello {result.data?.user?.username}</Title>
|
<UserGreeting userId={id} />
|
||||||
<UserEmailList userId={id} />
|
<UserEmailList userId={id} />
|
||||||
<AddEmailForm userId={id} />
|
<AddEmailForm userId={id} />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -16,6 +16,7 @@ import { useAtomValue } from "jotai";
|
|||||||
import { atomWithQuery } from "jotai-urql";
|
import { atomWithQuery } from "jotai-urql";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
|
import { atomFamily } from "jotai/utils";
|
||||||
|
|
||||||
const QUERY = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
query BrowserSessionQuery($id: ID!) {
|
query BrowserSessionQuery($id: ID!) {
|
||||||
@@ -34,25 +35,27 @@ const QUERY = graphql(/* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const BrowserSession: React.FC<{ id: string }> = ({ id }) => {
|
const browserSessionFamily = atomFamily((id: string) => {
|
||||||
const result = useAtomValue(
|
const browserSessionAtom = atomWithQuery({
|
||||||
useMemo(
|
query: QUERY,
|
||||||
() => atomWithQuery({ query: QUERY, getVariables: () => ({ id }) }),
|
getVariables: () => ({ id }),
|
||||||
[id]
|
});
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.error) {
|
return browserSessionAtom;
|
||||||
throw result.error;
|
});
|
||||||
|
|
||||||
|
const BrowserSession: React.FC<{ id: string }> = ({ id }) => {
|
||||||
|
const result = useAtomValue(browserSessionFamily(id));
|
||||||
|
|
||||||
|
if (result.data?.browserSession) {
|
||||||
|
return (
|
||||||
|
<pre>
|
||||||
|
<code>{JSON.stringify(result.data.browserSession, null, 2)}</code>
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = result.data!!;
|
return <>Failed to load browser session</>;
|
||||||
|
|
||||||
return (
|
|
||||||
<pre>
|
|
||||||
<code>{JSON.stringify(data.browserSession, null, 2)}</code>
|
|
||||||
</pre>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BrowserSession;
|
export default BrowserSession;
|
||||||
|
@@ -20,48 +20,20 @@ import CompatSsoLoginList from "../components/CompatSsoLoginList";
|
|||||||
import OAuth2SessionList from "../components/OAuth2SessionList";
|
import OAuth2SessionList from "../components/OAuth2SessionList";
|
||||||
import Typography from "../components/Typography";
|
import Typography from "../components/Typography";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
|
import { currentUserIdAtom } from "../atoms";
|
||||||
const QUERY = graphql(/* GraphQL */ `
|
import UserGreeting from "../components/UserGreeting";
|
||||||
query HomeQuery {
|
|
||||||
# eslint-disable-next-line @graphql-eslint/no-deprecated
|
|
||||||
currentBrowserSession {
|
|
||||||
id
|
|
||||||
user {
|
|
||||||
id
|
|
||||||
username
|
|
||||||
|
|
||||||
...CompatSsoLoginList_user
|
|
||||||
...BrowserSessionList_user
|
|
||||||
...OAuth2SessionList_user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const homeDataAtom = atomWithQuery({
|
|
||||||
query: QUERY,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
const result = useAtomValue(homeDataAtom);
|
const currentUserId = useAtomValue(currentUserIdAtom);
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
throw result.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = result.data!!;
|
|
||||||
|
|
||||||
if (data.currentBrowserSession) {
|
|
||||||
const session = data.currentBrowserSession;
|
|
||||||
const user = session.user;
|
|
||||||
|
|
||||||
|
if (currentUserId) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography variant="headline">Hello {user.username}!</Typography>
|
<UserGreeting userId={currentUserId} />
|
||||||
<div className="mt-4 grid lg:grid-cols-3 gap-1">
|
<div className="mt-4 grid lg:grid-cols-3 gap-1">
|
||||||
<OAuth2SessionList user={user} />
|
<OAuth2SessionList userId={currentUserId} />
|
||||||
<CompatSsoLoginList user={user} />
|
<CompatSsoLoginList userId={currentUserId} />
|
||||||
<BrowserSessionList user={user} currentSessionId={session.id} />
|
<BrowserSessionList userId={currentUserId} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@@ -16,6 +16,7 @@ import { useAtomValue } from "jotai";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { atomWithQuery } from "jotai-urql";
|
import { atomWithQuery } from "jotai-urql";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
|
import { atomFamily } from "jotai/utils";
|
||||||
|
|
||||||
const QUERY = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
query OAuth2ClientQuery($id: ID!) {
|
query OAuth2ClientQuery($id: ID!) {
|
||||||
@@ -31,25 +32,27 @@ const QUERY = graphql(/* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const OAuth2Client: React.FC<{ id: string }> = ({ id }) => {
|
const oauth2ClientFamily = atomFamily((id: string) => {
|
||||||
const result = useAtomValue(
|
const oauth2ClientAtom = atomWithQuery({
|
||||||
useMemo(
|
query: QUERY,
|
||||||
() => atomWithQuery({ query: QUERY, getVariables: () => ({ id }) }),
|
getVariables: () => ({ id }),
|
||||||
[id]
|
});
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.error) {
|
return oauth2ClientAtom;
|
||||||
throw result.error;
|
});
|
||||||
|
|
||||||
|
const OAuth2Client: React.FC<{ id: string }> = ({ id }) => {
|
||||||
|
const result = useAtomValue(oauth2ClientFamily(id));
|
||||||
|
|
||||||
|
if (result.data?.oauth2Client) {
|
||||||
|
return (
|
||||||
|
<pre>
|
||||||
|
<code>{JSON.stringify(result.data.oauth2Client, null, 2)}</code>
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = result.data!!;
|
return <>Failed to load OAuth2 client</>;
|
||||||
|
|
||||||
return (
|
|
||||||
<pre>
|
|
||||||
<code>{JSON.stringify(data.oauth2Client, null, 2)}</code>
|
|
||||||
</pre>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OAuth2Client;
|
export default OAuth2Client;
|
||||||
|
Reference in New Issue
Block a user