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
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 */ `
|
||||
query CurrentViewerQuery {
|
||||
viewer {
|
||||
__typename
|
||||
... on User {
|
||||
id
|
||||
}
|
||||
@ -52,6 +53,7 @@ export const currentUserIdAtom = atom(async (get) => {
|
||||
const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ `
|
||||
query CurrentViewerSessionQuery {
|
||||
viewerSession {
|
||||
__typename
|
||||
... on BrowserSession {
|
||||
id
|
||||
}
|
||||
|
@ -14,12 +14,13 @@
|
||||
|
||||
import React, { useRef, useTransition } from "react";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useAtom } from "jotai";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { graphql } from "../gql";
|
||||
import Button from "./Button";
|
||||
import UserEmail from "./UserEmail";
|
||||
import Input from "./Input";
|
||||
import Typography from "./Typography";
|
||||
import { emailPageResultFamily } from "./UserEmailList";
|
||||
|
||||
const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation AddEmail($userId: ID!, $email: String!) {
|
||||
@ -42,12 +43,15 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [addEmailResult, addEmail] = useAtom(addUserEmailAtom);
|
||||
const [pending, startTransition] = useTransition();
|
||||
// XXX: is this the right way to do this?
|
||||
const refetchList = useSetAtom(emailPageResultFamily(userId));
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const email = e.currentTarget.email.value;
|
||||
startTransition(() => {
|
||||
addEmail({ userId, email }).then(() => {
|
||||
refetchList();
|
||||
if (formRef.current) {
|
||||
formRef.current.reset();
|
||||
}
|
||||
@ -59,7 +63,7 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
<>
|
||||
{addEmailResult.data?.addEmail.status === "ADDED" && (
|
||||
<>
|
||||
<div className="p-4">
|
||||
<div className="pt-4">
|
||||
<Typography variant="subtitle">Email added!</Typography>
|
||||
</div>
|
||||
<UserEmail email={addEmailResult.data?.addEmail.email} />
|
||||
@ -67,7 +71,7 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
)}
|
||||
{addEmailResult.data?.addEmail.status === "EXISTS" && (
|
||||
<>
|
||||
<div className="p-4">
|
||||
<div className="pt-4">
|
||||
<Typography variant="subtitle">Email already exists!</Typography>
|
||||
</div>
|
||||
<UserEmail email={addEmailResult.data?.addEmail.email} />
|
||||
|
@ -15,42 +15,58 @@
|
||||
import BlockList from "./BlockList";
|
||||
import BrowserSession from "./BrowserSession";
|
||||
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 */ `
|
||||
fragment BrowserSessionList_user on User {
|
||||
browserSessions(first: 10) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
...BrowserSession_session
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query BrowserSessionList($userId: ID!) {
|
||||
user(id: $userId) {
|
||||
id
|
||||
browserSessions(first: 10) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
...BrowserSession_session
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
type Props = {
|
||||
user: FragmentType<typeof FRAGMENT>;
|
||||
currentSessionId: string;
|
||||
};
|
||||
const browserSessionListFamily = atomFamily((userId: string) => {
|
||||
const browserSessionList = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: () => ({ userId }),
|
||||
});
|
||||
return browserSessionList;
|
||||
});
|
||||
|
||||
const BrowserSessionList: React.FC<Props> = ({ user, currentSessionId }) => {
|
||||
const data = useFragment(FRAGMENT, user);
|
||||
const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const result = useAtomValue(browserSessionListFamily(userId));
|
||||
const currentSessionId = useAtomValue(currentBrowserSessionIdAtom);
|
||||
|
||||
return (
|
||||
<BlockList>
|
||||
<Title>List of browser sessions:</Title>
|
||||
{data.browserSessions.edges.map((n) => (
|
||||
<BrowserSession
|
||||
key={n.cursor}
|
||||
session={n.node}
|
||||
isCurrent={n.node.id === currentSessionId}
|
||||
/>
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
if (result.data?.user?.browserSessions) {
|
||||
const data = result.data.user.browserSessions;
|
||||
return (
|
||||
<BlockList>
|
||||
<Title>List of browser sessions:</Title>
|
||||
{data.edges.map((n) => (
|
||||
<BrowserSession
|
||||
key={n.cursor}
|
||||
session={n.node}
|
||||
isCurrent={n.node.id === currentSessionId}
|
||||
/>
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
}
|
||||
|
||||
return <>Failed to load browser sessions</>;
|
||||
};
|
||||
|
||||
export default BrowserSessionList;
|
||||
|
@ -15,36 +15,52 @@
|
||||
import BlockList from "./BlockList";
|
||||
import CompatSsoLogin from "./CompatSsoLogin";
|
||||
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 */ `
|
||||
fragment CompatSsoLoginList_user on User {
|
||||
compatSsoLogins(first: 10) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CompatSsoLogin_login
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query CompatSsoLoginList($userId: ID!) {
|
||||
user(id: $userId) {
|
||||
id
|
||||
compatSsoLogins(first: 10) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CompatSsoLogin_login
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
type Props = {
|
||||
user: FragmentType<typeof FRAGMENT>;
|
||||
};
|
||||
const compatSsoLoginListFamily = atomFamily((userId: string) => {
|
||||
const compatSsoLoginList = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: () => ({ userId }),
|
||||
});
|
||||
|
||||
const CompatSsoLoginList: React.FC<Props> = ({ user }) => {
|
||||
const data = useFragment(FRAGMENT, user);
|
||||
return compatSsoLoginList;
|
||||
});
|
||||
|
||||
return (
|
||||
<BlockList>
|
||||
<Title>List of compatibility sessions:</Title>
|
||||
{data.compatSsoLogins.edges.map((n) => (
|
||||
<CompatSsoLogin login={n.node} key={n.node.id} />
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
const CompatSsoLoginList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const result = useAtomValue(compatSsoLoginListFamily(userId));
|
||||
|
||||
if (result.data?.user?.compatSsoLogins) {
|
||||
const data = result.data.user.compatSsoLogins;
|
||||
return (
|
||||
<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;
|
||||
|
@ -12,41 +12,62 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
import { useAtomValue } from "jotai";
|
||||
|
||||
import BlockList from "./BlockList";
|
||||
import OAuth2Session from "./OAuth2Session";
|
||||
import { Title } from "./Typography";
|
||||
|
||||
import { FragmentType, graphql, useFragment } from "../gql";
|
||||
import { graphql } from "../gql";
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment OAuth2SessionList_user on User {
|
||||
oauth2Sessions(first: 10) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
...OAuth2Session_session
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query OAuth2SessionListQuery($userId: ID!) {
|
||||
user(id: $userId) {
|
||||
id
|
||||
oauth2Sessions(first: 10) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
...OAuth2Session_session
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const oauth2SessionListFamily = atomFamily((userId: string) => {
|
||||
const oauth2SessionList = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: () => ({ userId }),
|
||||
});
|
||||
|
||||
return oauth2SessionList;
|
||||
});
|
||||
|
||||
type Props = {
|
||||
user: FragmentType<typeof FRAGMENT>;
|
||||
userId: string;
|
||||
};
|
||||
|
||||
const OAuth2SessionList: React.FC<Props> = ({ user }) => {
|
||||
const data = useFragment(FRAGMENT, user);
|
||||
const OAuth2SessionList: React.FC<Props> = ({ userId }) => {
|
||||
const result = useAtomValue(oauth2SessionListFamily(userId));
|
||||
|
||||
return (
|
||||
<BlockList>
|
||||
<Title>List of OAuth 2.0 sessions:</Title>
|
||||
{data.oauth2Sessions.edges.map((n) => (
|
||||
<OAuth2Session key={n.cursor} session={n.node} />
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
if (result.data?.user?.oauth2Sessions) {
|
||||
const data = result.data.user.oauth2Sessions;
|
||||
return (
|
||||
<BlockList>
|
||||
<Title>List of OAuth 2.0 sessions:</Title>
|
||||
{data.edges.map((n) => (
|
||||
<OAuth2Session key={n.cursor} session={n.node} />
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
} else {
|
||||
return <>Failed to load OAuth 2.0 session list</>;
|
||||
}
|
||||
};
|
||||
|
||||
export default OAuth2SessionList;
|
||||
|
@ -83,7 +83,7 @@ const currentPagination = atomWithDefault<Pagination>((get) => ({
|
||||
after: null,
|
||||
}));
|
||||
|
||||
const emailPageResultFamily = atomFamily((userId: string) => {
|
||||
export const emailPageResultFamily = atomFamily((userId: string) => {
|
||||
const emailPageResult = atomWithQuery({
|
||||
query: QUERY,
|
||||
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.
|
||||
*/
|
||||
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,
|
||||
"\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,
|
||||
"\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,
|
||||
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n":
|
||||
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":
|
||||
types.BrowserSessionList_UserFragmentDoc,
|
||||
"\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.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":
|
||||
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":
|
||||
types.CompatSsoLoginList_UserFragmentDoc,
|
||||
"\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.CompatSsoLoginListDocument,
|
||||
"\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,
|
||||
"\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":
|
||||
types.OAuth2SessionList_UserFragmentDoc,
|
||||
"\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.OAuth2SessionListQueryDocument,
|
||||
"\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n":
|
||||
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":
|
||||
types.UserEmailListQueryDocument,
|
||||
"\n query AccountQuery($id: ID!) {\n user(id: $id) {\n id\n username\n }\n }\n":
|
||||
types.AccountQueryDocument,
|
||||
"\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\n id\n username\n }\n }\n":
|
||||
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":
|
||||
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":
|
||||
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.
|
||||
*/
|
||||
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"
|
||||
): (typeof documents)["\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 __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n query CurrentViewerSessionQuery {\n viewerSession {\n ... 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"];
|
||||
source: "\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"
|
||||
): (typeof documents)["\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@ -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.
|
||||
*/
|
||||
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"
|
||||
): (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"];
|
||||
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 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.
|
||||
*/
|
||||
@ -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.
|
||||
*/
|
||||
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"
|
||||
): (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"];
|
||||
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 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.
|
||||
*/
|
||||
@ -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.
|
||||
*/
|
||||
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"
|
||||
): (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"];
|
||||
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 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.
|
||||
*/
|
||||
@ -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.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n query AccountQuery($id: ID!) {\n user(id: $id) {\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"];
|
||||
source: "\n query UserGreeting($userId: ID!) {\n user(id: $userId) {\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.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"
|
||||
): (typeof documents)["\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"];
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -43,8 +43,6 @@ const cache = cacheExchange({
|
||||
|
||||
export const client = createClient({
|
||||
url: "/graphql",
|
||||
// XXX: else queries don't refetch on cache invalidation for some reason
|
||||
requestPolicy: "cache-and-network",
|
||||
exchanges: import.meta.env.DEV
|
||||
? [devtoolsExchange, cache, fetchExchange]
|
||||
: [cache, fetchExchange],
|
||||
|
@ -22,26 +22,12 @@ import UserEmailList from "../components/UserEmailList";
|
||||
import { Title } from "../components/Typography";
|
||||
import AddEmailForm from "../components/AddEmailForm";
|
||||
import { currentUserIdAtom } from "../atoms";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query AccountQuery($id: ID!) {
|
||||
user(id: $id) {
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const accountAtomFamily = atomFamily((id: string) =>
|
||||
atomWithQuery({ query: QUERY, getVariables: () => ({ id }) })
|
||||
);
|
||||
import UserGreeting from "../components/UserGreeting";
|
||||
|
||||
const UserAccount: React.FC<{ id: string }> = ({ id }) => {
|
||||
const result = useAtomValue(accountAtomFamily(id));
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<Title>Hello {result.data?.user?.username}</Title>
|
||||
<UserGreeting userId={id} />
|
||||
<UserEmailList userId={id} />
|
||||
<AddEmailForm userId={id} />
|
||||
</div>
|
||||
|
@ -16,6 +16,7 @@ import { useAtomValue } from "jotai";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
import { useMemo } from "react";
|
||||
import { graphql } from "../gql";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query BrowserSessionQuery($id: ID!) {
|
||||
@ -34,25 +35,27 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const BrowserSession: React.FC<{ id: string }> = ({ id }) => {
|
||||
const result = useAtomValue(
|
||||
useMemo(
|
||||
() => atomWithQuery({ query: QUERY, getVariables: () => ({ id }) }),
|
||||
[id]
|
||||
)
|
||||
);
|
||||
const browserSessionFamily = atomFamily((id: string) => {
|
||||
const browserSessionAtom = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: () => ({ id }),
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
return browserSessionAtom;
|
||||
});
|
||||
|
||||
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 (
|
||||
<pre>
|
||||
<code>{JSON.stringify(data.browserSession, null, 2)}</code>
|
||||
</pre>
|
||||
);
|
||||
return <>Failed to load browser session</>;
|
||||
};
|
||||
|
||||
export default BrowserSession;
|
||||
|
@ -20,48 +20,20 @@ import CompatSsoLoginList from "../components/CompatSsoLoginList";
|
||||
import OAuth2SessionList from "../components/OAuth2SessionList";
|
||||
import Typography from "../components/Typography";
|
||||
import { graphql } from "../gql";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
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,
|
||||
});
|
||||
import { currentUserIdAtom } from "../atoms";
|
||||
import UserGreeting from "../components/UserGreeting";
|
||||
|
||||
const Home: React.FC = () => {
|
||||
const result = useAtomValue(homeDataAtom);
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
const data = result.data!!;
|
||||
|
||||
if (data.currentBrowserSession) {
|
||||
const session = data.currentBrowserSession;
|
||||
const user = session.user;
|
||||
const currentUserId = useAtomValue(currentUserIdAtom);
|
||||
|
||||
if (currentUserId) {
|
||||
return (
|
||||
<>
|
||||
<Typography variant="headline">Hello {user.username}!</Typography>
|
||||
<UserGreeting userId={currentUserId} />
|
||||
<div className="mt-4 grid lg:grid-cols-3 gap-1">
|
||||
<OAuth2SessionList user={user} />
|
||||
<CompatSsoLoginList user={user} />
|
||||
<BrowserSessionList user={user} currentSessionId={session.id} />
|
||||
<OAuth2SessionList userId={currentUserId} />
|
||||
<CompatSsoLoginList userId={currentUserId} />
|
||||
<BrowserSessionList userId={currentUserId} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -16,6 +16,7 @@ import { useAtomValue } from "jotai";
|
||||
import { useMemo } from "react";
|
||||
import { atomWithQuery } from "jotai-urql";
|
||||
import { graphql } from "../gql";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query OAuth2ClientQuery($id: ID!) {
|
||||
@ -31,25 +32,27 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const OAuth2Client: React.FC<{ id: string }> = ({ id }) => {
|
||||
const result = useAtomValue(
|
||||
useMemo(
|
||||
() => atomWithQuery({ query: QUERY, getVariables: () => ({ id }) }),
|
||||
[id]
|
||||
)
|
||||
);
|
||||
const oauth2ClientFamily = atomFamily((id: string) => {
|
||||
const oauth2ClientAtom = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: () => ({ id }),
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
return oauth2ClientAtom;
|
||||
});
|
||||
|
||||
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 (
|
||||
<pre>
|
||||
<code>{JSON.stringify(data.oauth2Client, null, 2)}</code>
|
||||
</pre>
|
||||
);
|
||||
return <>Failed to load OAuth2 client</>;
|
||||
};
|
||||
|
||||
export default OAuth2Client;
|
||||
|
Reference in New Issue
Block a user