mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Merge branch 'feature/user-profile' of github.com:hydralauncher/hydra into feature/user-profile
This commit is contained in:
commit
93c6ef8510
30 changed files with 167 additions and 121 deletions
|
@ -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]);
|
||||
|
||||
|
|
1
src/renderer/src/declaration.d.ts
vendored
1
src/renderer/src/declaration.d.ts
vendored
|
@ -96,6 +96,7 @@ declare global {
|
|||
|
||||
/* Misc */
|
||||
openExternal: (src: string) => Promise<void>;
|
||||
isUserLoggedIn: () => Promise<boolean>;
|
||||
getVersion: () => Promise<string>;
|
||||
ping: () => string;
|
||||
getDefaultDownloadsPath: () => Promise<string>;
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
@ -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>
|
||||
|
|
37
src/renderer/src/pages/user/user-signout-modal.tsx
Normal file
37
src/renderer/src/pages/user/user-signout-modal.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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`,
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue