refactor: migrate settings page styles from VE to SCSS + BEM

This commit is contained in:
Hachi-R 2025-01-21 14:39:15 -03:00
parent eed28d7444
commit c51b61501e
14 changed files with 269 additions and 108 deletions

View File

@ -0,0 +1,27 @@
@use "../../scss/globals.scss";
.add-download-source-modal {
&__container {
display: flex;
flex-direction: column;
gap: globals.$spacing-unit;
min-width: 500px;
}
&__validation-result {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: calc(globals.$spacing-unit * 3);
}
&__validation-info {
display: flex;
flex-direction: column;
gap: calc(globals.$spacing-unit / 2);
}
&__validate-button {
align-self: flex-end;
}
}

View File

@ -2,7 +2,6 @@ import { useCallback, useContext, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Button, Modal, TextField } from "@renderer/components";
import { SPACING_UNIT } from "@renderer/theme.css";
import { settingsContext } from "@renderer/context";
import { useForm } from "react-hook-form";
@ -11,6 +10,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
import { downloadSourcesTable } from "@renderer/dexie";
import type { DownloadSourceValidationResult } from "@types";
import { downloadSourcesWorker } from "@renderer/workers";
import "./add-download-source-modal.scss";
interface AddDownloadSourceModalProps {
visible: boolean;
@ -138,14 +138,7 @@ export function AddDownloadSourceModal({
description={t("add_download_source_description")}
onClose={onClose}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: `${SPACING_UNIT}px`,
minWidth: "500px",
}}
>
<div className="add-download-source-modal__container">
<TextField
{...register("url")}
label={t("download_source_url")}
@ -155,7 +148,7 @@ export function AddDownloadSourceModal({
<Button
type="button"
theme="outline"
style={{ alignSelf: "flex-end" }}
className="add-download-source-modal__validate-button"
onClick={handleSubmit(onSubmit)}
disabled={isSubmitting || isLoading}
>
@ -165,21 +158,8 @@ export function AddDownloadSourceModal({
/>
{validationResult && (
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginTop: `${SPACING_UNIT * 3}px`,
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: `${SPACING_UNIT / 2}px`,
}}
>
<div className="add-download-source-modal__validation-result">
<div className="add-download-source-modal__validation-info">
<h4>{validationResult?.name}</h4>
<small>
{t("found_download_option", {

View File

@ -0,0 +1,67 @@
@use "../../scss/globals.scss";
.settings-account {
&__form {
display: flex;
flex-direction: column;
gap: calc(globals.$spacing-unit * 2);
}
&__section {
display: flex;
flex-direction: column;
gap: globals.$spacing-unit;
}
&__actions {
display: flex;
gap: globals.$spacing-unit;
margin-top: globals.$spacing-unit;
}
&__subscription-info {
display: flex;
flex-direction: column;
gap: calc(globals.$spacing-unit * 2);
}
&__subscription-button {
place-self: flex-start;
}
&__blocked-users {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: globals.$spacing-unit;
}
&__blocked-user {
display: flex;
align-items: center;
justify-content: space-between;
gap: globals.$spacing-unit;
}
&__user-info {
display: flex;
gap: globals.$spacing-unit;
align-items: center;
}
&__user-avatar {
filter: grayscale(100%);
}
&__unblock-button {
color: globals.$danger-color;
cursor: pointer;
background: none;
border: none;
padding: 0;
display: flex;
align-items: center;
}
}

View File

@ -1,9 +1,6 @@
import { Avatar, Button, SelectField } from "@renderer/components";
import { SPACING_UNIT } from "@renderer/theme.css";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import * as styles from "./settings-account.css";
import { useDate, useToast, useUserDetails } from "@renderer/hooks";
import { useCallback, useContext, useEffect, useState } from "react";
import {
@ -14,6 +11,7 @@ import {
} from "@primer/octicons-react";
import { settingsContext } from "@renderer/context";
import { AuthPage } from "@shared";
import "./settings-account.scss";
interface FormValues {
profileVisibility: "PUBLIC" | "FRIENDS" | "PRIVATE";
@ -145,7 +143,7 @@ export function SettingsAccount() {
if (!userDetails) return null;
return (
<form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
<form className="settings-account__form" onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="profileVisibility"
@ -158,7 +156,7 @@ export function SettingsAccount() {
};
return (
<section>
<section className="settings-account__section">
<SelectField
label={t("profile_visibility")}
value={field.value}
@ -177,19 +175,11 @@ export function SettingsAccount() {
}}
/>
<section>
<section className="settings-account__section">
<h4>{t("current_email")}</h4>
<p>{userDetails?.email ?? t("no_email_account")}</p>
<div
style={{
display: "flex",
justifyContent: "start",
alignItems: "center",
gap: `${SPACING_UNIT}px`,
marginTop: `${SPACING_UNIT * 2}px`,
}}
>
<div className="settings-account__actions">
<Button
theme="outline"
onClick={() => window.electron.openAuthWindow(AuthPage.UpdateEmail)}
@ -210,28 +200,14 @@ export function SettingsAccount() {
</div>
</section>
<section
style={{
display: "flex",
flexDirection: "column",
gap: `${SPACING_UNIT * 2}px`,
}}
>
<section className="settings-account__section">
<h3>Hydra Cloud</h3>
<div
style={{
display: "flex",
flexDirection: "column",
gap: `${SPACING_UNIT}px`,
}}
>
<div className="settings-account__subscription-info">
{getHydraCloudSectionContent().description}
</div>
<Button
style={{
placeSelf: "flex-start",
}}
className="settings-account__subscription-button"
theme="outline"
onClick={() => window.electron.openCheckout()}
>
@ -240,29 +216,17 @@ export function SettingsAccount() {
</Button>
</section>
<section
style={{
display: "flex",
flexDirection: "column",
gap: `${SPACING_UNIT * 2}px`,
}}
>
<section className="settings-account__section">
<h3>{t("blocked_users")}</h3>
{blockedUsers.length > 0 ? (
<ul className={styles.blockedUsersList}>
<ul className="settings-account__blocked-users">
{blockedUsers.map((user) => {
return (
<li key={user.id} className={styles.blockedUser}>
<div
style={{
display: "flex",
gap: `${SPACING_UNIT}px`,
alignItems: "center",
}}
>
<li key={user.id} className="settings-account__blocked-user">
<div className="settings-account__user-info">
<Avatar
style={{ filter: "grayscale(100%)" }}
className="settings-account__user-avatar"
size={32}
src={user.profileImageUrl}
alt={user.displayName}
@ -272,7 +236,7 @@ export function SettingsAccount() {
<button
type="button"
className={styles.unblockButton}
className="settings-account__unblock-button"
onClick={() => handleUnblockClick(user.id)}
disabled={isUnblocking}
>

View File

@ -0,0 +1,13 @@
@use "../../scss/globals.scss";
.settings-behavior {
&__checkbox-container {
opacity: globals.$disabled-opacity;
cursor: not-allowed;
&--enabled {
opacity: 1;
cursor: pointer;
}
}
}

View File

@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { CheckboxField } from "@renderer/components";
import { useAppSelector } from "@renderer/hooks";
import { settingsContext } from "@renderer/context";
import "./settings-behavior.scss";
export function SettingsBehavior() {
const userPreferences = useAppSelector(
@ -77,10 +78,11 @@ export function SettingsBehavior() {
)}
{showRunAtStartup && (
<div style={{ opacity: form.runAtStartup ? 1 : 0.5 }}>
<div
className={`settings-behavior__checkbox-container ${form.runAtStartup ? "settings-behavior__checkbox-container--enabled" : ""}`}
>
<CheckboxField
label={t("launch_minimized")}
style={{ cursor: form.runAtStartup ? "pointer" : "not-allowed" }}
checked={form.runAtStartup && form.startMinimized}
disabled={!form.runAtStartup}
onChange={() => {

View File

@ -0,0 +1,56 @@
@use "../../scss/globals.scss";
.settings-download-sources {
&__list {
padding: 0;
margin: 0;
gap: calc(globals.$spacing-unit * 2);
display: flex;
flex-direction: column;
}
&__item {
display: flex;
flex-direction: column;
background-color: globals.$dark-background-color;
border-radius: 8px;
padding: calc(globals.$spacing-unit * 2);
gap: globals.$spacing-unit;
border: solid 1px globals.$border-color;
transition: all ease 0.2s;
&--syncing {
opacity: globals.$disabled-opacity;
}
}
&__item-header {
margin-bottom: globals.$spacing-unit;
display: flex;
flex-direction: column;
gap: globals.$spacing-unit;
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
}
&__navigate-button {
display: flex;
align-items: center;
gap: globals.$spacing-unit;
color: globals.$muted-color;
text-decoration: underline;
cursor: pointer;
background: none;
border: none;
padding: 0;
&:disabled {
cursor: default;
text-decoration: none;
}
}
}

View File

@ -3,7 +3,6 @@ import { useContext, useEffect, useState } from "react";
import { TextField, Button, Badge } from "@renderer/components";
import { useTranslation } from "react-i18next";
import * as styles from "./settings-download-sources.css";
import type { DownloadSource } from "@types";
import { NoEntryIcon, PlusCircleIcon, SyncIcon } from "@primer/octicons-react";
import { AddDownloadSourceModal } from "./add-download-source-modal";
@ -14,6 +13,7 @@ import { downloadSourcesTable } from "@renderer/dexie";
import { downloadSourcesWorker } from "@renderer/workers";
import { useNavigate } from "react-router-dom";
import { setFilters, clearFilters } from "@renderer/features";
import "./settings-download-sources.scss";
export function SettingsDownloadSources() {
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
@ -118,7 +118,7 @@ export function SettingsDownloadSources() {
<p>{t("download_sources_description")}</p>
<div className={styles.downloadSourcesHeader}>
<div className="settings-download-sources__header">
<Button
type="button"
theme="outline"
@ -144,15 +144,13 @@ export function SettingsDownloadSources() {
</Button>
</div>
<ul className={styles.downloadSources}>
<ul className="settings-download-sources__list">
{downloadSources.map((downloadSource) => (
<li
key={downloadSource.id}
className={styles.downloadSourceItem({
isSyncing: isSyncingDownloadSources,
})}
className={`settings-download-sources__item ${isSyncingDownloadSources ? "settings-download-sources__item--syncing" : ""}`}
>
<div className={styles.downloadSourceItemHeader}>
<div className="settings-download-sources__item-header">
<h2>{downloadSource.name}</h2>
<div style={{ display: "flex" }}>
@ -161,7 +159,7 @@ export function SettingsDownloadSources() {
<button
type="button"
className={styles.navigateToCatalogueButton}
className="settings-download-sources__navigate-button"
disabled={!downloadSource.fingerprint}
onClick={() => navigateToCatalogue(downloadSource.fingerprint)}
>

View File

@ -0,0 +1,12 @@
@use "../../scss/globals.scss";
.settings-general {
display: flex;
flex-direction: column;
gap: globals.$spacing-unit * 2;
&__notifications-title {
margin-top: calc(globals.$spacing-unit * 2);
margin-bottom: globals.$spacing-unit;
}
}

View File

@ -11,6 +11,7 @@ import { changeLanguage } from "i18next";
import languageResources from "@locales";
import { orderBy } from "lodash-es";
import { settingsContext } from "@renderer/context";
import "./settings-general.scss";
interface LanguageOption {
option: string;
@ -114,7 +115,7 @@ export function SettingsGeneral() {
}
return (
<>
<div className="settings-general">
<TextField
label={t("downloads_path")}
value={form.downloadsPath}
@ -138,7 +139,9 @@ export function SettingsGeneral() {
}))}
/>
<p>{t("notifications")}</p>
<p className="settings-general__notifications-title">
{t("notifications")}
</p>
<CheckboxField
label={t("enable_download_notifications")}
@ -171,6 +174,6 @@ export function SettingsGeneral() {
})
}
/>
</>
</div>
);
}

View File

@ -0,0 +1,18 @@
@use "../../scss/globals.scss";
.settings-real-debrid {
&__form {
display: flex;
flex-direction: column;
gap: globals.$spacing-unit;
}
&__description {
margin-bottom: calc(globals.$spacing-unit * 2);
}
&__submit-button {
align-self: flex-end;
width: 100%;
}
}

View File

@ -2,11 +2,10 @@ 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 "./settings-real-debrid.scss";
import { useAppSelector, useToast } from "@renderer/hooks";
import { SPACING_UNIT } from "@renderer/theme.css";
import { settingsContext } from "@renderer/context";
const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken";
@ -78,8 +77,10 @@ export function SettingsRealDebrid() {
(form.useRealDebrid && !form.realDebridApiToken) || isLoading;
return (
<form className={styles.form} onSubmit={handleFormSubmit}>
<p className={styles.description}>{t("real_debrid_description")}</p>
<form className="settings-real-debrid__form" onSubmit={handleFormSubmit}>
<p className="settings-real-debrid__description">
{t("real_debrid_description")}
</p>
<CheckboxField
label={t("enable_real_debrid")}
@ -100,8 +101,12 @@ export function SettingsRealDebrid() {
onChange={(event) =>
setForm({ ...form, realDebridApiToken: event.target.value })
}
rightContent={
<Button type="submit" disabled={isButtonDisabled}>
{t("save_changes")}
</Button>
}
placeholder="API Token"
containerProps={{ style: { marginTop: `${SPACING_UNIT}px` } }}
hint={
<Trans i18nKey="real_debrid_api_token_hint" ns="settings">
<Link to={REAL_DEBRID_API_TOKEN_URL} />
@ -109,14 +114,6 @@ export function SettingsRealDebrid() {
}
/>
)}
<Button
type="submit"
style={{ alignSelf: "flex-end", marginTop: `${SPACING_UNIT * 2}px` }}
disabled={isButtonDisabled}
>
{t("save_changes")}
</Button>
</form>
);
}

View File

@ -0,0 +1,27 @@
@use "../../scss/globals.scss";
.settings {
&__container {
padding: 24px;
width: 100%;
display: flex;
}
&__content {
background-color: globals.$background-color;
width: 100%;
height: 100%;
padding: calc(globals.$spacing-unit * 3);
border: solid 1px globals.$border-color;
box-shadow: 0px 0px 15px 0px #000000;
border-radius: 8px;
gap: calc(globals.$spacing-unit * 2);
display: flex;
flex-direction: column;
}
&__categories {
display: flex;
gap: globals.$spacing-unit;
}
}

View File

@ -1,11 +1,8 @@
import { Button } from "@renderer/components";
import * as styles from "./settings.css";
import { useTranslation } from "react-i18next";
import { SettingsRealDebrid } from "./settings-real-debrid";
import { SettingsGeneral } from "./settings-general";
import { SettingsBehavior } from "./settings-behavior";
import { SettingsDownloadSources } from "./settings-download-sources";
import {
SettingsContextConsumer,
@ -14,10 +11,10 @@ import {
import { SettingsAccount } from "./settings-account";
import { useUserDetails } from "@renderer/hooks";
import { useMemo } from "react";
import "./settings.scss";
export default function Settings() {
const { t } = useTranslation("settings");
const { userDetails } = useUserDetails();
const categories = useMemo(() => {
@ -57,9 +54,9 @@ export default function Settings() {
};
return (
<section className={styles.container}>
<div className={styles.content}>
<section className={styles.settingsCategories}>
<section className="settings__container">
<div className="settings__content">
<section className="settings__categories">
{categories.map((category, index) => (
<Button
key={category}