diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 4e3dcb37..233d04e4 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -280,7 +280,8 @@ "launch_minimized": "Launch Hydra minimized", "disable_nsfw_alert": "Disable NSFW alert", "seed_after_download_complete": "Seed after download complete", - "show_hidden_achievement_description": "Show hidden achievements description before unlocking them" + "show_hidden_achievement_description": "Show hidden achievements description before unlocking them", + "debrid_services": "Debrid Services" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 2a80084f..453aff7c 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -268,7 +268,8 @@ "launch_minimized": "Iniciar o Hydra minimizado", "disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado", "seed_after_download_complete": "Semear após a conclusão do download", - "show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las" + "show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las", + "debrid_services": "Serviços Debrid" }, "notifications": { "download_complete": "Download concluído", diff --git a/src/renderer/src/pages/settings/settings-debrid-input.tsx b/src/renderer/src/pages/settings/settings-debrid-input.tsx new file mode 100644 index 00000000..5223e378 --- /dev/null +++ b/src/renderer/src/pages/settings/settings-debrid-input.tsx @@ -0,0 +1,186 @@ +import { useContext, useEffect, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Button, CheckboxField, Link, TextField } from "@renderer/components"; +import * as styles from "./settings-debrid.css"; +import { useAppSelector, useToast } from "@renderer/hooks"; +import { SPACING_UNIT } from "@renderer/theme.css"; +import { settingsContext } from "@renderer/context"; +import { DebridServices } from "@types"; + +const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken"; +const TORBOX_API_TOKEN_URL = "https://torbox.app/settings"; + +interface SettingsDebridForm { + useRealDebrid: boolean; + realDebridApiToken: string | null; + useTorBox: boolean; + torBoxApiToken: string | null; +} + +export interface SettingsDebridProps { + service: DebridServices; + form: SettingsDebridForm; + setForm: (SettingsDebridForm) => void; +} + +export function SettingsDebridInput({ + service, + form, + setForm, +}: SettingsDebridProps) { + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + + const { updateUserPreferences } = useContext(settingsContext); + + const [isLoading, setIsLoading] = useState(false); + + const { showSuccessToast, showErrorToast } = useToast(); + + const { t } = useTranslation("settings"); + + useEffect(() => { + if (userPreferences) { + setForm({ + useRealDebrid: Boolean(userPreferences.realDebridApiToken), + realDebridApiToken: userPreferences.realDebridApiToken ?? null, + useTorBox: Boolean(userPreferences.torBoxApiToken), + torBoxApiToken: userPreferences.torBoxApiToken ?? null, + }); + } + }, [userPreferences]); + + const handleFormSubmit: React.FormEventHandler = async ( + event + ) => { + setIsLoading(true); + event.preventDefault(); + + try { + if (form.useRealDebrid) { + const user = await window.electron.authenticateRealDebrid( + form.realDebridApiToken! + ); + + if (user.type === "free") { + showErrorToast( + t("real_debrid_free_account_error", { username: user.username }) + ); + + return; + } else { + showSuccessToast( + t("real_debrid_linked_message", { username: user.username }) + ); + } + } else { + showSuccessToast(t("changes_saved")); + } + + updateUserPreferences({ + realDebridApiToken: form.useRealDebrid ? form.realDebridApiToken : null, + }); + } catch (err) { + showErrorToast(t("real_debrid_invalid_token")); + } finally { + setIsLoading(false); + } + }; + + const useDebridService = useMemo(() => { + if (service === "RealDebrid") { + return form.useRealDebrid; + } + + if (service === "TorBox") { + return form.useTorBox; + } + + return false; + }, [form, service]); + + const debridApiToken = useMemo(() => { + if (service === "RealDebrid") { + return form.realDebridApiToken; + } + + if (service === "TorBox") { + return form.torBoxApiToken; + } + + return null; + }, [form, service]); + + const onChangeCheckbox = () => { + if (service === "RealDebrid") { + setForm((prev) => ({ + ...prev, + useRealDebrid: !form.useRealDebrid, + })); + } + + if (service === "TorBox") { + setForm((prev) => ({ + ...prev, + useTorBox: !form.useTorBox, + })); + } + }; + + const onChangeInput = (event: React.ChangeEvent) => { + if (service === "RealDebrid") { + setForm((prev) => ({ + ...prev, + realDebridApiToken: event.target.value, + })); + } + + if (service === "TorBox") { + setForm((prev) => ({ + ...prev, + torBoxApiToken: event.target.value, + })); + } + }; + + const isButtonDisabled = + (form.useRealDebrid && !form.realDebridApiToken) || isLoading; + + return ( +
+ + + {useDebridService && ( + + {t("save")} + + } + hint={ + + + + } + /> + )} + + ); +} diff --git a/src/renderer/src/pages/settings/settings-real-debrid.css.ts b/src/renderer/src/pages/settings/settings-debrid.css.ts similarity index 100% rename from src/renderer/src/pages/settings/settings-real-debrid.css.ts rename to src/renderer/src/pages/settings/settings-debrid.css.ts diff --git a/src/renderer/src/pages/settings/settings-real-debrid.tsx b/src/renderer/src/pages/settings/settings-debrid.tsx similarity index 65% rename from src/renderer/src/pages/settings/settings-real-debrid.tsx rename to src/renderer/src/pages/settings/settings-debrid.tsx index 35804664..9d63e68a 100644 --- a/src/renderer/src/pages/settings/settings-real-debrid.tsx +++ b/src/renderer/src/pages/settings/settings-debrid.tsx @@ -2,7 +2,7 @@ import { useContext, useEffect, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { Button, CheckboxField, Link, TextField } from "@renderer/components"; -import * as styles from "./settings-real-debrid.css"; +import * as styles from "./settings-debrid.css"; import { useAppSelector, useToast } from "@renderer/hooks"; @@ -10,8 +10,9 @@ import { SPACING_UNIT } from "@renderer/theme.css"; import { settingsContext } from "@renderer/context"; const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken"; +const TORBOX_API_TOKEN_URL = "https://torbox.app/settings"; -export function SettingsRealDebrid() { +export function SettingsDebrid() { const userPreferences = useAppSelector( (state) => state.userPreferences.value ); @@ -22,6 +23,8 @@ export function SettingsRealDebrid() { const [form, setForm] = useState({ useRealDebrid: false, realDebridApiToken: null as string | null, + useTorBox: false, + torBoxApiToken: null as string | null, }); const { showSuccessToast, showErrorToast } = useToast(); @@ -33,6 +36,8 @@ export function SettingsRealDebrid() { setForm({ useRealDebrid: Boolean(userPreferences.realDebridApiToken), realDebridApiToken: userPreferences.realDebridApiToken ?? null, + useTorBox: Boolean(userPreferences.torBoxApiToken), + torBoxApiToken: userPreferences.torBoxApiToken ?? null, }); } }, [userPreferences]); @@ -102,6 +107,17 @@ export function SettingsRealDebrid() { } placeholder="API Token" containerProps={{ style: { marginTop: `${SPACING_UNIT}px` } }} + rightContent={ + + } hint={ @@ -110,13 +126,45 @@ export function SettingsRealDebrid() { /> )} - + + setForm((prev) => ({ + ...prev, + useTorBox: !form.useTorBox, + })) + } + /> + + {form.useTorBox && ( + + setForm({ ...form, torBoxApiToken: event.target.value }) + } + placeholder="API Token" + containerProps={{ style: { marginTop: `${SPACING_UNIT}px` } }} + rightContent={ + + } + hint={ + + + + } + /> + )} ); } diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index dffdfbae..00ceebd7 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -2,7 +2,7 @@ import { Button } from "@renderer/components"; import * as styles from "./settings.css"; import { useTranslation } from "react-i18next"; -import { SettingsRealDebrid } from "./settings-real-debrid"; +import { SettingsDebrid } from "./settings-debrid"; import { SettingsGeneral } from "./settings-general"; import { SettingsBehavior } from "./settings-behavior"; @@ -25,7 +25,7 @@ export default function Settings() { t("general"), t("behavior"), t("download_sources"), - "Real-Debrid", + t("debrid_services"), ]; if (userDetails) return [...categories, t("privacy")]; @@ -50,7 +50,7 @@ export default function Settings() { } if (currentCategoryIndex === 3) { - return ; + return ; } return ; diff --git a/src/types/index.ts b/src/types/index.ts index b6fcbbb4..da5deea4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -19,6 +19,8 @@ export type HydraCloudFeature = | "backup" | "achievements-points"; +export type DebridServices = "RealDebrid" | "TorBox"; + export interface GameRepack { id: number; title: string;