Merge branch 'feature/user-profile' of github.com:hydralauncher/hydra into feature/user-profile

This commit is contained in:
Chubby Granny Chaser 2024-06-19 20:58:21 +01:00
commit 93c6ef8510
No known key found for this signature in database
30 changed files with 167 additions and 121 deletions

View file

@ -86,9 +86,12 @@ export function App() {
dispatch(setProfileBackground(profileBackground));
}
/* TODO: Check if user is logged in before calling this */
fetchUserDetails().then((response) => {
if (response) setUserDetails(response);
window.electron.isUserLoggedIn().then((isLoggedIn) => {
if (isLoggedIn) {
fetchUserDetails().then((response) => {
if (response) setUserDetails(response);
});
}
});
}, [dispatch, fetchUserDetails]);

View file

@ -96,6 +96,7 @@ declare global {
/* Misc */
openExternal: (src: string) => Promise<void>;
isUserLoggedIn: () => Promise<boolean>;
getVersion: () => Promise<string>;
ping: () => string;
getDefaultDownloadsPath: () => Promise<string>;

View file

@ -12,6 +12,7 @@ import { buildGameDetailsPath } from "@renderer/helpers";
import { PersonIcon, TelescopeIcon } from "@primer/octicons-react";
import { Button } from "@renderer/components";
import { UserEditProfileModal } from "./user-edit-modal";
import { UserSignOutModal } from "./user-signout-modal";
const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
@ -29,6 +30,7 @@ export function UserContent({
const { userDetails, profileBackground, signOut } = useUserDetails();
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
const [showSignOutModal, setShowSignOutModal] = useState(false);
const navigate = useNavigate();
@ -65,7 +67,7 @@ export function UserContent({
setShowEditProfileModal(true);
};
const handleSignout = async () => {
const handleConfirmSignout = async () => {
signOut();
navigate("/");
};
@ -87,6 +89,12 @@ export function UserContent({
userProfile={userProfile}
/>
<UserSignOutModal
visible={showSignOutModal}
onClose={() => setShowSignOutModal(false)}
onConfirm={handleConfirmSignout}
/>
<section
className={styles.profileContentBox}
style={{
@ -124,7 +132,10 @@ export function UserContent({
Editar perfil
</Button>
<Button theme="danger" onClick={handleSignout}>
<Button
theme="danger"
onClick={() => setShowSignOutModal(true)}
>
{t("sign_out")}
</Button>
</>

View file

@ -5,6 +5,7 @@ import { DeviceCameraIcon, PersonIcon } from "@primer/octicons-react";
import { SPACING_UNIT } from "@renderer/theme.css";
import { useEffect, useMemo, useState } from "react";
import { useToast, useUserDetails } from "@renderer/hooks";
import { useTranslation } from "react-i18next";
export interface UserEditProfileModalProps {
userProfile: UserProfile;
@ -19,6 +20,8 @@ export const UserEditProfileModal = ({
onClose,
updateUserProfile,
}: UserEditProfileModalProps) => {
const { t } = useTranslation("user_profile");
const [displayName, setDisplayName] = useState("");
const [newImagePath, setNewImagePath] = useState<string | null>(null);
const [isSaving, setIsSaving] = useState(false);
@ -36,8 +39,8 @@ export const UserEditProfileModal = ({
properties: ["openFile"],
filters: [
{
name: "Profile image",
extensions: ["jpg", "png", "gif", "webp", "jpeg"],
name: "Image",
extensions: ["jpg", "jpeg", "png", "gif", "webp", "bmp"],
},
],
});
@ -58,11 +61,11 @@ export const UserEditProfileModal = ({
patchUser(displayName, newImagePath)
.then(async () => {
await updateUserProfile();
showSuccessToast("Salvo com sucesso");
showSuccessToast(t("saved_successfully"));
cleanFormAndClose();
})
.catch(() => {
showErrorToast("Tente novamente");
showErrorToast(t("try_again"));
})
.finally(() => {
setIsSaving(false);
@ -89,7 +92,7 @@ export const UserEditProfileModal = ({
<>
<Modal
visible={visible}
title="Editar Perfil"
title={t("edit_profile")}
onClose={cleanFormAndClose}
>
<form
@ -123,8 +126,10 @@ export const UserEditProfileModal = ({
</button>
<TextField
label="Nome de exibição"
label={t("display_name")}
value={displayName}
required
minLength={3}
containerProps={{ style: { width: "100%" } }}
onChange={(e) => setDisplayName(e.target.value)}
/>
@ -133,7 +138,7 @@ export const UserEditProfileModal = ({
style={{ alignSelf: "end" }}
type="submit"
>
{isSaving ? "Salvando…" : "Salvar"}
{isSaving ? t("saving") : t("save")}
</Button>
</form>
</Modal>

View file

@ -0,0 +1,37 @@
import { Button, Modal } from "@renderer/components";
import * as styles from "./user.css";
import { useTranslation } from "react-i18next";
export interface UserEditProfileModalProps {
visible: boolean;
onConfirm: () => void;
onClose: () => void;
}
export const UserSignOutModal = ({
visible,
onConfirm,
onClose,
}: UserEditProfileModalProps) => {
const { t } = useTranslation("user_profile");
return (
<>
<Modal
visible={visible}
title={t("signout_modal_title")}
onClose={onClose}
>
<div className={styles.signOutModalButtonsContainer}>
<Button onClick={onConfirm} theme="outline">
{t("signout")}
</Button>
<Button onClick={onClose} theme="primary">
{t("cancel")}
</Button>
</div>
</Modal>
</>
);
};

View file

@ -1,13 +1,40 @@
import Skeleton from "react-loading-skeleton";
import cn from "classnames";
import * as styles from "./user.css";
import { SPACING_UNIT } from "@renderer/theme.css";
import { useTranslation } from "react-i18next";
export const UserSkeleton = () => {
const { t } = useTranslation("user_profile");
return (
<>
<Skeleton className={styles.profileHeaderSkeleton} />
<div className={styles.profileContent}>
<Skeleton height={140} style={{ flex: 1 }} />
<Skeleton width={300} className={styles.contentSidebar} />
<div className={styles.profileGameSection}>
<h2>{t("activity")}</h2>
{Array.from({ length: 3 }).map((_, index) => (
<Skeleton
key={index}
height={72}
style={{ flex: "1", width: "100%" }}
/>
))}
</div>
<div className={cn(styles.contentSidebar, styles.profileGameSection)}>
<h2>{t("library")}</h2>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(4, 1fr)",
gap: `${SPACING_UNIT}px`,
}}
>
{Array.from({ length: 8 }).map((_, index) => (
<Skeleton key={index} style={{ aspectRatio: "1" }} />
))}
</div>
</div>
</div>
</>
);

View file

@ -132,9 +132,6 @@ export const feedItem = style({
export const gameListItem = style({
color: vars.color.body,
display: "flex",
flexDirection: "row",
gap: `${SPACING_UNIT}px`,
transition: "all ease 0.2s",
cursor: "pointer",
zIndex: "1",
@ -153,7 +150,7 @@ export const gameInformation = style({
});
export const profileHeaderSkeleton = style({
height: "200px",
height: "144px",
});
export const editProfileImageBadge = style({
@ -191,3 +188,11 @@ export const noDownloads = style({
flexDirection: "column",
gap: `${SPACING_UNIT}px`,
});
export const signOutModalButtonsContainer = style({
display: "flex",
width: "100%",
justifyContent: "end",
alignItems: "center",
gap: `${SPACING_UNIT}px`,
});