You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-20 12:02:22 +03:00
Nicer email management UI
This commit is contained in:
@@ -17,12 +17,16 @@ import { atomWithMutation } from "jotai-urql";
|
||||
import { useRef, useTransition } from "react";
|
||||
|
||||
import { graphql } from "../gql";
|
||||
import { LAST_PAGE } from "../pagination";
|
||||
|
||||
import Button from "./Button";
|
||||
import Input from "./Input";
|
||||
import Typography from "./Typography";
|
||||
import UserEmail from "./UserEmail";
|
||||
import { emailPageResultFamily } from "./UserEmailList";
|
||||
import {
|
||||
currentPaginationAtom,
|
||||
emailPageResultFamily,
|
||||
primaryEmailResultFamily,
|
||||
} from "./UserEmailList";
|
||||
|
||||
const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation AddEmail($userId: ID!, $email: String!) {
|
||||
@@ -36,24 +40,36 @@ const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const addUserEmailAtom = atomWithMutation(ADD_EMAIL_MUTATION);
|
||||
export const addUserEmailAtom = atomWithMutation(ADD_EMAIL_MUTATION);
|
||||
|
||||
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 refetchPrimaryEmail = useSetAtom(primaryEmailResultFamily(userId));
|
||||
const setCurrentPagination = useSetAtom(currentPaginationAtom);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const email = e.currentTarget.email.value;
|
||||
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const email = formData.get("email") as string;
|
||||
startTransition(() => {
|
||||
addEmail({ userId, email }).then(() => {
|
||||
startTransition(() => {
|
||||
// Paginate to the last page
|
||||
setCurrentPagination(LAST_PAGE);
|
||||
|
||||
// Make it refetch the list and the primary email, in case they changed
|
||||
refetchList();
|
||||
if (formRef.current) {
|
||||
formRef.current.reset();
|
||||
}
|
||||
refetchPrimaryEmail();
|
||||
|
||||
// Reset the form
|
||||
formRef.current?.reset();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -65,7 +81,6 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
<div className="pt-4">
|
||||
<Typography variant="subtitle">Email added!</Typography>
|
||||
</div>
|
||||
<UserEmail email={addEmailResult.data?.addEmail.email} />
|
||||
</>
|
||||
)}
|
||||
{addEmailResult.data?.addEmail.status === "EXISTS" && (
|
||||
@@ -73,14 +88,14 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
<div className="pt-4">
|
||||
<Typography variant="subtitle">Email already exists!</Typography>
|
||||
</div>
|
||||
<UserEmail email={addEmailResult.data?.addEmail.email} />
|
||||
</>
|
||||
)}
|
||||
<form className="flex" onSubmit={handleSubmit} ref={formRef}>
|
||||
<Input
|
||||
className="flex-1 mr-2"
|
||||
disabled={pending}
|
||||
type="text"
|
||||
type="email"
|
||||
inputMode="email"
|
||||
name="email"
|
||||
/>
|
||||
<Button disabled={pending} type="submit">
|
||||
|
||||
@@ -14,11 +14,18 @@
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
highlight?: boolean;
|
||||
};
|
||||
|
||||
const Block: React.FC<Props> = ({ children }) => {
|
||||
const Block: React.FC<Props> = ({ children, highlight }) => {
|
||||
return (
|
||||
<div className="p-4 bg-grey-50 dark:bg-grey-450 dark:text-white rounded">
|
||||
<div
|
||||
className={`p-4 dark:text-white rounded ${
|
||||
highlight
|
||||
? "border-2 border-grey-50 dark:border-grey-450 bg-white dark:bg-black"
|
||||
: "bg-grey-50 dark:bg-grey-450"
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,9 +19,9 @@ type Props = {
|
||||
|
||||
const Input: React.FC<Props> = ({ disabled, className, ...props }) => {
|
||||
const disabledClass = disabled
|
||||
? "bg-grey-50 dark:bg-grey-400"
|
||||
? "bg-grey-100 dark:bg-grey-400"
|
||||
: "bg-white dark:bg-grey-450";
|
||||
const fullClassName = `${className} px-2 py-1 border-2 border-grey-50 dark:border-grey-400 dark:text-white placeholder-grey-100 dark:placeholder-grey-150 rounded-lg ${disabledClass}`;
|
||||
const fullClassName = `${className} px-2 py-1 border-2 border-grey-100 dark:border-grey-400 dark:text-white placeholder-grey-100 dark:placeholder-grey-150 rounded-lg ${disabledClass}`;
|
||||
return <input disabled={disabled} className={fullClassName} {...props} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,10 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { atom, useAtom, useSetAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { atomWithMutation } from "jotai-urql";
|
||||
import { useRef, useTransition } from "react";
|
||||
|
||||
import { FragmentType, graphql, useFragment } from "../gql";
|
||||
|
||||
import Block from "./Block";
|
||||
import Button from "./Button";
|
||||
import DateTime from "./DateTime";
|
||||
import Input from "./Input";
|
||||
import Typography, { Bold } from "./Typography";
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
@@ -27,24 +34,141 @@ const FRAGMENT = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const UserEmail: React.FC<{ email: FragmentType<typeof FRAGMENT> }> = ({
|
||||
email,
|
||||
}) => {
|
||||
const VERIFY_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation VerifyEmail($id: ID!, $code: String!) {
|
||||
verifyEmail(input: { userEmailId: $id, code: $code }) {
|
||||
status
|
||||
|
||||
user {
|
||||
id
|
||||
primaryEmail {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
email {
|
||||
id
|
||||
...UserEmail_email
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const RESEND_VERIFICATION_EMAIL_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation ResendVerificationEmail($id: ID!) {
|
||||
sendVerificationEmail(input: { userEmailId: $id }) {
|
||||
status
|
||||
|
||||
user {
|
||||
id
|
||||
primaryEmail {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
email {
|
||||
id
|
||||
...UserEmail_email
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const verifyEmailFamily = atomFamily((id: string) => {
|
||||
const verifyEmail = atomWithMutation(VERIFY_EMAIL_MUTATION);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const verifyEmailAtom = atom(
|
||||
(get) => get(verifyEmail),
|
||||
(get, set, code: string) => set(verifyEmail, { id, code })
|
||||
);
|
||||
|
||||
return verifyEmailAtom;
|
||||
});
|
||||
|
||||
const resendVerificationEmailFamily = atomFamily((id: string) => {
|
||||
const resendVerificationEmail = atomWithMutation(
|
||||
RESEND_VERIFICATION_EMAIL_MUTATION
|
||||
);
|
||||
|
||||
// A proxy atom which pre-sets the id variable in the mutation
|
||||
const resendVerificationEmailAtom = atom(
|
||||
(get) => get(resendVerificationEmail),
|
||||
(get, set) => set(resendVerificationEmail, { id })
|
||||
);
|
||||
|
||||
return resendVerificationEmailAtom;
|
||||
});
|
||||
|
||||
const UserEmail: React.FC<{
|
||||
email: FragmentType<typeof FRAGMENT>;
|
||||
isPrimary?: boolean;
|
||||
highlight?: boolean;
|
||||
}> = ({ email, isPrimary, highlight }) => {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const data = useFragment(FRAGMENT, email);
|
||||
const [verifyEmailResult, verifyEmail] = useAtom(verifyEmailFamily(data.id));
|
||||
const [resendVerificationEmailResult, resendVerificationEmail] = useAtom(
|
||||
resendVerificationEmailFamily(data.id)
|
||||
);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const code = formData.get("code") as string;
|
||||
startTransition(() => {
|
||||
verifyEmail(code).then(() => {
|
||||
formRef.current?.reset();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onResendClick = () => {
|
||||
startTransition(() => {
|
||||
resendVerificationEmail().then(() => {
|
||||
formRef.current?.code.focus();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const emailSent =
|
||||
resendVerificationEmailResult.data?.sendVerificationEmail.status === "SENT";
|
||||
|
||||
return (
|
||||
<Block>
|
||||
<Block highlight={highlight}>
|
||||
{isPrimary && (
|
||||
<Typography variant="body" bold>
|
||||
Primary
|
||||
</Typography>
|
||||
)}
|
||||
<Typography variant="caption">
|
||||
<Bold>{data.email}</Bold>
|
||||
{data.confirmedAt ? "" : " (not verified)"}
|
||||
</Typography>
|
||||
{data.confirmedAt ? (
|
||||
<Typography variant="micro">
|
||||
Verified <DateTime datetime={data.confirmedAt} />
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography variant="micro">
|
||||
Added <DateTime datetime={data.createdAt} />
|
||||
</Typography>
|
||||
<form
|
||||
onSubmit={onFormSubmit}
|
||||
className="mt-2 grid grid-cols-2 gap-2"
|
||||
ref={formRef}
|
||||
>
|
||||
<Input
|
||||
className="col-span-2"
|
||||
name="code"
|
||||
placeholder="Code"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
/>
|
||||
<Button type="submit" disabled={pending}>
|
||||
Submit
|
||||
</Button>
|
||||
<Button disabled={pending || emailSent} onClick={onResendClick}>
|
||||
{emailSent ? "Sent!" : "Resend"}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</Block>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,12 @@ import { useTransition } from "react";
|
||||
|
||||
import { graphql } from "../gql";
|
||||
import { PageInfo } from "../gql/graphql";
|
||||
import { atomWithPagination, pageSizeAtom, Pagination } from "../pagination";
|
||||
import {
|
||||
atomForCurrentPagination,
|
||||
atomWithPagination,
|
||||
pageSizeAtom,
|
||||
Pagination,
|
||||
} from "../pagination";
|
||||
|
||||
import BlockList from "./BlockList";
|
||||
import PaginationControls from "./PaginationControls";
|
||||
@@ -35,6 +40,7 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
) {
|
||||
user(id: $userId) {
|
||||
id
|
||||
|
||||
emails(first: $first, after: $after, last: $last, before: $before) {
|
||||
edges {
|
||||
cursor
|
||||
@@ -55,15 +61,40 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const currentPagination = atomWithDefault<Pagination>((get) => ({
|
||||
first: get(pageSizeAtom),
|
||||
after: null,
|
||||
}));
|
||||
const PRIMARY_EMAIL_QUERY = graphql(/* GraphQL */ `
|
||||
query UserPrimaryEmail($userId: ID!) {
|
||||
user(id: $userId) {
|
||||
id
|
||||
primaryEmail {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const primaryEmailResultFamily = atomFamily((userId: string) => {
|
||||
const primaryEmailResult = atomWithQuery({
|
||||
query: PRIMARY_EMAIL_QUERY,
|
||||
getVariables: () => ({ userId }),
|
||||
});
|
||||
return primaryEmailResult;
|
||||
});
|
||||
|
||||
const primaryEmailIdFamily = atomFamily((userId: string) => {
|
||||
const primaryEmailIdAtom = atom(async (get) => {
|
||||
const result = await get(primaryEmailResultFamily(userId));
|
||||
return result.data?.user?.primaryEmail?.id ?? null;
|
||||
});
|
||||
|
||||
return primaryEmailIdAtom;
|
||||
});
|
||||
|
||||
export const currentPaginationAtom = atomForCurrentPagination();
|
||||
|
||||
export const emailPageResultFamily = atomFamily((userId: string) => {
|
||||
const emailPageResult = atomWithQuery({
|
||||
query: QUERY,
|
||||
getVariables: (get) => ({ userId, ...get(currentPagination) }),
|
||||
getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }),
|
||||
});
|
||||
return emailPageResult;
|
||||
});
|
||||
@@ -79,17 +110,21 @@ const pageInfoFamily = atomFamily((userId: string) => {
|
||||
|
||||
const paginationFamily = atomFamily((userId: string) => {
|
||||
const paginationAtom = atomWithPagination(
|
||||
currentPagination,
|
||||
currentPaginationAtom,
|
||||
pageInfoFamily(userId)
|
||||
);
|
||||
return paginationAtom;
|
||||
});
|
||||
|
||||
const UserEmailList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
const UserEmailList: React.FC<{
|
||||
userId: string;
|
||||
highlightedEmail?: string;
|
||||
}> = ({ userId, highlightedEmail }) => {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const result = useAtomValue(emailPageResultFamily(userId));
|
||||
const setPagination = useSetAtom(currentPagination);
|
||||
const setPagination = useSetAtom(currentPaginationAtom);
|
||||
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
|
||||
const primaryEmailId = useAtomValue(primaryEmailIdFamily(userId));
|
||||
|
||||
const paginate = (pagination: Pagination) => {
|
||||
startTransition(() => {
|
||||
@@ -106,7 +141,12 @@ const UserEmailList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||
disabled={pending}
|
||||
/>
|
||||
{result.data?.user?.emails?.edges?.map((edge) => (
|
||||
<UserEmail email={edge.node} key={edge.cursor} />
|
||||
<UserEmail
|
||||
email={edge.node}
|
||||
key={edge.cursor}
|
||||
isPrimary={primaryEmailId === edge.node.id}
|
||||
highlight={highlightedEmail === edge.node.id}
|
||||
/>
|
||||
))}
|
||||
</BlockList>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ import { atomWithQuery } from "jotai-urql";
|
||||
|
||||
import { graphql } from "../gql";
|
||||
|
||||
import { Title } from "./Typography";
|
||||
import Typography from "./Typography";
|
||||
|
||||
const QUERY = graphql(/* GraphQL */ `
|
||||
query UserGreeting($userId: ID!) {
|
||||
@@ -42,7 +42,11 @@ 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 (
|
||||
<Typography variant="headline">
|
||||
Hello, {result.data.user.username}!
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return <>Failed to load user</>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable */
|
||||
import * as types from './graphql';
|
||||
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
import * as types from "./graphql";
|
||||
import { TypedDocumentNode as DocumentNode } from "@graphql-typed-document-node/core";
|
||||
|
||||
/**
|
||||
* Map of all GraphQL operations in the project.
|
||||
@@ -13,20 +13,40 @@ 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 __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 __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 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 query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n": types.BrowserSessionListDocument,
|
||||
"\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n }\n": types.CompatSsoLogin_LoginFragmentDoc,
|
||||
"\n query CompatSsoLoginList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSsoLogins(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n": types.CompatSsoLoginListDocument,
|
||||
"\n 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 query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n": types.OAuth2SessionListQueryDocument,
|
||||
"\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n": 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 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 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,
|
||||
"\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 __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 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 query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.BrowserSessionListDocument,
|
||||
"\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n }\n":
|
||||
types.CompatSsoLogin_LoginFragmentDoc,
|
||||
"\n query CompatSsoLoginList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSsoLogins(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.CompatSsoLoginListDocument,
|
||||
"\n 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 query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
|
||||
types.OAuth2SessionListQueryDocument,
|
||||
"\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n":
|
||||
types.UserEmail_EmailFragmentDoc,
|
||||
"\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.VerifyEmailDocument,
|
||||
"\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||
types.ResendVerificationEmailDocument,
|
||||
"\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\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 UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\n id\n }\n }\n }\n":
|
||||
types.UserPrimaryEmailDocument,
|
||||
"\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 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,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -46,62 +66,109 @@ 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 __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"];
|
||||
export function graphql(
|
||||
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 __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"];
|
||||
export function graphql(
|
||||
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.
|
||||
*/
|
||||
export function graphql(source: "\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"): (typeof documents)["\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"];
|
||||
export function graphql(
|
||||
source: "\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"
|
||||
): (typeof documents)["\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n email {\n id\n ...UserEmail_email\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 fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"): (typeof documents)["\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"];
|
||||
export function graphql(
|
||||
source: "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"
|
||||
): (typeof documents)["\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"): (typeof documents)["\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"];
|
||||
export function graphql(
|
||||
source: "\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"
|
||||
): (typeof documents)["\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n }\n"): (typeof documents)["\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n }\n"];
|
||||
export function graphql(
|
||||
source: "\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"
|
||||
): (typeof documents)["\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"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query CompatSsoLoginList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSsoLogins(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"): (typeof documents)["\n query CompatSsoLoginList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSsoLogins(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"];
|
||||
export function graphql(
|
||||
source: "\n query CompatSsoLoginList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSsoLogins(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"
|
||||
): (typeof documents)["\n query CompatSsoLoginList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n compatSsoLogins(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* 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 OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n"): (typeof documents)["\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n"];
|
||||
export function graphql(
|
||||
source: "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n"
|
||||
): (typeof documents)["\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\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 OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"): (typeof documents)["\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"];
|
||||
export function graphql(
|
||||
source: "\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"
|
||||
): (typeof documents)["\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* 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 UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n"): (typeof documents)["\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n"];
|
||||
export function graphql(
|
||||
source: "\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n"
|
||||
): (typeof documents)["\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\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 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"];
|
||||
export function graphql(
|
||||
source: "\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"
|
||||
): (typeof documents)["\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\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 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"];
|
||||
export function graphql(
|
||||
source: "\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n"
|
||||
): (typeof documents)["\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\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 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"];
|
||||
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 id\n\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\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 OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n"): (typeof documents)["\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"];
|
||||
export function graphql(
|
||||
source: "\n query UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\n id\n }\n }\n }\n"
|
||||
): (typeof documents)["\n query UserPrimaryEmail($userId: ID!) {\n user(id: $userId) {\n id\n primaryEmail {\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 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 OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n"
|
||||
): (typeof documents)["\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"];
|
||||
|
||||
export function graphql(source: string) {
|
||||
return (documents as any)[source] ?? {};
|
||||
}
|
||||
|
||||
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never;
|
||||
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> =
|
||||
TDocumentNode extends DocumentNode<infer TType, any> ? TType : never;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,15 +15,19 @@
|
||||
import { useAtomValue } from "jotai";
|
||||
|
||||
import { currentUserIdAtom } from "../atoms";
|
||||
import AddEmailForm from "../components/AddEmailForm";
|
||||
import AddEmailForm, { addUserEmailAtom } from "../components/AddEmailForm";
|
||||
import UserEmailList from "../components/UserEmailList";
|
||||
import UserGreeting from "../components/UserGreeting";
|
||||
|
||||
const UserAccount: React.FC<{ id: string }> = ({ id }) => {
|
||||
const addUserEmail = useAtomValue(addUserEmailAtom);
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<UserGreeting userId={id} />
|
||||
<UserEmailList userId={id} />
|
||||
<UserEmailList
|
||||
userId={id}
|
||||
highlightedEmail={addUserEmail.data?.addEmail?.email?.id}
|
||||
/>
|
||||
<AddEmailForm userId={id} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,10 @@ import { atom, Atom } from "jotai";
|
||||
|
||||
import { PageInfo } from "./gql/graphql";
|
||||
|
||||
export const FIRST_PAGE = Symbol("FIRST_PAGE");
|
||||
export const LAST_PAGE = Symbol("LAST_PAGE");
|
||||
const EMPTY = Symbol("EMPTY");
|
||||
|
||||
export type ForwardPagination = {
|
||||
first: number;
|
||||
after: string | null;
|
||||
@@ -45,6 +49,42 @@ export const isBackwardPagination = (
|
||||
// This atom sets the default page size for pagination.
|
||||
export const pageSizeAtom = atom(6);
|
||||
|
||||
export const atomForCurrentPagination = () => {
|
||||
const dataAtom = atom<typeof EMPTY | Pagination>(EMPTY);
|
||||
|
||||
const currentPaginationAtom = atom(
|
||||
(get) => {
|
||||
const data = get(dataAtom);
|
||||
if (data === EMPTY) {
|
||||
return {
|
||||
first: get(pageSizeAtom),
|
||||
after: null,
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
(get, set, action: Pagination | typeof FIRST_PAGE | typeof LAST_PAGE) => {
|
||||
if (action === FIRST_PAGE) {
|
||||
set(dataAtom, EMPTY);
|
||||
} else if (action === LAST_PAGE) {
|
||||
set(dataAtom, {
|
||||
last: get(pageSizeAtom),
|
||||
before: null,
|
||||
});
|
||||
} else {
|
||||
set(dataAtom, action);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
currentPaginationAtom.onMount = (setAtom) => {
|
||||
setAtom(FIRST_PAGE);
|
||||
};
|
||||
|
||||
return currentPaginationAtom;
|
||||
};
|
||||
|
||||
// This atom is used to create a pagination atom that gives the previous and
|
||||
// next pagination objects, given the current pagination and the page info.
|
||||
export const atomWithPagination = (
|
||||
|
||||
Reference in New Issue
Block a user