Skip to content

Commit

Permalink
feat(web/user): User settigs setup
Browse files Browse the repository at this point in the history
  • Loading branch information
RezaRahemtola committed Oct 24, 2023
1 parent 8037d36 commit e4dbe17
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 11 deletions.
26 changes: 25 additions & 1 deletion frontend/web/app/dashboard/user/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
"use client";

import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";

import DashboardPageWrapper from "@/layouts/dashboard/DashboardPageWrapper";
import services from "@/services";
import { User } from "@/types/user";
import UserSettings from "@/layouts/user/UserSettings";

const UserPage = () => {
const [userData, setUserData] = useState<User | null>(null);
const { t } = useTranslation();

useEffect(() => {
(async () => {
const userProfile = await services.user.getProfile();
setUserData(userProfile.data);
})();
}, []);

const UserPage = () => <DashboardPageWrapper title="User settings" />;
return (
<DashboardPageWrapper title={t("user.settings.title")}>
{userData && <UserSettings user={userData} />}
</DashboardPageWrapper>
);
};

export default UserPage;
23 changes: 16 additions & 7 deletions frontend/web/components/ThemeSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
"use client";

import { ChangeEvent, useEffect } from "react";

import { ChangeEvent, useEffect, useState } from "react";
import { useAtom } from "jotai";
import { interfaceThemeAtom } from "@/stores/user";

import { interfaceThemeAtom, userAuthTokenAtom } from "@/stores/user";
import useSystemTheme from "@/hooks/useSystemTheme";
import services from "@/services";

const ThemeSelector = () => {
const [userAuthToken] = useAtom(userAuthTokenAtom);
const [interfaceTheme, setInterfaceTheme] = useAtom(interfaceThemeAtom);
const systemTheme = useSystemTheme();
const [selectedTheme, setSelectedTheme] = useState(interfaceTheme === "auto" ? systemTheme : interfaceTheme);

useEffect(() => {
document.querySelector("html")?.setAttribute("data-theme", interfaceTheme);
}, [interfaceTheme]);
document.querySelector("html")?.setAttribute("data-theme", selectedTheme);
}, [selectedTheme]);

const handleThemeChange = (e: ChangeEvent<HTMLInputElement>) => {
const handleThemeChange = async (e: ChangeEvent<HTMLInputElement>) => {
const newTheme = e.target.checked ? "dark" : "light";
setInterfaceTheme(newTheme);
setSelectedTheme(newTheme);
if (userAuthToken) {
await services.user.updateProfile({ theme: newTheme });
}
};

return (
<label className="swap swap-rotate mr-2">
<input type="checkbox" name="theme-selector" onChange={handleThemeChange} checked={interfaceTheme !== "light"} />
<input type="checkbox" name="theme-selector" onChange={handleThemeChange} checked={selectedTheme !== "light"} />

{/* sun icon */}
<svg className="swap-on fill-current w-10 h-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
Expand Down
19 changes: 19 additions & 0 deletions frontend/web/hooks/useSystemTheme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useState } from "react";

const useSystemTheme = (): "dark" | "light" => {
if (typeof window === "undefined") return "dark";
const getCurrentTheme = () => window.matchMedia("(prefers-color-scheme: dark)").matches;
const [isDarkTheme, setIsDarkTheme] = useState(getCurrentTheme());
const mqListener = (e: MediaQueryListEvent) => {
setIsDarkTheme(e.matches);
};

useEffect(() => {
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
darkThemeMq.addEventListener("change", mqListener);
return () => darkThemeMq.removeEventListener("change", mqListener);
}, []);
return isDarkTheme ? "dark" : "light";
};

export default useSystemTheme;
22 changes: 22 additions & 0 deletions frontend/web/layouts/user/UserSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useTranslation } from "react-i18next";
import { User } from "@/types/user";

type UserSettingsProps = {
user: User;
};
const UserSettings = ({ user }: UserSettingsProps) => {
const { t } = useTranslation();

return (
<div className="w-fit ml-4">
<div>
<label className="label">
<span className="text-primary label-text">{t("auth.fields.email")}</span>
</label>
<input type="text" value={user.email} className="input input-bordered input-primary bg-neutral" />
</div>
</div>
);
};

export default UserSettings;
5 changes: 5 additions & 0 deletions frontend/web/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ const translations = {
},
},
},
user: {
settings: {
title: "User settings",
},
},
landing: {
actions: {
downloadApk: " Download APK",
Expand Down
5 changes: 5 additions & 0 deletions frontend/web/locales/fr-FR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ const translations = {
},
},
},
user: {
settings: {
title: "Paramètres",
},
},
landing: {
actions: {
downloadApk: " Télécharger l'APK",
Expand Down
2 changes: 2 additions & 0 deletions frontend/web/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import authService from "@/services/auth";
import servicesService from "@/services/services";
import workflowsService from "@/services/workflows";
import connectionsService from "@/services/connections";
import userService from "@/services/user";

const axiosInstance = axios.create({
baseURL: API_URL,
Expand Down Expand Up @@ -38,6 +39,7 @@ const services = {
services: servicesService,
workflows: workflowsService,
connections: connectionsService,
user: userService,
};

export default services;
Expand Down
8 changes: 8 additions & 0 deletions frontend/web/services/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getProfile, updateProfile } from "@/services/user/me";

const userService = {
getProfile,
updateProfile,
};

export default userService;
24 changes: 24 additions & 0 deletions frontend/web/services/user/me.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { axiosInstance } from "@/services";
import { SERVICE_ERROR_UNKNOWN } from "@/config/services";
import { ServiceReturn } from "@/types/api";
import { User, UserProfileUpdate } from "@/types/user";

const getProfile = async (): Promise<ServiceReturn<User>> => {
try {
const response = await axiosInstance.get<User>(`/me`);
return { data: response.data, error: undefined };
} catch (error) {
return { data: null, error: SERVICE_ERROR_UNKNOWN };
}
};

const updateProfile = async (data: UserProfileUpdate): Promise<ServiceReturn<void>> => {
try {
const response = await axiosInstance.patch(`/me`, data);
return { data: response.data, error: undefined };
} catch (error) {
return { data: null, error: SERVICE_ERROR_UNKNOWN };
}
};

export { getProfile, updateProfile };
2 changes: 1 addition & 1 deletion frontend/web/stores/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { atomWithStorage } from "jotai/utils";

import { InterfaceTheme } from "@/types/user";

export const interfaceThemeAtom = atomWithStorage<InterfaceTheme>("interfaceTheme", "dark");
export const interfaceThemeAtom = atomWithStorage<InterfaceTheme>("interfaceTheme", "auto");
export const userAuthTokenAtom = atomWithStorage<string | null>("areaAuthToken", null);
23 changes: 21 additions & 2 deletions frontend/web/types/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export type InterfaceTheme = "dark" | "light";

export type Credentials = {
email: string;
password: string;
Expand All @@ -8,3 +6,24 @@ export type Credentials = {
export type AuthResult = {
accessToken: string;
};

const INTERFACE_THEMES = ["auto", "light", "dark"] as const;
export type InterfaceTheme = (typeof INTERFACE_THEMES)[number];
export type InterfaceLanguage = "en" | "fr";

export type UserProfileUpdate = {
email?: string;
language?: InterfaceLanguage;
theme?: InterfaceTheme;
};

export type User = {
id: string;
isAdmin: boolean;
email: string;
createdAt: string;
settings: {
language: InterfaceLanguage;
theme: InterfaceTheme;
};
};

0 comments on commit e4dbe17

Please sign in to comment.