Skip to content

Commit 8dcd2d9

Browse files
committed
really bad settings page!
1 parent f76d803 commit 8dcd2d9

File tree

9 files changed

+136
-183
lines changed

9 files changed

+136
-183
lines changed

site/src/api/api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,14 @@ export const updateProfile = async (
712712
return response.data;
713713
};
714714

715+
export const updateThemePreference = async (
716+
userId: string,
717+
data: TypesGen.UpdateUserThemePreferenceRequest,
718+
): Promise<TypesGen.User> => {
719+
const response = await axios.put(`/api/v2/users/${userId}/theme`, data);
720+
return response.data;
721+
};
722+
715723
export const getUserQuietHoursSchedule = async (
716724
userId: TypesGen.User["id"],
717725
): Promise<TypesGen.UserQuietHoursScheduleResponse> => {

site/src/api/queries/users.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { QueryClient, type UseQueryOptions } from "react-query";
1+
import { QueryClient, type QueryKey, type UseQueryOptions } from "react-query";
22
import * as API from "api/api";
33
import type {
44
AuthorizationRequest,
55
GetUsersResponse,
66
UpdateUserPasswordRequest,
77
UpdateUserProfileRequest,
8+
UpdateUserThemePreferenceRequest,
89
UsersRequest,
910
User,
1011
} from "api/typesGenerated";
@@ -116,11 +117,13 @@ export const authMethods = () => {
116117

117118
const initialUserData = getMetadataAsJSON<User>("user");
118119

120+
const meKey = ["me"];
121+
119122
export const me = (): UseQueryOptions<User> & {
120-
queryKey: NonNullable<UseQueryOptions<User>["queryKey"]>;
123+
queryKey: QueryKey;
121124
} => {
122125
return {
123-
queryKey: ["me"],
126+
queryKey: meKey,
124127
initialData: initialUserData,
125128
queryFn: API.getAuthenticatedUser,
126129
};
@@ -179,14 +182,24 @@ export const logout = (queryClient: QueryClient) => {
179182
};
180183
};
181184

182-
export const updateProfile = () => {
185+
export const updateProfile = (userId: string) => {
183186
return {
184-
mutationFn: ({
185-
userId,
186-
req,
187-
}: {
188-
userId: string;
189-
req: UpdateUserProfileRequest;
190-
}) => API.updateProfile(userId, req),
187+
mutationFn: (req: UpdateUserProfileRequest) =>
188+
API.updateProfile(userId, req),
189+
};
190+
};
191+
192+
export const updateThemePreference = (
193+
userId: string,
194+
queryClient: QueryClient,
195+
) => {
196+
return {
197+
mutationFn: (req: UpdateUserThemePreferenceRequest) =>
198+
API.updateThemePreference(userId, req),
199+
onSuccess: () => {
200+
// Could technically invalidate more, but we only ever care about the
201+
// `theme_preference` for the `me` query.
202+
queryClient.invalidateQueries(meKey);
203+
},
191204
};
192205
};

site/src/components/AuthProvider/AuthProvider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
6161
);
6262
const logoutMutation = useMutation(logout(queryClient));
6363
const updateProfileMutation = useMutation({
64-
...updateProfileOptions(),
64+
...updateProfileOptions("me"),
65+
6566
onSuccess: (user) => {
6667
queryClient.setQueryData(meOptions.queryKey, user);
6768
displaySuccess("Updated settings.");
@@ -92,7 +93,7 @@ export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
9293
};
9394

9495
const updateProfile = (req: UpdateUserProfileRequest) => {
95-
updateProfileMutation.mutate({ userId: userQuery.data!.id, req });
96+
updateProfileMutation.mutate(req);
9697
};
9798

9899
if (isLoading) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { AppearanceForm } from "./AppearanceForm";
3+
4+
const meta: Meta<typeof AppearanceForm> = {
5+
title: "pages/UserSettingsPage/AppearanceForm",
6+
component: AppearanceForm,
7+
args: {
8+
isLoading: false,
9+
},
10+
};
11+
12+
export default meta;
13+
type Story = StoryObj<typeof AppearanceForm>;
14+
15+
export const Example: Story = {};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import TextField from "@mui/material/TextField";
2+
import LoadingButton from "@mui/lab/LoadingButton";
3+
import { type FormikTouched, useFormik } from "formik";
4+
import { type FC } from "react";
5+
import * as Yup from "yup";
6+
import type { UpdateUserThemePreferenceRequest } from "api/typesGenerated";
7+
import { getFormHelpers, onChangeTrimmed } from "utils/formUtils";
8+
import { ErrorAlert } from "components/Alert/ErrorAlert";
9+
import { Form, FormFields } from "components/Form/Form";
10+
11+
const validationSchema = Yup.object({
12+
theme_preference: Yup.string().required(),
13+
});
14+
15+
export interface AppearanceFormProps {
16+
isLoading: boolean;
17+
error?: unknown;
18+
initialValues: UpdateUserThemePreferenceRequest;
19+
onSubmit: (values: UpdateUserThemePreferenceRequest) => void;
20+
// initialTouched is only used for testing the error state of the form.
21+
initialTouched?: FormikTouched<UpdateUserThemePreferenceRequest>;
22+
}
23+
24+
export const AppearanceForm: FC<AppearanceFormProps> = ({
25+
isLoading,
26+
error,
27+
onSubmit,
28+
initialValues,
29+
initialTouched,
30+
}) => {
31+
const form = useFormik({
32+
initialValues,
33+
validationSchema,
34+
onSubmit,
35+
initialTouched,
36+
});
37+
const getFieldHelpers = getFormHelpers(form, error);
38+
39+
return (
40+
<>
41+
<Form onSubmit={form.handleSubmit}>
42+
<FormFields>
43+
{Boolean(error) && <ErrorAlert error={error} />}
44+
<TextField
45+
{...getFieldHelpers("theme_preference")}
46+
onChange={onChangeTrimmed(form)}
47+
fullWidth
48+
label="Theme name"
49+
/>
50+
51+
<div>
52+
<LoadingButton
53+
loading={isLoading}
54+
type="submit"
55+
variant="contained"
56+
>
57+
Update theme
58+
</LoadingButton>
59+
</div>
60+
</FormFields>
61+
</Form>
62+
</>
63+
);
64+
};

site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { fireEvent, screen, within } from "@testing-library/react";
22
import * as API from "api/api";
33
import { renderWithAuth } from "testHelpers/renderHelpers";
4-
import {
5-
Language as SSHKeysPageLanguage,
6-
AppearancePage,
7-
} from "./AppearancePage";
4+
import { AppearancePage } from "./AppearancePage";
85
import { MockGitSSHKey } from "testHelpers/entities";
96

107
describe("SSH keys Page", () => {
@@ -25,9 +22,7 @@ describe("SSH keys Page", () => {
2522
const regenerateButton = screen.getByTestId("regenerate");
2623
fireEvent.click(regenerateButton);
2724
const confirmDialog = screen.getByRole("dialog");
28-
expect(confirmDialog).toHaveTextContent(
29-
SSHKeysPageLanguage.regenerateDialogMessage,
30-
);
25+
expect(confirmDialog).toHaveTextContent("foo");
3126

3227
const newUserSSHKey =
3328
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDSC/ouD/LqiT1Rd99vDv/MwUmqzJuinLTMTpk5kVy66";
@@ -38,7 +33,7 @@ describe("SSH keys Page", () => {
3833

3934
// Click on the "Confirm" button
4035
const confirmButton = within(confirmDialog).getByRole("button", {
41-
name: SSHKeysPageLanguage.confirmLabel,
36+
name: "foo",
4237
});
4338
fireEvent.click(confirmButton);
4439

site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.tsx

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,32 @@
1-
import { type FC, useState } from "react";
2-
import { useMutation, useQuery, useQueryClient } from "react-query";
3-
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
4-
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
5-
import { regenerateUserSSHKey, userSSHKey } from "api/queries/sshKeys";
6-
import { getErrorMessage } from "api/errors";
1+
import { type FC } from "react";
2+
import { useMutation, useQueryClient } from "react-query";
3+
import { updateThemePreference } from "api/queries/users";
74
import { Section } from "../Section";
8-
import { AppearancePageView } from "./AppearancePageView";
9-
10-
export const Language = {
11-
title: "SSH keys",
12-
regenerateDialogTitle: "Regenerate SSH key?",
13-
regenerationError: "Failed to regenerate SSH key",
14-
regenerationSuccess: "SSH Key regenerated successfully.",
15-
regenerateDialogMessage:
16-
"You will need to replace the public SSH key on services you use it with, and you'll need to rebuild existing workspaces.",
17-
confirmLabel: "Confirm",
18-
cancelLabel: "Cancel",
19-
};
5+
import { AppearanceForm } from "./AppearanceForm";
6+
import { useMe } from "hooks";
207

218
export const AppearancePage: FC = () => {
22-
const [isConfirmingRegeneration, setIsConfirmingRegeneration] =
23-
useState(false);
24-
25-
const userSSHKeyQuery = useQuery(userSSHKey("me"));
9+
const me = useMe();
2610
const queryClient = useQueryClient();
27-
const regenerateSSHKeyMutation = useMutation(
28-
regenerateUserSSHKey("me", queryClient),
11+
const updateThemePreferenceMutation = useMutation(
12+
updateThemePreference("me", queryClient),
2913
);
3014

3115
return (
3216
<>
33-
<Section title={Language.title}>
34-
<AppearancePageView
35-
isLoading={userSSHKeyQuery.isLoading}
36-
getSSHKeyError={userSSHKeyQuery.error}
37-
sshKey={userSSHKeyQuery.data}
38-
onRegenerateClick={() => setIsConfirmingRegeneration(true)}
17+
<Section title="Theme">
18+
<AppearanceForm
19+
isLoading={updateThemePreferenceMutation.isLoading}
20+
error={updateThemePreferenceMutation.error}
21+
initialValues={{ theme_preference: me.theme_preference }}
22+
onSubmit={async (arg: any) => {
23+
console.log("going");
24+
const x = await updateThemePreferenceMutation.mutateAsync(arg);
25+
console.log(x);
26+
return x;
27+
}}
3928
/>
4029
</Section>
41-
42-
<ConfirmDialog
43-
type="delete"
44-
hideCancel={false}
45-
open={isConfirmingRegeneration}
46-
confirmLoading={regenerateSSHKeyMutation.isLoading}
47-
title={Language.regenerateDialogTitle}
48-
description={Language.regenerateDialogMessage}
49-
confirmText={Language.confirmLabel}
50-
onClose={() => setIsConfirmingRegeneration(false)}
51-
onConfirm={async () => {
52-
try {
53-
await regenerateSSHKeyMutation.mutateAsync();
54-
displaySuccess(Language.regenerationSuccess);
55-
} catch (error) {
56-
displayError(getErrorMessage(error, Language.regenerationError));
57-
} finally {
58-
setIsConfirmingRegeneration(false);
59-
}
60-
}}
61-
/>
6230
</>
6331
);
6432
};

site/src/pages/UserSettingsPage/AppearancePage/AppearancePageView.stories.tsx

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)