Skip to content

Commit b527d03

Browse files
johanbookJohan Book
and
Johan Book
authored
feat(web-ui): redesign profile page (#842)
* feat(web-ui): redesign profile page * refactor(web-ui): move links * refactor(web-ui): move settings page to module --------- Co-authored-by: Johan Book <{ID}+{username}@users.noreply.github.com>
1 parent dba099a commit b527d03

20 files changed

+362
-228
lines changed

services/web-ui/public/locales/en/profile.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@
1212
},
1313
"header": "Profile",
1414
"links": {
15-
"blog": "Blog"
15+
"appearence": "Appearence",
16+
"blog": "Blog",
17+
"create-organization": "Create new group",
18+
"current-organization": "Current group",
19+
"journal": "Journal",
20+
"list-organizations": "Groups",
21+
"log-out": "Log out",
22+
"new-organization": "Create new group",
23+
"settings": "Settings"
1624
},
1725
"save": "Save",
1826
"update": {

services/web-ui/public/locales/en/settings.json

-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
11
{
2-
"advanced": {
3-
"header": "Advanced settings",
4-
"links": {
5-
"create-organization": "Create new group",
6-
"current-organization": "Manage current group",
7-
"journal": "Journal",
8-
"list-organizations": "See available groups",
9-
"new-organization": "Create new group"
10-
}
11-
},
122
"danger-zone": {
133
"description": "These are actions that cannot be undone",
144
"header": "Danger zone",

services/web-ui/public/locales/sv/profile.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@
1212
},
1313
"header": "Profil",
1414
"links": {
15-
"blog": "Blog"
15+
"appearence": "Utseende",
16+
"blog": "Blog",
17+
"create-organization": "Skapa ny grupp",
18+
"current-organization": "Nuvarande grupp",
19+
"journal": "Journal",
20+
"list-organizations": "Grupper",
21+
"log-out": "Logga ut",
22+
"settings": "Inställningar"
1623
},
1724
"save": "Spara",
1825
"update": {

services/web-ui/public/locales/sv/settings.json

-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
11
{
2-
"advanced": {
3-
"header": "Avancerade inställningar",
4-
"links": {
5-
"create-organization": "Skapa ny grupp",
6-
"current-organization": "Hantera nuvarande grupp",
7-
"journal": "Journal",
8-
"list-organizations": "Se dina grupper",
9-
"new-organization": "Skapa ny grupp"
10-
}
11-
},
122
"danger-zone": {
133
"description": "Detta är åtgärder som inte kan ångras",
144
"header": "Farozon",
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
11
{
2-
"advanced": {
3-
"header": "高级设置",
4-
"links": {
5-
"create-organization": "创建新的组织",
6-
"current-organization": "管理当前组织",
7-
"journal": "日志",
8-
"list-organizations": "查看可用组织",
9-
"new-organization": "创建新的组织"
10-
}
11-
},
122
"darkmode": "深色模式",
133
"header": "设置"
144
}

services/web-ui/src/Router.tsx

+11-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { BlogPostPage } from "src/features/blogs/pages/BlogPostPage";
99
import { ChatListPage } from "src/features/chat/pages/ChatListPage";
1010
import { ChatPage } from "src/features/chat/pages/ChatPage";
1111
import { CreateChatPage } from "src/features/chat/pages/CreateChatPage";
12+
import { SettingsPage } from "src/features/settings/pages/SettingsPage";
1213
import { CreateOrganizationPage } from "src/pages/CreateOrganizationPage";
1314
import { CurrentOrganizationPage } from "src/pages/CurrentOrganizationPage";
1415
import { CurrentProfilePage } from "src/pages/CurrentProfilePage";
@@ -18,9 +19,10 @@ import { OrganizationListPage } from "src/pages/OrganizationListPage";
1819
import { ProfileGuard } from "src/pages/ProfileGuard";
1920
import { ProfileJournalPage } from "src/pages/ProfileJournalPage";
2021
import { ProfilePage } from "src/pages/ProfilePage";
21-
import { SettingsPage } from "src/pages/SettingsPage";
2222
import { LoadingView } from "src/views/LoadingView";
2323

24+
import { AppearancePage } from "./features/settings/pages/AppearancePage";
25+
2426
const router = createBrowserRouter([
2527
{
2628
path: "/",
@@ -75,17 +77,21 @@ const router = createBrowserRouter([
7577
path: "/profile",
7678
element: <CurrentProfilePage />,
7779
},
80+
{
81+
path: "/profile/appearence",
82+
element: <AppearancePage />,
83+
},
7884
{
7985
path: "/profile/journal",
8086
element: <ProfileJournalPage />,
8187
},
8288
{
83-
path: "/profile/:id",
84-
element: <ProfilePage />,
89+
path: "/profile/settings",
90+
element: <SettingsPage />,
8591
},
8692
{
87-
path: "/settings",
88-
element: <SettingsPage />,
93+
path: "/profile/:id",
94+
element: <ProfilePage />,
8995
},
9096
],
9197
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { ReactElement } from "react";
2+
3+
import { Box, FormControlLabel, FormGroup } from "@mui/material";
4+
5+
import { SettingsDetails } from "src/api";
6+
import { settingsApi } from "src/apis";
7+
import { Switch } from "src/components/ui";
8+
import { useTranslation } from "src/core/i18n";
9+
import { useMutation, useQueryClient } from "src/core/query";
10+
import { CacheKeysConstants, useQuery } from "src/core/query";
11+
import { ErrorView } from "src/views/ErrorView";
12+
13+
import { AppearancePageNav } from "./AppearancePage.nav";
14+
import { AppearancePageSkeleton } from "./AppearancePage.skeleton";
15+
16+
export function AppearancePageContainer(): ReactElement {
17+
const { t } = useTranslation("settings");
18+
19+
const queryClient = useQueryClient();
20+
const { error, data, isPending } = useQuery({
21+
queryKey: [CacheKeysConstants.Settings],
22+
queryFn: () => settingsApi.getCurrentSettings(),
23+
});
24+
25+
// TODO: Investigate why type is incorrect here
26+
const mutation = useMutation({
27+
mutationFn: (body: object) => settingsApi.updateCurrentSettings({ body }),
28+
});
29+
30+
if (error) {
31+
return (
32+
<AppearancePageNav>
33+
<ErrorView />
34+
</AppearancePageNav>
35+
);
36+
}
37+
38+
if (isPending) {
39+
return (
40+
<AppearancePageNav>
41+
<AppearancePageSkeleton />
42+
</AppearancePageNav>
43+
);
44+
}
45+
46+
if (!data) {
47+
return (
48+
<AppearancePageNav>
49+
<ErrorView message="Settings not found" />
50+
</AppearancePageNav>
51+
);
52+
}
53+
54+
async function handleChange(settings: SettingsDetails): Promise<void> {
55+
await mutation.mutateAsync(settings, {
56+
onSuccess: () => {
57+
queryClient.invalidateQueries({
58+
queryKey: [CacheKeysConstants.Settings],
59+
});
60+
},
61+
});
62+
}
63+
64+
return (
65+
<Box
66+
sx={{
67+
display: "flex",
68+
flexDirection: "column",
69+
}}
70+
>
71+
<AppearancePageNav>
72+
<Box sx={{ flexGrow: 1 }}>
73+
<FormGroup>
74+
<FormControlLabel
75+
control={
76+
<Switch
77+
onChange={(value) =>
78+
handleChange({
79+
...data,
80+
darkmode: value,
81+
})
82+
}
83+
value={data.darkmode}
84+
/>
85+
}
86+
disabled={isPending || mutation.isPending}
87+
label={t("darkmode")}
88+
/>
89+
</FormGroup>
90+
</Box>
91+
</AppearancePageNav>
92+
</Box>
93+
);
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ReactElement, ReactNode } from "react";
2+
3+
import { NavLayout } from "src/components/layout";
4+
import { useTranslation } from "src/core/i18n";
5+
6+
interface AppearancePageNavProps {
7+
children: ReactNode;
8+
}
9+
10+
export function AppearancePageNav({
11+
children,
12+
}: AppearancePageNavProps): ReactElement {
13+
const { t } = useTranslation("settings");
14+
15+
return (
16+
<NavLayout header={t("header")} linkText="Back" to="/profile">
17+
{children}
18+
</NavLayout>
19+
);
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ReactElement } from "react";
2+
3+
import { Skeleton } from "@mui/material";
4+
5+
export function AppearancePageSkeleton(): ReactElement {
6+
return (
7+
<>
8+
<Skeleton />
9+
</>
10+
);
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ReactQueryTestProvider, TestRouter, render, screen } from "src/test";
2+
3+
import { AppearancePage } from ".";
4+
5+
describe.skip("<AppearancePage />", () => {
6+
it("renders message when no matches", async () => {
7+
render(
8+
<TestRouter>
9+
<ReactQueryTestProvider>
10+
<AppearancePage />
11+
</ReactQueryTestProvider>
12+
</TestRouter>
13+
);
14+
15+
const message = await screen.findByText(/You do not have any matches/);
16+
expect(message).toBeInTheDocument();
17+
});
18+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { AppearancePageContainer as AppearancePage } from "./AppearancePage.container";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { ReactElement } from "react";
2+
3+
import { Box } from "@mui/material";
4+
5+
import { profileApi, settingsApi } from "src/apis";
6+
import { Button, ConfirmationDialog, Typography } from "src/components/ui";
7+
import { useDialog } from "src/core/dialog";
8+
import { useTranslation } from "src/core/i18n";
9+
import { useMutation } from "src/core/query";
10+
import { CacheKeysConstants, useQuery } from "src/core/query";
11+
import { useSnackbar } from "src/core/snackbar";
12+
import { ErrorView } from "src/views/ErrorView";
13+
14+
import { SettingsPageNav } from "./SettingsPage.nav";
15+
import { SettingsPageSkeleton } from "./SettingsPage.skeleton";
16+
17+
export function SettingsPageContainer(): ReactElement {
18+
const { t } = useTranslation("settings");
19+
const { openDialog } = useDialog();
20+
const snackbar = useSnackbar();
21+
22+
const { error, data, isPending } = useQuery({
23+
queryKey: [CacheKeysConstants.Settings],
24+
queryFn: () => settingsApi.getCurrentSettings(),
25+
});
26+
27+
const deleteAccountMutation = useMutation({
28+
onError: () => snackbar.success(t("danger-zone.delete-account.success")),
29+
onSuccess: () => snackbar.error(t("danger-zone.delete-account.error")),
30+
mutationFn: () => profileApi.deleteCurrentProfile(),
31+
});
32+
33+
if (error) {
34+
return (
35+
<SettingsPageNav>
36+
<ErrorView />
37+
</SettingsPageNav>
38+
);
39+
}
40+
41+
if (isPending) {
42+
return (
43+
<SettingsPageNav>
44+
<SettingsPageSkeleton />
45+
</SettingsPageNav>
46+
);
47+
}
48+
49+
if (!data) {
50+
return (
51+
<SettingsPageNav>
52+
<ErrorView message="Settings not found" />
53+
</SettingsPageNav>
54+
);
55+
}
56+
57+
function handleClickDelete() {
58+
openDialog(ConfirmationDialog, {
59+
description: t("danger-zone.delete-account.description"),
60+
onConfirm: async (onClose) => {
61+
await deleteAccountMutation.mutateAsync();
62+
onClose();
63+
},
64+
title: t("danger-zone.delete-account.title"),
65+
});
66+
}
67+
68+
return (
69+
<Box
70+
sx={{
71+
display: "flex",
72+
flexDirection: "column",
73+
}}
74+
>
75+
<SettingsPageNav>
76+
<Typography gutterBottom sx={{ paddingTop: 3 }} variant="h5">
77+
{t("danger-zone.header")}
78+
</Typography>
79+
80+
<Typography color="textSecondary" sx={{ pb: 2 }}>
81+
{t("danger-zone.description")}
82+
</Typography>
83+
84+
<div>
85+
<Button
86+
color="error"
87+
loading={deleteAccountMutation.isPending}
88+
onClick={handleClickDelete}
89+
variant="contained"
90+
>
91+
{t("danger-zone.delete-account.button")}
92+
</Button>
93+
</div>
94+
</SettingsPageNav>
95+
</Box>
96+
);
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ReactElement, ReactNode } from "react";
2+
3+
import { NavLayout } from "src/components/layout";
4+
import { useTranslation } from "src/core/i18n";
5+
6+
interface SettingsPageNavProps {
7+
children: ReactNode;
8+
}
9+
10+
export function SettingsPageNav({
11+
children,
12+
}: SettingsPageNavProps): ReactElement {
13+
const { t } = useTranslation("settings");
14+
15+
return (
16+
<NavLayout header={t("header")} linkText="Back" to="/profile">
17+
{children}
18+
</NavLayout>
19+
);
20+
}

0 commit comments

Comments
 (0)