diff --git a/frontend/codegen.ts b/frontend/codegen.ts index a10cdbcf..93556a0f 100644 --- a/frontend/codegen.ts +++ b/frontend/codegen.ts @@ -2,7 +2,7 @@ import { CodegenConfig } from "@graphql-codegen/cli"; const config: CodegenConfig = { schema: "./schema.graphql", - documents: ["src/**/*.tsx", "!src/gql/**/*"], + documents: ["src/**/*.{tsx,ts}", "!src/gql/**/*"], ignoreNoDocuments: true, // for better experience with the watcher generates: { "./src/gql/": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c25939c0..fd2bc2d6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@emotion/react": "^11.10.6", "@urql/core": "^4.0.7", + "@urql/devtools": "^2.0.3", "@urql/exchange-graphcache": "^6.0.3", "date-fns": "^2.29.3", "graphql": "^16.6.0", @@ -6440,6 +6441,18 @@ "wonka": "^6.3.2" } }, + "node_modules/@urql/devtools": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@urql/devtools/-/devtools-2.0.3.tgz", + "integrity": "sha512-TktPLiBS9LcBPHD6qcnb8wqOVcg3Bx0iCtvQ80uPpfofwwBGJmqnQTjUdEFU6kwaLOFZULQ9+Uo4831G823mQw==", + "dependencies": { + "wonka": ">= 4.0.9" + }, + "peerDependencies": { + "@urql/core": ">= 1.14.0", + "graphql": ">= 0.11.0" + } + }, "node_modules/@urql/exchange-graphcache": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@urql/exchange-graphcache/-/exchange-graphcache-6.0.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0e9ad742..108ba20d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "dependencies": { "@emotion/react": "^11.10.6", "@urql/core": "^4.0.7", + "@urql/devtools": "^2.0.3", "@urql/exchange-graphcache": "^6.0.3", "date-fns": "^2.29.3", "graphql": "^16.6.0", diff --git a/frontend/src/atoms.ts b/frontend/src/atoms.ts index 96e0d8e8..ba44274c 100644 --- a/frontend/src/atoms.ts +++ b/frontend/src/atoms.ts @@ -13,12 +13,66 @@ // limitations under the License. import type { ReactElement } from "react"; +import { atom } from "jotai"; import { useHydrateAtoms } from "jotai/utils"; -import { clientAtom } from "jotai-urql"; +import { atomWithQuery, clientAtom } from "jotai-urql"; import { client } from "./graphql"; +import { graphql } from "./gql"; export const HydrateAtoms = ({ children }: { children: ReactElement }) => { useHydrateAtoms([[clientAtom, client]]); return children; }; + +const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ ` + query CurrentViewerQuery { + viewer { + ... on User { + id + } + + ... on Anonymous { + id + } + } + } +`); + +const currentViewerAtom = atomWithQuery({ query: CURRENT_VIEWER_QUERY }); + +export const currentUserIdAtom = atom(async (get) => { + const result = await get(currentViewerAtom); + if (result.data?.viewer.__typename === "User") { + return result.data.viewer.id; + } + return null; +}); + +const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ ` + query CurrentViewerSessionQuery { + viewerSession { + ... on BrowserSession { + id + } + + ... on Anonymous { + id + } + } + } +`); + +const currentViewerSessionAtom = atomWithQuery({ + query: CURRENT_VIEWER_SESSION_QUERY, +}); + +export const currentBrowserSessionIdAtom = atom( + async (get): Promise => { + const result = await get(currentViewerSessionAtom); + if (result.data?.viewerSession.__typename === "BrowserSession") { + return result.data.viewerSession.id; + } + return null; + } +); diff --git a/frontend/src/components/BrowserSessionList.tsx b/frontend/src/components/BrowserSessionList.tsx index 82a4e668..cb1f0894 100644 --- a/frontend/src/components/BrowserSessionList.tsx +++ b/frontend/src/components/BrowserSessionList.tsx @@ -19,7 +19,7 @@ import { FragmentType, graphql, useFragment } from "../gql"; const FRAGMENT = graphql(/* GraphQL */ ` fragment BrowserSessionList_user on User { - browserSessions(first: $count, after: $cursor) { + browserSessions(first: 10) { edges { cursor node { diff --git a/frontend/src/components/CompatSsoLoginList.tsx b/frontend/src/components/CompatSsoLoginList.tsx index 683f62ef..22c45c74 100644 --- a/frontend/src/components/CompatSsoLoginList.tsx +++ b/frontend/src/components/CompatSsoLoginList.tsx @@ -19,7 +19,7 @@ import { FragmentType, graphql, useFragment } from "../gql"; const FRAGMENT = graphql(/* GraphQL */ ` fragment CompatSsoLoginList_user on User { - compatSsoLogins(first: $count, after: $cursor) { + compatSsoLogins(first: 10) { edges { node { id diff --git a/frontend/src/components/OAuth2SessionList.tsx b/frontend/src/components/OAuth2SessionList.tsx index 8389f6d6..2506e34d 100644 --- a/frontend/src/components/OAuth2SessionList.tsx +++ b/frontend/src/components/OAuth2SessionList.tsx @@ -20,7 +20,7 @@ import { FragmentType, graphql, useFragment } from "../gql"; const FRAGMENT = graphql(/* GraphQL */ ` fragment OAuth2SessionList_user on User { - oauth2Sessions(first: $count, after: $cursor) { + oauth2Sessions(first: 10) { edges { cursor node { diff --git a/frontend/src/components/UserEmailList.tsx b/frontend/src/components/UserEmailList.tsx index 6925398e..158ccacc 100644 --- a/frontend/src/components/UserEmailList.tsx +++ b/frontend/src/components/UserEmailList.tsx @@ -31,7 +31,6 @@ const QUERY = graphql(/* GraphQL */ ` $before: String ) { user(id: $userId) { - __typename id emails(first: $first, after: $after, last: $last, before: $before) { edges { @@ -54,12 +53,12 @@ const QUERY = graphql(/* GraphQL */ ` `); type ForwardPagination = { - first: int; + first: number; after: string | null; }; type BackwardPagination = { - last: int; + last: number; before: string | null; }; diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts index 665a47e6..78937d10 100644 --- a/frontend/src/gql/gql.ts +++ b/frontend/src/gql/gql.ts @@ -13,31 +13,33 @@ 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": + types.CurrentViewerQueryDocument, + "\n query CurrentViewerSessionQuery {\n viewerSession {\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: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n": + "\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 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: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n": + "\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 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: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n": + "\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 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 __typename\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, - "\n query CurrentUserQuery {\n viewer {\n ... on User {\n __typename\n id\n }\n }\n }\n": - types.CurrentUserQueryDocument, "\n query AccountQuery($id: ID!) {\n user(id: $id) {\n id\n username\n }\n }\n": types.AccountQueryDocument, "\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($count: Int!, $cursor: String) {\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": + "\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, @@ -57,6 +59,18 @@ const documents = { */ 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"]; +/** + * 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"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -73,8 +87,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: $count, after: $cursor) {\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: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n"]; + 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"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -85,8 +99,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: $count, after: $cursor) {\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: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n"]; + 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"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -97,8 +111,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: $count, after: $cursor) {\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: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n"]; + 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"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -109,14 +123,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 query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n __typename\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" -): (typeof documents)["\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n __typename\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"]; -/** - * 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 CurrentUserQuery {\n viewer {\n ... on User {\n __typename\n id\n }\n }\n }\n" -): (typeof documents)["\n query CurrentUserQuery {\n viewer {\n ... on User {\n __typename\n id\n }\n }\n }\n"]; + source: "\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n" +): (typeof documents)["\n query UserEmailListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -133,8 +141,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 query HomeQuery($count: Int!, $cursor: String) {\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($count: Int!, $cursor: String) {\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"]; + 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. */ diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index d2d9563d..597d18a6 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -577,6 +577,26 @@ export type Viewer = Anonymous | User; /** Represents the current viewer's session */ export type ViewerSession = Anonymous | BrowserSession; +export type CurrentViewerQueryQueryVariables = Exact<{ [key: string]: never }>; + +export type CurrentViewerQueryQuery = { + __typename?: "Query"; + viewer: + | { __typename?: "Anonymous"; id: string } + | { __typename?: "User"; id: string }; +}; + +export type CurrentViewerSessionQueryQueryVariables = Exact<{ + [key: string]: never; +}>; + +export type CurrentViewerSessionQueryQuery = { + __typename?: "Query"; + viewerSession: + | { __typename?: "Anonymous"; id: string } + | { __typename?: "BrowserSession"; id: string }; +}; + export type AddEmailMutationVariables = Exact<{ userId: Scalars["ID"]; email: Scalars["String"]; @@ -698,7 +718,7 @@ export type UserEmailListQueryQueryVariables = Exact<{ export type UserEmailListQueryQuery = { __typename?: "Query"; user?: { - __typename: "User"; + __typename?: "User"; id: string; emails: { __typename?: "UserEmailConnection"; @@ -723,13 +743,6 @@ export type UserEmailListQueryQuery = { } | null; }; -export type CurrentUserQueryQueryVariables = Exact<{ [key: string]: never }>; - -export type CurrentUserQueryQuery = { - __typename?: "Query"; - viewer: { __typename?: "Anonymous" } | { __typename: "User"; id: string }; -}; - export type AccountQueryQueryVariables = Exact<{ id: Scalars["ID"]; }>; @@ -758,10 +771,7 @@ export type BrowserSessionQueryQuery = { } | null; }; -export type HomeQueryQueryVariables = Exact<{ - count: Scalars["Int"]; - cursor?: InputMaybe; -}>; +export type HomeQueryQueryVariables = Exact<{ [key: string]: never }>; export type HomeQueryQuery = { __typename?: "Query"; @@ -847,18 +857,7 @@ export const BrowserSessionList_UserFragmentDoc = { { kind: "Argument", name: { kind: "Name", value: "first" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "count" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "after" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "cursor" }, - }, + value: { kind: "IntValue", value: "10" }, }, ], selectionSet: { @@ -985,18 +984,7 @@ export const CompatSsoLoginList_UserFragmentDoc = { { kind: "Argument", name: { kind: "Name", value: "first" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "count" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "after" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "cursor" }, - }, + value: { kind: "IntValue", value: "10" }, }, ], selectionSet: { @@ -1121,18 +1109,7 @@ export const OAuth2SessionList_UserFragmentDoc = { { kind: "Argument", name: { kind: "Name", value: "first" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "count" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "after" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "cursor" }, - }, + value: { kind: "IntValue", value: "10" }, }, ], selectionSet: { @@ -1229,6 +1206,112 @@ export const UserEmail_EmailFragmentDoc = { }, ], } as unknown as DocumentNode; +export const CurrentViewerQueryDocument = { + kind: "Document", + definitions: [ + { + kind: "OperationDefinition", + operation: "query", + name: { kind: "Name", value: "CurrentViewerQuery" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "viewer" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "InlineFragment", + typeCondition: { + kind: "NamedType", + name: { kind: "Name", value: "User" }, + }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + ], + }, + }, + { + kind: "InlineFragment", + typeCondition: { + kind: "NamedType", + name: { kind: "Name", value: "Anonymous" }, + }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode< + CurrentViewerQueryQuery, + CurrentViewerQueryQueryVariables +>; +export const CurrentViewerSessionQueryDocument = { + kind: "Document", + definitions: [ + { + kind: "OperationDefinition", + operation: "query", + name: { kind: "Name", value: "CurrentViewerSessionQuery" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "viewerSession" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "InlineFragment", + typeCondition: { + kind: "NamedType", + name: { kind: "Name", value: "BrowserSession" }, + }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + ], + }, + }, + { + kind: "InlineFragment", + typeCondition: { + kind: "NamedType", + name: { kind: "Name", value: "Anonymous" }, + }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode< + CurrentViewerSessionQueryQuery, + CurrentViewerSessionQueryQueryVariables +>; export const AddEmailDocument = { kind: "Document", definitions: [ @@ -1417,7 +1500,6 @@ export const UserEmailListQueryDocument = { selectionSet: { kind: "SelectionSet", selections: [ - { kind: "Field", name: { kind: "Name", value: "__typename" } }, { kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", @@ -1552,50 +1634,6 @@ export const UserEmailListQueryDocument = { UserEmailListQueryQuery, UserEmailListQueryQueryVariables >; -export const CurrentUserQueryDocument = { - kind: "Document", - definitions: [ - { - kind: "OperationDefinition", - operation: "query", - name: { kind: "Name", value: "CurrentUserQuery" }, - selectionSet: { - kind: "SelectionSet", - selections: [ - { - kind: "Field", - name: { kind: "Name", value: "viewer" }, - selectionSet: { - kind: "SelectionSet", - selections: [ - { - kind: "InlineFragment", - typeCondition: { - kind: "NamedType", - name: { kind: "Name", value: "User" }, - }, - selectionSet: { - kind: "SelectionSet", - selections: [ - { - kind: "Field", - name: { kind: "Name", value: "__typename" }, - }, - { kind: "Field", name: { kind: "Name", value: "id" } }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - ], -} as unknown as DocumentNode< - CurrentUserQueryQuery, - CurrentUserQueryQueryVariables ->; export const AccountQueryDocument = { kind: "Document", definitions: [ @@ -1726,27 +1764,6 @@ export const HomeQueryDocument = { kind: "OperationDefinition", operation: "query", name: { kind: "Name", value: "HomeQuery" }, - variableDefinitions: [ - { - kind: "VariableDefinition", - variable: { - kind: "Variable", - name: { kind: "Name", value: "count" }, - }, - type: { - kind: "NonNullType", - type: { kind: "NamedType", name: { kind: "Name", value: "Int" } }, - }, - }, - { - kind: "VariableDefinition", - variable: { - kind: "Variable", - name: { kind: "Name", value: "cursor" }, - }, - type: { kind: "NamedType", name: { kind: "Name", value: "String" } }, - }, - ], selectionSet: { kind: "SelectionSet", selections: [ @@ -1895,18 +1912,7 @@ export const HomeQueryDocument = { { kind: "Argument", name: { kind: "Name", value: "first" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "count" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "after" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "cursor" }, - }, + value: { kind: "IntValue", value: "10" }, }, ], selectionSet: { @@ -1964,18 +1970,7 @@ export const HomeQueryDocument = { { kind: "Argument", name: { kind: "Name", value: "first" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "count" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "after" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "cursor" }, - }, + value: { kind: "IntValue", value: "10" }, }, ], selectionSet: { @@ -2037,18 +2032,7 @@ export const HomeQueryDocument = { { kind: "Argument", name: { kind: "Name", value: "first" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "count" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "after" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "cursor" }, - }, + value: { kind: "IntValue", value: "10" }, }, ], selectionSet: { diff --git a/frontend/src/graphql.ts b/frontend/src/graphql.ts index 8f8f8a17..fd2745a9 100644 --- a/frontend/src/graphql.ts +++ b/frontend/src/graphql.ts @@ -17,33 +17,35 @@ import { cacheExchange } from "@urql/exchange-graphcache"; import schema from "./gql/schema"; import type { MutationAddEmailArgs } from "./gql/graphql"; +import { devtoolsExchange } from "@urql/devtools"; + +const cache = cacheExchange({ + schema, + updates: { + Mutation: { + addEmail: (result, args: MutationAddEmailArgs, cache, _info) => { + const key = cache.keyOfEntity({ + __typename: "User", + id: args.input.userId, + }); + + // Invalidate the emails field on the User object so that it gets refetched + cache + .inspectFields(key) + .filter((field) => field.fieldName === "emails") + .forEach((field) => { + cache.invalidate(key, field.fieldName, field.arguments); + }); + }, + }, + }, +}); export const client = createClient({ url: "/graphql", // XXX: else queries don't refetch on cache invalidation for some reason requestPolicy: "cache-and-network", - exchanges: [ - cacheExchange({ - schema, - updates: { - Mutation: { - addEmail: (result, args: MutationAddEmailArgs, cache, _info) => { - const key = cache.keyOfEntity({ - __typename: "User", - id: args.input.userId, - }); - - // Invalidate the emails field on the User object so that it gets refetched - cache - .inspectFields(key) - .filter((field) => field.fieldName === "emails") - .forEach((field) => { - cache.invalidate(key, field.fieldName, field.arguments); - }); - }, - }, - }, - }), - fetchExchange, - ], + exchanges: import.meta.env.DEV + ? [devtoolsExchange, cache, fetchExchange] + : [cache, fetchExchange], }); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 0fc771d3..8fba362d 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -24,7 +24,7 @@ import { HydrateAtoms } from "./atoms"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + {import.meta.env.DEV && } }> diff --git a/frontend/src/pages/Account.tsx b/frontend/src/pages/Account.tsx index 04d7acab..72d7e74a 100644 --- a/frontend/src/pages/Account.tsx +++ b/frontend/src/pages/Account.tsx @@ -21,19 +21,7 @@ import { graphql } from "../gql"; import UserEmailList from "../components/UserEmailList"; import { Title } from "../components/Typography"; import AddEmailForm from "../components/AddEmailForm"; - -const CURRENT_USER_QUERY = graphql(/* GraphQL */ ` - query CurrentUserQuery { - viewer { - ... on User { - __typename - id - } - } - } -`); - -const currentUserAtom = atomWithQuery({ query: CURRENT_USER_QUERY }); +import { currentUserIdAtom } from "../atoms"; const QUERY = graphql(/* GraphQL */ ` query AccountQuery($id: ID!) { @@ -61,11 +49,11 @@ const UserAccount: React.FC<{ id: string }> = ({ id }) => { }; const CurrentUserAccount: React.FC = () => { - const result = useAtomValue(currentUserAtom); - if (result.data?.viewer?.__typename === "User") { + const userId = useAtomValue(currentUserIdAtom); + if (userId !== null) { return (
- +
); } diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 0b9ed602..37e84955 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -22,7 +22,7 @@ import Typography from "../components/Typography"; import { graphql } from "../gql"; const QUERY = graphql(/* GraphQL */ ` - query HomeQuery($count: Int!, $cursor: String) { + query HomeQuery { # eslint-disable-next-line @graphql-eslint/no-deprecated currentBrowserSession { id @@ -40,7 +40,6 @@ const QUERY = graphql(/* GraphQL */ ` const homeDataAtom = atomWithQuery({ query: QUERY, - getVariables: () => ({ count: 10 }), }); const Home: React.FC = () => {