feat: adding user local storage cache

This commit is contained in:
Chubby Granny Chaser 2024-06-18 20:29:37 +01:00
parent eea19d43c2
commit a8624058e4
No known key found for this signature in database
8 changed files with 152 additions and 149 deletions

View File

@ -19,6 +19,8 @@ import {
setUserPreferences, setUserPreferences,
toggleDraggingDisabled, toggleDraggingDisabled,
closeToast, closeToast,
setUserDetails,
setProfileBackground,
} from "@renderer/features"; } from "@renderer/features";
export interface AppProps { export interface AppProps {
@ -31,7 +33,8 @@ export function App() {
const { clearDownload, setLastPacket } = useDownload(); const { clearDownload, setLastPacket } = useDownload();
const { updateUser, clearUser } = useUserDetails(); const { fetchUserDetails, updateUserDetails, clearUserDetails } =
useUserDetails();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -73,23 +76,38 @@ export function App() {
}, [clearDownload, setLastPacket, updateLibrary]); }, [clearDownload, setLastPacket, updateLibrary]);
useEffect(() => { useEffect(() => {
updateUser(); const cachedUserDetails = window.localStorage.getItem("userDetails");
}, [updateUser]);
if (cachedUserDetails) {
const { profileBackground, ...userDetails } =
JSON.parse(cachedUserDetails);
dispatch(setUserDetails(userDetails));
dispatch(setProfileBackground(profileBackground));
}
/* TODO: Check if user is logged in before calling this */
fetchUserDetails().then((response) => {
if (response) setUserDetails(response);
});
}, [dispatch, fetchUserDetails]);
useEffect(() => { useEffect(() => {
const listeners = [ const listeners = [
window.electron.onSignIn(() => { window.electron.onSignIn(() => {
updateUser(); fetchUserDetails().then((response) => {
if (response) updateUserDetails(response);
});
}), }),
window.electron.onSignOut(() => { window.electron.onSignOut(() => {
clearUser(); clearUserDetails();
}), }),
]; ];
return () => { return () => {
listeners.forEach((unsubscribe) => unsubscribe()); listeners.forEach((unsubscribe) => unsubscribe());
}; };
}, [updateUser, clearUser]); }, [fetchUserDetails, updateUserDetails, clearUserDetails]);
const handleSearch = useCallback( const handleSearch = useCallback(
(query: string) => { (query: string) => {

View File

@ -0,0 +1,58 @@
import { style } from "@vanilla-extract/css";
import { SPACING_UNIT, vars } from "../../theme.css";
export const profileButton = style({
display: "flex",
cursor: "pointer",
transition: "all ease 0.1s",
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
color: vars.color.muted,
borderBottom: `solid 1px ${vars.color.border}`,
boxShadow: "0px 0px 15px 0px #000000",
":hover": {
backgroundColor: "rgba(255, 255, 255, 0.15)",
},
});
export const profileButtonContent = style({
display: "flex",
alignItems: "center",
gap: `${SPACING_UNIT + SPACING_UNIT / 2}px`,
height: "40px",
});
export const profileAvatar = style({
width: "35px",
height: "35px",
borderRadius: "50%",
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: vars.color.background,
border: `solid 1px ${vars.color.border}`,
position: "relative",
objectFit: "cover",
});
export const profileButtonInformation = style({
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
});
export const statusBadge = style({
width: "9px",
height: "9px",
borderRadius: "50%",
backgroundColor: vars.color.danger,
position: "absolute",
bottom: "-2px",
right: "-3px",
zIndex: "1",
});
export const profileButtonTitle = style({
fontWeight: "bold",
fontSize: vars.size.body,
});

View File

@ -1,6 +1,7 @@
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { PersonIcon } from "@primer/octicons-react"; import { PersonIcon } from "@primer/octicons-react";
import * as styles from "./sidebar.css"; import * as styles from "./sidebar-profile.css";
import { useUserDetails } from "@renderer/hooks"; import { useUserDetails } from "@renderer/hooks";
import { useMemo } from "react"; import { useMemo } from "react";
@ -9,12 +10,13 @@ export function SidebarProfile() {
const { userDetails, profileBackground } = useUserDetails(); const { userDetails, profileBackground } = useUserDetails();
const handleClickProfile = () => { const handleButtonClick = () => {
navigate(`/user/${userDetails!.id}`); if (userDetails === null) {
};
const handleClickLogin = () => {
window.electron.openExternal("https://auth.hydra.losbroxas.org"); window.electron.openExternal("https://auth.hydra.losbroxas.org");
return;
}
navigate(`/user/${userDetails!.id}`);
}; };
const profileButtonBackground = useMemo(() => { const profileButtonBackground = useMemo(() => {
@ -22,36 +24,16 @@ export function SidebarProfile() {
return undefined; return undefined;
}, [profileBackground]); }, [profileBackground]);
if (userDetails == null) {
return ( return (
<>
<button
type="button"
className={styles.profileButton}
onClick={handleClickLogin}
>
<div className={styles.profileAvatar}>
<PersonIcon />
</div>
<div className={styles.profileButtonInformation}>
<p style={{ fontWeight: "bold" }}>Fazer login</p>
</div>
</button>
</>
);
}
return (
<>
<button <button
type="button" type="button"
className={styles.profileButton} className={styles.profileButton}
style={{ background: profileButtonBackground }} style={{ background: profileButtonBackground }}
onClick={handleClickProfile} onClick={handleButtonClick}
> >
<div className={styles.profileButtonContent}>
<div className={styles.profileAvatar}> <div className={styles.profileAvatar}>
{userDetails.profileImageUrl ? ( {userDetails?.profileImageUrl ? (
<img <img
className={styles.profileAvatar} className={styles.profileAvatar}
src={userDetails.profileImageUrl} src={userDetails.profileImageUrl}
@ -63,9 +45,11 @@ export function SidebarProfile() {
</div> </div>
<div className={styles.profileButtonInformation}> <div className={styles.profileButtonInformation}>
<p style={{ fontWeight: "bold" }}>{userDetails.displayName}</p> <p className={styles.profileButtonTitle}>
{userDetails ? userDetails.displayName : "Sign in"}
</p>
</div>
</div> </div>
</button> </button>
</>
); );
} }

View File

@ -125,48 +125,3 @@ export const section = style({
flexDirection: "column", flexDirection: "column",
paddingBottom: `${SPACING_UNIT}px`, paddingBottom: `${SPACING_UNIT}px`,
}); });
export const profileButton = style({
display: "flex",
cursor: "pointer",
transition: "all ease 0.1s",
gap: `${SPACING_UNIT + SPACING_UNIT / 2}px`,
alignItems: "center",
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
color: vars.color.muted,
borderBottom: `solid 1px ${vars.color.border}`,
boxShadow: "0px 0px 15px 0px #000000",
":hover": {
backgroundColor: "rgba(255, 255, 255, 0.15)",
},
});
export const profileAvatar = style({
width: "30px",
height: "30px",
borderRadius: "50%",
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: vars.color.background,
border: `solid 1px ${vars.color.border}`,
position: "relative",
objectFit: "cover",
});
export const profileButtonInformation = style({
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
});
export const statusBadge = style({
width: "9px",
height: "9px",
borderRadius: "50%",
backgroundColor: vars.color.danger,
position: "absolute",
bottom: "-2px",
right: "-3px",
zIndex: "1",
});

View File

@ -125,7 +125,7 @@ declare global {
updateProfile: ( updateProfile: (
displayName: string, displayName: string,
newProfileImagePath: string | null newProfileImagePath: string | null
) => Promise<UserProfile | null>; ) => Promise<UserProfile>;
} }
interface Window { interface Window {

View File

@ -15,18 +15,14 @@ export const userDetailsSlice = createSlice({
name: "user-details", name: "user-details",
initialState, initialState,
reducers: { reducers: {
setUserDetails: (state, action: PayloadAction<UserDetails>) => { setUserDetails: (state, action: PayloadAction<UserDetails | null>) => {
state.userDetails = action.payload; state.userDetails = action.payload;
}, },
setProfileBackground: (state, action: PayloadAction<string>) => { setProfileBackground: (state, action: PayloadAction<string | null>) => {
state.profileBackground = action.payload; state.profileBackground = action.payload;
}, },
clearUserDetails: (state) => {
state.userDetails = null;
state.profileBackground = null;
},
}, },
}); });
export const { setUserDetails, setProfileBackground, clearUserDetails } = export const { setUserDetails, setProfileBackground } =
userDetailsSlice.actions; userDetailsSlice.actions;

View File

@ -2,12 +2,9 @@ import { useCallback } from "react";
import { average } from "color.js"; import { average } from "color.js";
import { useAppDispatch, useAppSelector } from "./redux"; import { useAppDispatch, useAppSelector } from "./redux";
import { import { setProfileBackground, setUserDetails } from "@renderer/features";
clearUserDetails,
setProfileBackground,
setUserDetails,
} from "@renderer/features";
import { darkenColor } from "@renderer/helpers"; import { darkenColor } from "@renderer/helpers";
import { UserDetails } from "@types";
export function useUserDetails() { export function useUserDetails() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -16,19 +13,21 @@ export function useUserDetails() {
(state) => state.userDetails (state) => state.userDetails
); );
const clearUser = useCallback(async () => { const clearUserDetails = useCallback(async () => {
dispatch(clearUserDetails()); dispatch(setUserDetails(null));
dispatch(setProfileBackground(null));
window.localStorage.removeItem("userDetails");
}, [dispatch]); }, [dispatch]);
const signOut = useCallback(async () => { const signOut = useCallback(async () => {
clearUser(); clearUserDetails();
return window.electron.signOut(); return window.electron.signOut();
}, [clearUser]); }, [clearUserDetails]);
const updateUser = useCallback(async () => { const updateUserDetails = useCallback(
return window.electron.getMe().then(async (userDetails) => { async (userDetails: UserDetails) => {
if (userDetails) {
dispatch(setUserDetails(userDetails)); dispatch(setUserDetails(userDetails));
if (userDetails.profileImageUrl) { if (userDetails.profileImageUrl) {
@ -37,47 +36,41 @@ export function useUserDetails() {
format: "hex", format: "hex",
}); });
dispatch( const profileBackground = `linear-gradient(135deg, ${darkenColor(output as string, 0.6)}, ${darkenColor(output as string, 0.8)})`;
setProfileBackground(
`linear-gradient(135deg, ${darkenColor(output as string, 0.6)}, ${darkenColor(output as string, 0.7)})` dispatch(setProfileBackground(profileBackground));
)
window.localStorage.setItem(
"userDetails",
JSON.stringify({ ...userDetails, profileBackground })
); );
} }
}
});
}, [dispatch]);
const patchUser = useCallback(
async (displayName: string, imageProfileUrl: string | null) => {
return window.electron
.updateProfile(displayName, imageProfileUrl)
.then(async (userDetails) => {
if (userDetails) {
dispatch(setUserDetails(userDetails));
if (userDetails.profileImageUrl) {
const output = await average(userDetails.profileImageUrl, {
amount: 1,
format: "hex",
});
dispatch(
setProfileBackground(
`linear-gradient(135deg, ${darkenColor(output as string, 0.6)}, ${darkenColor(output as string, 0.7)})`
)
);
}
}
});
}, },
[dispatch] [dispatch]
); );
const fetchUserDetails = useCallback(async () => {
return window.electron.getMe();
}, []);
const patchUser = useCallback(
async (displayName: string, imageProfileUrl: string | null) => {
const response = await window.electron.updateProfile(
displayName,
imageProfileUrl
);
return updateUserDetails(response);
},
[updateUserDetails]
);
return { return {
userDetails, userDetails,
updateUser, fetchUserDetails,
signOut, signOut,
clearUser, clearUserDetails,
updateUserDetails,
patchUser, patchUser,
profileBackground, profileBackground,
}; };

View File

@ -71,7 +71,6 @@ export const UserEditProfileModal = ({
<Modal visible={visible} title="Editar Perfil" onClose={onClose}> <Modal visible={visible} title="Editar Perfil" onClose={onClose}>
<section <section
style={{ style={{
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
justifyContent: "center", justifyContent: "center",