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,
toggleDraggingDisabled,
closeToast,
setUserDetails,
setProfileBackground,
} from "@renderer/features";
export interface AppProps {
@ -31,7 +33,8 @@ export function App() {
const { clearDownload, setLastPacket } = useDownload();
const { updateUser, clearUser } = useUserDetails();
const { fetchUserDetails, updateUserDetails, clearUserDetails } =
useUserDetails();
const dispatch = useAppDispatch();
@ -73,23 +76,38 @@ export function App() {
}, [clearDownload, setLastPacket, updateLibrary]);
useEffect(() => {
updateUser();
}, [updateUser]);
const cachedUserDetails = window.localStorage.getItem("userDetails");
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(() => {
const listeners = [
window.electron.onSignIn(() => {
updateUser();
fetchUserDetails().then((response) => {
if (response) updateUserDetails(response);
});
}),
window.electron.onSignOut(() => {
clearUser();
clearUserDetails();
}),
];
return () => {
listeners.forEach((unsubscribe) => unsubscribe());
};
}, [updateUser, clearUser]);
}, [fetchUserDetails, updateUserDetails, clearUserDetails]);
const handleSearch = useCallback(
(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 { PersonIcon } from "@primer/octicons-react";
import * as styles from "./sidebar.css";
import * as styles from "./sidebar-profile.css";
import { useUserDetails } from "@renderer/hooks";
import { useMemo } from "react";
@ -9,12 +10,13 @@ export function SidebarProfile() {
const { userDetails, profileBackground } = useUserDetails();
const handleClickProfile = () => {
navigate(`/user/${userDetails!.id}`);
};
const handleButtonClick = () => {
if (userDetails === null) {
window.electron.openExternal("https://auth.hydra.losbroxas.org");
return;
}
const handleClickLogin = () => {
window.electron.openExternal("https://auth.hydra.losbroxas.org");
navigate(`/user/${userDetails!.id}`);
};
const profileButtonBackground = useMemo(() => {
@ -22,36 +24,16 @@ export function SidebarProfile() {
return undefined;
}, [profileBackground]);
if (userDetails == null) {
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
type="button"
className={styles.profileButton}
style={{ background: profileButtonBackground }}
onClick={handleClickProfile}
>
<button
type="button"
className={styles.profileButton}
style={{ background: profileButtonBackground }}
onClick={handleButtonClick}
>
<div className={styles.profileButtonContent}>
<div className={styles.profileAvatar}>
{userDetails.profileImageUrl ? (
{userDetails?.profileImageUrl ? (
<img
className={styles.profileAvatar}
src={userDetails.profileImageUrl}
@ -63,9 +45,11 @@ export function SidebarProfile() {
</div>
<div className={styles.profileButtonInformation}>
<p style={{ fontWeight: "bold" }}>{userDetails.displayName}</p>
<p className={styles.profileButtonTitle}>
{userDetails ? userDetails.displayName : "Sign in"}
</p>
</div>
</button>
</>
</div>
</button>
);
}

View File

@ -125,48 +125,3 @@ export const section = style({
flexDirection: "column",
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: (
displayName: string,
newProfileImagePath: string | null
) => Promise<UserProfile | null>;
) => Promise<UserProfile>;
}
interface Window {

View File

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

View File

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

View File

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