Make Profile page the default route (#1653)

* make profile the default route

* src/pages/Home.tsx -> src/pages/SessionsOverview.tsx

* UserHome -> UserSessionsOverview

* update snapshots, fix session overview button alignment
This commit is contained in:
Kerry
2023-08-31 11:27:39 +12:00
committed by GitHub
parent bea4d57124
commit da8a489748
19 changed files with 189 additions and 184 deletions
@@ -0,0 +1,27 @@
/* 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.
*/
.session-list-block {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
gap: var(--cpd-space-1x);
}
.session-list-block-info {
display: flex;
flex-direction: column;
}
@@ -0,0 +1,135 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { Meta, StoryObj } from "@storybook/react";
import { Provider } from "jotai";
import { useHydrateAtoms } from "jotai/utils";
import { appConfigAtom, locationAtom } from "../../Router";
import { makeFragmentData } from "../../gql";
import { FRAGMENT as EMAIL_FRAGMENT } from "../UserEmail";
import UserSessionsOverview, { FRAGMENT } from "./UserSessionsOverview";
type Props = {
primaryEmail: string | null;
unverifiedEmails: number;
confirmedEmails: number;
oauth2Sessions: number;
browserSessions: number;
compatSessions: number;
};
const WithHomePage: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
useHydrateAtoms([
[appConfigAtom, { root: "/" }],
[locationAtom, { pathname: "/" }],
]);
return <>{children}</>;
};
const Template: React.FC<Props> = ({
primaryEmail: email,
unverifiedEmails,
confirmedEmails,
oauth2Sessions,
browserSessions,
compatSessions,
}) => {
let primaryEmail = null;
if (email) {
primaryEmail = {
id: "email:123",
...makeFragmentData(
{
id: "email:123",
email,
confirmedAt: new Date(),
},
EMAIL_FRAGMENT,
),
};
}
const data = makeFragmentData(
{
id: "user:123",
primaryEmail,
unverifiedEmails: {
totalCount: unverifiedEmails,
},
confirmedEmails: {
totalCount: confirmedEmails,
},
oauth2Sessions: {
totalCount: oauth2Sessions,
},
browserSessions: {
totalCount: browserSessions,
},
compatSessions: {
totalCount: compatSessions,
},
},
FRAGMENT,
);
return (
<Provider>
<WithHomePage>
<UserSessionsOverview user={data} />
</WithHomePage>
</Provider>
);
};
const meta = {
title: "Pages/User Sessions Overview",
component: Template,
tags: ["autodocs"],
} satisfies Meta<typeof Template>;
export default meta;
type Story = StoryObj<typeof Template>;
export const Basic: Story = {
args: {
primaryEmail: "hello@example.com",
unverifiedEmails: 0,
confirmedEmails: 5,
oauth2Sessions: 3,
compatSessions: 1,
browserSessions: 2,
},
};
export const Empty: Story = {
args: {
primaryEmail: "hello@example.com",
unverifiedEmails: 0,
confirmedEmails: 1,
oauth2Sessions: 0,
compatSessions: 0,
browserSessions: 0,
},
};
export const UnverifiedEmails: Story = {
args: {
primaryEmail: "hello@example.com",
unverifiedEmails: 1,
confirmedEmails: 1,
oauth2Sessions: 0,
compatSessions: 0,
browserSessions: 0,
},
};
@@ -0,0 +1,103 @@
// 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 { create } from "react-test-renderer";
import { describe, expect, it } from "vitest";
import { makeFragmentData } from "../../gql";
import { FRAGMENT as EMAIL_FRAGMENT } from "../UserEmail";
import UserSessionsOverview, { FRAGMENT } from "./";
describe("UserSessionsOverview", () => {
it("render an simple <UserSessionsOverview />", () => {
const primaryEmail = makeFragmentData(
{
id: "email:123",
email: "hello@example.com",
confirmedAt: new Date(),
},
EMAIL_FRAGMENT,
);
const user = makeFragmentData(
{
id: "user:123",
primaryEmail: {
id: "email:123",
...primaryEmail,
},
compatSessions: {
totalCount: 0,
},
browserSessions: {
totalCount: 0,
},
oauth2Sessions: {
totalCount: 0,
},
unverifiedEmails: {
totalCount: 0,
},
confirmedEmails: {
totalCount: 1,
},
},
FRAGMENT,
);
const component = create(<UserSessionsOverview user={user} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it("render a <UserSessionsOverview /> with sessions", () => {
const primaryEmail = makeFragmentData(
{
id: "email:123",
email: "hello@example.com",
confirmedAt: new Date(),
},
EMAIL_FRAGMENT,
);
const user = makeFragmentData(
{
id: "user:123",
primaryEmail: {
id: "email:123",
...primaryEmail,
},
compatSessions: {
totalCount: 1,
},
browserSessions: {
totalCount: 2,
},
oauth2Sessions: {
totalCount: 3,
},
unverifiedEmails: {
totalCount: 0,
},
confirmedEmails: {
totalCount: 1,
},
},
FRAGMENT,
);
const component = create(<UserSessionsOverview user={user} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
@@ -0,0 +1,109 @@
// 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 { Body, H3, H6 } from "@vector-im/compound-web";
import { Link } from "../../Router";
import { FragmentType, graphql, useFragment } from "../../gql";
import Block from "../Block/Block";
import BlockList from "../BlockList/BlockList";
import styles from "./UserSessionsOverview.module.css";
export const FRAGMENT = graphql(/* GraphQL */ `
fragment UserSessionsOverview_user on User {
id
primaryEmail {
id
...UserEmail_email
}
confirmedEmails: emails(first: 0, state: CONFIRMED) {
totalCount
}
browserSessions(first: 0, state: ACTIVE) {
totalCount
}
oauth2Sessions(first: 0, state: ACTIVE) {
totalCount
}
compatSessions(first: 0, state: ACTIVE) {
totalCount
}
}
`);
const UserSessionsOverview: React.FC<{
user: FragmentType<typeof FRAGMENT>;
}> = ({ user }) => {
const data = useFragment(FRAGMENT, user);
// allow this until we get i18n
const pluraliseSession = (count: number): string =>
count === 1 ? "session" : "sessions";
// user friendly description of sessions is:
// browser -> browser
// oauth2 sessions -> New apps
// compatibility sessions -> Regular apps
return (
<BlockList>
{/* This is a short term solution, so I won't bother extracting these blocks into components */}
<H3>Where you're signed in</H3>
<Block className={styles.sessionListBlock}>
<div className={styles.sessionListBlockInfo}>
<H6>Browser</H6>
<Body>
{data.browserSessions.totalCount} active{" "}
{pluraliseSession(data.browserSessions.totalCount)}
</Body>
</div>
<Link kind="button" route={{ type: "browser-session-list" }}>
View all
</Link>
</Block>
<Block className={styles.sessionListBlock}>
<div className={styles.sessionListBlockInfo}>
<H6>New apps</H6>
<Body>
{data.oauth2Sessions.totalCount} active{" "}
{pluraliseSession(data.oauth2Sessions.totalCount)}
</Body>
</div>
<Link kind="button" route={{ type: "oauth2-session-list" }}>
View all
</Link>
</Block>
<Block className={styles.sessionListBlock}>
<div className={styles.sessionListBlockInfo}>
<H6>Regular apps</H6>
<Body>
{data.compatSessions.totalCount} active{" "}
{pluraliseSession(data.compatSessions.totalCount)}
</Body>
</div>
<Link kind="button" route={{ type: "compat-session-list" }}>
View all
</Link>
</Block>
</BlockList>
);
};
export default UserSessionsOverview;
@@ -0,0 +1,193 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`UserSessionsOverview > render a <UserSessionsOverview /> with sessions 1`] = `
<div
className="_blockList_f8cc7f"
>
<h3
className="_font-heading-md-semibold_1g2sj_129"
>
Where you're signed in
</h3>
<div
className="_block_17898c _sessionListBlock_1c0f31"
>
<div
className="_sessionListBlockInfo_1c0f31"
>
<h6
className="_font-body-md-semibold_1g2sj_69"
>
Browser
</h6>
<p
className="_font-body-md-regular_1g2sj_65"
>
2
active
sessions
</p>
</div>
<a
className="_linkButton_4f14fc"
href="/sessions"
onClick={[Function]}
>
View all
</a>
</div>
<div
className="_block_17898c _sessionListBlock_1c0f31"
>
<div
className="_sessionListBlockInfo_1c0f31"
>
<h6
className="_font-body-md-semibold_1g2sj_69"
>
New apps
</h6>
<p
className="_font-body-md-regular_1g2sj_65"
>
3
active
sessions
</p>
</div>
<a
className="_linkButton_4f14fc"
href="/oauth2-sessions"
onClick={[Function]}
>
View all
</a>
</div>
<div
className="_block_17898c _sessionListBlock_1c0f31"
>
<div
className="_sessionListBlockInfo_1c0f31"
>
<h6
className="_font-body-md-semibold_1g2sj_69"
>
Regular apps
</h6>
<p
className="_font-body-md-regular_1g2sj_65"
>
1
active
session
</p>
</div>
<a
className="_linkButton_4f14fc"
href="/compat-sessions"
onClick={[Function]}
>
View all
</a>
</div>
</div>
`;
exports[`UserSessionsOverview > render an simple <UserSessionsOverview /> 1`] = `
<div
className="_blockList_f8cc7f"
>
<h3
className="_font-heading-md-semibold_1g2sj_129"
>
Where you're signed in
</h3>
<div
className="_block_17898c _sessionListBlock_1c0f31"
>
<div
className="_sessionListBlockInfo_1c0f31"
>
<h6
className="_font-body-md-semibold_1g2sj_69"
>
Browser
</h6>
<p
className="_font-body-md-regular_1g2sj_65"
>
0
active
sessions
</p>
</div>
<a
className="_linkButton_4f14fc"
href="/sessions"
onClick={[Function]}
>
View all
</a>
</div>
<div
className="_block_17898c _sessionListBlock_1c0f31"
>
<div
className="_sessionListBlockInfo_1c0f31"
>
<h6
className="_font-body-md-semibold_1g2sj_69"
>
New apps
</h6>
<p
className="_font-body-md-regular_1g2sj_65"
>
0
active
sessions
</p>
</div>
<a
className="_linkButton_4f14fc"
href="/oauth2-sessions"
onClick={[Function]}
>
View all
</a>
</div>
<div
className="_block_17898c _sessionListBlock_1c0f31"
>
<div
className="_sessionListBlockInfo_1c0f31"
>
<h6
className="_font-body-md-semibold_1g2sj_69"
>
Regular apps
</h6>
<p
className="_font-body-md-regular_1g2sj_65"
>
0
active
sessions
</p>
</div>
<a
className="_linkButton_4f14fc"
href="/compat-sessions"
onClick={[Function]}
>
View all
</a>
</div>
</div>
`;
@@ -0,0 +1,15 @@
// 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.
export { default, FRAGMENT } from "./UserSessionsOverview";