feat: adding real debrid user auth

This commit is contained in:
Chubby Granny Chaser 2024-05-28 14:01:28 +01:00
parent 86816dc3c3
commit 183b85d66a
No known key found for this signature in database
24 changed files with 234 additions and 137 deletions

View file

@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef } from "react";
import { Sidebar, BottomPanel, Header } from "@renderer/components";
import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components";
import {
useAppDispatch,
@ -18,6 +18,7 @@ import {
clearSearch,
setUserPreferences,
toggleDraggingDisabled,
closeToast,
} from "@renderer/features";
document.body.classList.add(themeClass);
@ -41,6 +42,7 @@ export function App() {
const draggingDisabled = useAppSelector(
(state) => state.window.draggingDisabled
);
const toast = useAppSelector((state) => state.toast);
useEffect(() => {
Promise.all([window.electron.getUserPreferences(), updateLibrary()]).then(
@ -108,6 +110,10 @@ export function App() {
});
}, [dispatch, draggingDisabled]);
const handleToastClose = useCallback(() => {
dispatch(closeToast());
}, [dispatch]);
return (
<>
{window.electron.platform === "win32" && (
@ -128,6 +134,13 @@ export function App() {
<section ref={contentRef} className={styles.content}>
<Outlet />
<Toast
visible={toast.visible}
message={toast.message}
type={toast.type}
onClose={handleToastClose}
/>
</section>
</article>
</main>

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M3.537 0C2.165 0 1.66.506 1.66 1.879V18.44a4.262 4.262 0 0 0 .02.433c.031.3.037.59.316.92c.027.033.311.245.311.245c.153.075.258.13.43.2l8.335 3.491c.433.199.614.276.928.27h.002c.314.006.495-.071.928-.27l8.335-3.492c.172-.07.277-.124.43-.2c0 0 .284-.211.311-.243c.28-.33.285-.621.316-.92a4.261 4.261 0 0 0 .02-.434V1.879c0-1.373-.506-1.88-1.878-1.88zm13.366 3.11h.68c1.138 0 1.688.553 1.688 1.696v1.88h-1.374v-1.8c0-.369-.17-.54-.523-.54h-.235c-.367 0-.537.17-.537.539v5.81c0 .369.17.54.537.54h.262c.353 0 .523-.171.523-.54V8.619h1.373v2.143c0 1.144-.562 1.71-1.7 1.71h-.694c-1.138 0-1.7-.566-1.7-1.71V4.82c0-1.144.562-1.709 1.7-1.709zm-12.186.08h3.114v1.274H6.117v2.603h1.648v1.275H6.117v2.774h1.74v1.275h-3.14zm3.816 0h2.198c1.138 0 1.7.564 1.7 1.708v2.445c0 1.144-.562 1.71-1.7 1.71h-.799v3.338h-1.4zm4.53 0h1.4v9.201h-1.4zm-3.13 1.235v3.392h.575c.354 0 .523-.171.523-.54V4.965c0-.368-.17-.54-.523-.54zm-3.74 10.147a1.708 1.708 0 0 1 .591.108a1.745 1.745 0 0 1 .49.299l-.452.546a1.247 1.247 0 0 0-.308-.195a.91.91 0 0 0-.363-.068a.658.658 0 0 0-.28.06a.703.703 0 0 0-.224.163a.783.783 0 0 0-.151.243a.799.799 0 0 0-.056.299v.008a.852.852 0 0 0 .056.31a.7.7 0 0 0 .157.245a.736.736 0 0 0 .238.16a.774.774 0 0 0 .303.058a.79.79 0 0 0 .445-.116v-.339h-.548v-.565H7.37v1.255a2.019 2.019 0 0 1-.524.307a1.789 1.789 0 0 1-.683.123a1.642 1.642 0 0 1-.602-.107a1.46 1.46 0 0 1-.478-.3a1.371 1.371 0 0 1-.318-.455a1.438 1.438 0 0 1-.115-.58v-.008a1.426 1.426 0 0 1 .113-.57a1.449 1.449 0 0 1 .312-.46a1.418 1.418 0 0 1 .474-.309a1.58 1.58 0 0 1 .598-.111a1.708 1.708 0 0 1 .045 0zm11.963.008a2.006 2.006 0 0 1 .612.094a1.61 1.61 0 0 1 .507.277l-.386.546a1.562 1.562 0 0 0-.39-.205a1.178 1.178 0 0 0-.388-.07a.347.347 0 0 0-.208.052a.154.154 0 0 0-.07.127v.008a.158.158 0 0 0 .022.084a.198.198 0 0 0 .076.066a.831.831 0 0 0 .147.06c.062.02.14.04.236.061a3.389 3.389 0 0 1 .43.122a1.292 1.292 0 0 1 .328.17a.678.678 0 0 1 .207.24a.739.739 0 0 1 .071.337v.008a.865.865 0 0 1-.081.382a.82.82 0 0 1-.229.285a1.032 1.032 0 0 1-.353.18a1.606 1.606 0 0 1-.46.061a2.16 2.16 0 0 1-.71-.116a1.718 1.718 0 0 1-.593-.346l.43-.514c.277.223.578.335.9.335a.457.457 0 0 0 .236-.05a.157.157 0 0 0 .082-.142v-.008a.15.15 0 0 0-.02-.077a.204.204 0 0 0-.073-.066a.753.753 0 0 0-.143-.062a2.45 2.45 0 0 0-.233-.062a5.036 5.036 0 0 1-.413-.113a1.26 1.26 0 0 1-.331-.16a.72.72 0 0 1-.222-.243a.73.73 0 0 1-.082-.36v-.008a.863.863 0 0 1 .074-.359a.794.794 0 0 1 .214-.283a1.007 1.007 0 0 1 .34-.185a1.423 1.423 0 0 1 .448-.066a2.006 2.006 0 0 1 .025 0m-9.358.025h.742l1.183 2.81h-.825l-.203-.499H8.623l-.198.498h-.81zm2.197.02h.814l.663 1.08l.663-1.08h.814v2.79h-.766v-1.602l-.711 1.091h-.016l-.707-1.083v1.593h-.754zm3.469 0h2.235v.658h-1.473v.422h1.334v.61h-1.334v.442h1.493v.658h-2.255zm-5.3.897l-.315.793h.624zm-1.145 5.19h8.014l-4.09 1.348z"/></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1 +0,0 @@
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M41.4193 7.30899C41.4193 7.30899 45.3046 5.79399 44.9808 9.47328C44.8729 10.9883 43.9016 16.2908 43.1461 22.0262L40.5559 39.0159C40.5559 39.0159 40.3401 41.5048 38.3974 41.9377C36.4547 42.3705 33.5408 40.4227 33.0011 39.9898C32.5694 39.6652 24.9068 34.7955 22.2086 32.4148C21.4531 31.7655 20.5897 30.4669 22.3165 28.9519L33.6487 18.1305C34.9438 16.8319 36.2389 13.8019 30.8426 17.4812L15.7331 27.7616C15.7331 27.7616 14.0063 28.8437 10.7686 27.8698L3.75342 25.7055C3.75342 25.7055 1.16321 24.0823 5.58815 22.459C16.3807 17.3729 29.6555 12.1786 41.4193 7.30899Z" fill="currentColor"></path> </g></svg>

Before

Width:  |  Height:  |  Size: 838 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="16px" height="16px"><g fill="currentColor" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M5.91992,6l14.66211,21.375l-14.35156,16.625h3.17969l12.57617,-14.57812l10,14.57813h12.01367l-15.31836,-22.33008l13.51758,-15.66992h-3.16992l-11.75391,13.61719l-9.3418,-13.61719zM9.7168,8h7.16406l23.32227,34h-7.16406z"></path></g></g></svg>

Before

Width:  |  Height:  |  Size: 702 B

View file

@ -9,6 +9,7 @@ export const bottomPanel = style({
alignItems: "center",
transition: "all ease 0.2s",
justifyContent: "space-between",
position: "relative",
zIndex: "1",
});

View file

@ -1,10 +1,10 @@
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useDownload } from "@renderer/hooks";
import * as styles from "./bottom-panel.css";
import { vars } from "../../theme.css";
import { useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { VERSION_CODENAME } from "@renderer/constants";
@ -25,6 +25,16 @@ export function BottomPanel() {
const status = useMemo(() => {
if (isGameDownloading) {
if (lastPacket?.isDownloadingMetadata)
return t("downloading_metadata", { title: lastPacket?.game.title });
if (!eta) {
return t("calculating_eta", {
title: lastPacket?.game.title,
percentage: progress,
});
}
return t("downloading", {
title: lastPacket?.game.title,
percentage: progress,
@ -34,17 +44,18 @@ export function BottomPanel() {
}
return t("no_downloads_in_progress");
}, [t, isGameDownloading, lastPacket?.game, progress, eta, downloadSpeed]);
}, [
t,
isGameDownloading,
lastPacket?.game,
lastPacket?.isDownloadingMetadata,
progress,
eta,
downloadSpeed,
]);
return (
<footer
className={styles.bottomPanel}
style={{
background: isGameDownloading
? `linear-gradient(90deg, ${vars.color.background} ${progress}, ${vars.color.darkBackground} ${progress})`
: vars.color.darkBackground,
}}
>
<footer className={styles.bottomPanel}>
<button
type="button"
className={styles.downloadsButton}

View file

@ -2,7 +2,6 @@ import { DownloadIcon, FileDirectoryIcon } from "@primer/octicons-react";
import type { CatalogueEntry } from "@types";
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
import EpicGamesLogo from "@renderer/assets/epic-games-logo.svg?react";
import * as styles from "./game-card.css";
import { useTranslation } from "react-i18next";
@ -16,7 +15,6 @@ export interface GameCardProps
}
const shopIcon = {
epic: <EpicGamesLogo className={styles.shopIcon} />,
steam: <SteamLogo className={styles.shopIcon} />,
};

View file

@ -8,3 +8,4 @@ export * from "./sidebar/sidebar";
export * from "./text-field/text-field";
export * from "./checkbox-field/checkbox-field";
export * from "./link/link";
export * from "./toast/toast";

View file

@ -73,6 +73,7 @@ declare global {
preferences: Partial<UserPreferences>
) => Promise<void>;
autoLaunch: (enabled: boolean) => Promise<void>;
authenticateRealDebrid: (apiToken: string) => Promise<RealDebridUser>;
/* Hardware */
getDiskFreeSpace: (path: string) => Promise<DiskSpace>;

View file

@ -3,3 +3,4 @@ export * from "./library-slice";
export * from "./use-preferences-slice";
export * from "./download-slice";
export * from "./window-slice";
export * from "./toast-slice";

View file

@ -0,0 +1,32 @@
import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import { ToastProps } from "@renderer/components/toast/toast";
export interface ToastState {
message: string;
type: ToastProps["type"];
visible: boolean;
}
const initialState: ToastState = {
message: "",
type: "success",
visible: false,
};
export const toastSlice = createSlice({
name: "toast",
initialState,
reducers: {
showToast: (state, action: PayloadAction<Omit<ToastState, "visible">>) => {
console.log(action.payload);
state.message = action.payload.message;
state.visible = true;
},
closeToast: (state) => {
state.visible = false;
},
},
});
export const { showToast, closeToast } = toastSlice.actions;

View file

@ -42,7 +42,7 @@ export function HeroPanel() {
if (game?.progress === 1) return <HeroPanelPlaytime />;
if (game?.status === "active") {
if (lastPacket?.downloadingMetadata && isGameDownloading) {
if (lastPacket?.isDownloadingMetadata && isGameDownloading) {
return (
<>
<p>{progress}</p>
@ -65,7 +65,8 @@ export function HeroPanel() {
{isGameDownloading
? progress
: formatDownloadProgress(game?.progress)}
{eta && <small>{t("eta", { eta })}</small>}
<small>{eta ? t("eta", { eta }) : t("calculating_eta")}</small>
</p>
<p className={styles.downloadDetailsRow}>
@ -87,7 +88,9 @@ export function HeroPanel() {
return (
<>
<p>{t("paused_progress", { progress: formattedProgress })}</p>
<p className={styles.downloadDetailsRow}>
{formattedProgress} <small>{t("paused")}</small>
</p>
<p>
{formatBytes(game.bytesDownloaded)} / {finalDownloadSize}
</p>

View file

@ -5,6 +5,8 @@ import { Button, CheckboxField, Link, TextField } from "@renderer/components";
import * as styles from "./settings-real-debrid.css";
import type { UserPreferences } from "@types";
import { SPACING_UNIT } from "@renderer/theme.css";
import { showToast } from "@renderer/features";
import { useAppDispatch } from "@renderer/hooks";
const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken";
@ -22,6 +24,8 @@ export function SettingsRealDebrid({
realDebridApiToken: null as string | null,
});
const dispatch = useAppDispatch();
const { t } = useTranslation("settings");
useEffect(() => {
@ -33,11 +37,36 @@ export function SettingsRealDebrid({
}
}, [userPreferences]);
const handleFormSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
const handleFormSubmit: React.FormEventHandler<HTMLFormElement> = async (
event
) => {
event.preventDefault();
updateUserPreferences({
realDebridApiToken: form.useRealDebrid ? form.realDebridApiToken : null,
});
dispatch(
showToast({
message: t("real_debrid_authenticated"),
type: "success",
})
);
if (form.useRealDebrid) {
const user = await window.electron.authenticateRealDebrid(
form.realDebridApiToken!
);
console.log(user);
if (user.type === "premium") {
dispatch(
showToast({
message: t("real_debrid_authenticated"),
type: "success",
})
);
}
}
// updateUserPreferences({
// realDebridApiToken: form.useRealDebrid ? form.realDebridApiToken : null,
// });
};
const isButtonDisabled = form.useRealDebrid && !form.realDebridApiToken;

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { Button } from "@renderer/components";
import * as styles from "./settings.css";
@ -7,7 +7,6 @@ import { UserPreferences } from "@types";
import { SettingsRealDebrid } from "./settings-real-debrid";
import { SettingsGeneral } from "./settings-general";
import { SettingsBehavior } from "./settings-behavior";
import { Toast } from "@renderer/components/toast/toast";
const categories = ["general", "behavior", "real_debrid"];
@ -15,7 +14,6 @@ export function Settings() {
const [currentCategory, setCurrentCategory] = useState(categories.at(0)!);
const [userPreferences, setUserPreferences] =
useState<UserPreferences | null>(null);
const [isToastVisible, setIsToastVisible] = useState(false);
const { t } = useTranslation("settings");
@ -28,12 +26,9 @@ export function Settings() {
const handleUpdateUserPreferences = async (
values: Partial<UserPreferences>
) => {
setIsToastVisible(false);
await window.electron.updateUserPreferences(values);
window.electron.getUserPreferences().then((userPreferences) => {
setUserPreferences(userPreferences);
setIsToastVisible(true);
});
};
@ -64,37 +59,24 @@ export function Settings() {
);
};
const handleToastClose = useCallback(() => {
setIsToastVisible(false);
}, []);
return (
<>
<section className={styles.container}>
<div className={styles.content}>
<section className={styles.settingsCategories}>
{categories.map((category) => (
<Button
key={category}
theme={currentCategory === category ? "primary" : "outline"}
onClick={() => setCurrentCategory(category)}
>
{t(category)}
</Button>
))}
</section>
<section className={styles.container}>
<div className={styles.content}>
<section className={styles.settingsCategories}>
{categories.map((category) => (
<Button
key={category}
theme={currentCategory === category ? "primary" : "outline"}
onClick={() => setCurrentCategory(category)}
>
{t(category)}
</Button>
))}
</section>
<h2>{t(currentCategory)}</h2>
{renderCategory()}
</div>
</section>
<Toast
message="Settings have been saved"
visible={isToastVisible}
onClose={handleToastClose}
type="success"
/>
</>
<h2>{t(currentCategory)}</h2>
{renderCategory()}
</div>
</section>
);
}

View file

@ -5,6 +5,7 @@ import {
librarySlice,
searchSlice,
userPreferencesSlice,
toastSlice,
} from "@renderer/features";
export const store = configureStore({
@ -14,6 +15,7 @@ export const store = configureStore({
library: librarySlice.reducer,
userPreferences: userPreferencesSlice.reducer,
download: downloadSlice.reducer,
toast: toastSlice.reducer,
},
});