diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index 1baf4614..43e98033 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -296,7 +296,8 @@
"become_subscriber": "Be Hydra Cloud",
"subscription_renew_cancelled": "Automatic renewal is disabled",
"subscription_renews_on": "Your subscription renews on {{date}}",
- "bill_sent_until": "Your next bill will be sent until this day"
+ "bill_sent_until": "Your next bill will be sent until this day",
+ "no_themes": "Seems like you don't have any themes yet, but no worries, click here to create your first masterpiece."
},
"notifications": {
"download_complete": "Download complete",
diff --git a/src/renderer/src/pages/settings/aparence/components/theme-actions.scss b/src/renderer/src/pages/settings/aparence/components/theme-actions.scss
new file mode 100644
index 00000000..0b038a28
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/components/theme-actions.scss
@@ -0,0 +1,25 @@
+@use "../../../../scss/globals.scss";
+
+.settings-appearance {
+ &__actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ &-left {
+ display: flex;
+ gap: 8px;
+ }
+
+ &-right {
+ display: flex;
+ gap: 8px;
+ }
+ }
+
+ &__button {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+}
diff --git a/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx b/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx
new file mode 100644
index 00000000..18e14afe
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/components/theme-actions.tsx
@@ -0,0 +1,32 @@
+import { GlobeIcon, TrashIcon } from "@primer/octicons-react";
+import { PlusIcon } from "@primer/octicons-react";
+import { Button } from "@renderer/components/button/button";
+import { useTranslation } from "react-i18next";
+import "./theme-actions.scss";
+
+export const ThemeActions = () => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/renderer/src/pages/settings/aparence/components/theme-card.scss b/src/renderer/src/pages/settings/aparence/components/theme-card.scss
new file mode 100644
index 00000000..fd43893e
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/components/theme-card.scss
@@ -0,0 +1,93 @@
+@use "../../../../scss/globals.scss";
+
+.theme-card {
+ width: 100%;
+ min-height: 160px;
+ display: flex;
+ flex-direction: column;
+ background-color: rgba(globals.$border-color, 0.01);
+ border: 1px solid globals.$border-color;
+ border-radius: 12px;
+ gap: 4px;
+ transition: background-color 0.2s ease;
+ padding: 16px;
+ position: relative;
+
+ &--active {
+ background-color: rgba(globals.$border-color, 0.04);
+ }
+
+ &__header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-direction: row;
+ gap: 16px;
+
+ &__title {
+ font-size: 18px;
+ font-weight: 600;
+ color: globals.$muted-color;
+ text-transform: capitalize;
+ }
+
+ &__colors {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+
+ &__color {
+ width: 16px;
+ height: 16px;
+ border-radius: 4px;
+ border: 1px solid globals.$border-color;
+ }
+ }
+ }
+
+ &__author {
+ font-size: 12px;
+ color: globals.$body-color;
+ font-weight: 400;
+
+ &__name {
+ font-weight: 600;
+ color: rgba(globals.$muted-color, 0.8);
+ margin-left: 4px;
+
+ &:hover {
+ color: globals.$muted-color;
+ cursor: pointer;
+ text-decoration: underline;
+ text-underline-offset: 2px;
+ }
+ }
+ }
+
+ &__actions {
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ bottom: 16px;
+ left: 16px;
+ right: 16px;
+ gap: 8px;
+ justify-content: space-between;
+
+ &__left {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ }
+
+ &__right {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+
+ Button {
+ padding: 8px 11px;
+ }
+ }
+ }
+}
diff --git a/src/renderer/src/pages/settings/aparence/components/theme-card.tsx b/src/renderer/src/pages/settings/aparence/components/theme-card.tsx
new file mode 100644
index 00000000..80c37f51
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/components/theme-card.tsx
@@ -0,0 +1,84 @@
+import { PencilIcon, TrashIcon } from "@primer/octicons-react";
+import { useTranslation } from "react-i18next";
+import { Button } from "@renderer/components/button/button";
+import type { Theme } from "../themes-manager";
+import { useNavigate } from "react-router-dom";
+import "./theme-card.scss";
+
+interface ThemeCardProps {
+ theme: Theme;
+ handleSetTheme: (themeId: string) => void;
+ handleDeleteTheme: (themeId: string) => void;
+}
+
+export const ThemeCard = ({
+ theme,
+ handleSetTheme,
+ handleDeleteTheme,
+}: ThemeCardProps) => {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+
+ return (
+
+
+
{theme.name}
+
+
+ {Object.entries(theme.colors).map(([key, color]) => (
+
+ {/* color circle */}
+
+ ))}
+
+
+
+ {theme.author && theme.authorId && (
+
+ {t("by")}
+
+ navigate(`/profile/${theme.authorId}`)}
+ >
+ {theme.author}
+
+
+ )}
+
+
+
+ {theme.isActive ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/renderer/src/pages/settings/aparence/components/theme-placeholder.scss b/src/renderer/src/pages/settings/aparence/components/theme-placeholder.scss
new file mode 100644
index 00000000..737ad5e5
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/components/theme-placeholder.scss
@@ -0,0 +1,39 @@
+@use "../../../../scss/globals.scss";
+
+.theme-placeholder {
+ width: 100%;
+ min-height: 160px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 40px 24px;
+ background-color: rgba(globals.$border-color, 0.01);
+ cursor: pointer;
+ border: 1px dashed globals.$border-color;
+ border-radius: 12px;
+ gap: 12px;
+ transition: background-color 0.2s ease;
+
+ &:hover {
+ background-color: rgba(globals.$border-color, 0.03);
+ }
+
+ &__icon {
+ svg {
+ width: 32px;
+ height: 32px;
+ color: globals.$body-color;
+ opacity: 0.7;
+ }
+ }
+
+ &__text {
+ text-align: center;
+ max-width: 400px;
+ font-size: 14.5px;
+ line-height: 1.6;
+ font-weight: 400;
+ color: rgba(globals.$body-color, 0.85);
+ }
+}
diff --git a/src/renderer/src/pages/settings/aparence/components/theme-placeholder.tsx b/src/renderer/src/pages/settings/aparence/components/theme-placeholder.tsx
new file mode 100644
index 00000000..2f10877e
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/components/theme-placeholder.tsx
@@ -0,0 +1,26 @@
+import { AlertIcon } from "@primer/octicons-react";
+import { useTranslation } from "react-i18next";
+import "./theme-placeholder.scss";
+
+interface ThemePlaceholderProps {
+ setAddThemeModalVisible: (visible: boolean) => void;
+}
+
+export const ThemePlaceholder = ({
+ setAddThemeModalVisible,
+}: ThemePlaceholderProps) => {
+ const { t } = useTranslation();
+
+ return (
+
+ );
+};
diff --git a/src/renderer/src/pages/settings/aparence/index.ts b/src/renderer/src/pages/settings/aparence/index.ts
new file mode 100644
index 00000000..f78246c3
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/index.ts
@@ -0,0 +1,8 @@
+export { SettingsAppearance } from "./settings-appearance";
+export { AddThemeModal } from "./modals/add-theme-modal";
+export { DeleteAllThemesModal } from "./modals/delete-all-themes-modal";
+export { DeleteThemeModal } from "./modals/delete-theme-modal";
+export { ThemeCard } from "./components/theme-card";
+export { ThemePlaceholder } from "./components/theme-placeholder";
+export { ThemesManager } from "./themes-manager";
+export { ThemeActions } from "./components/theme-actions";
diff --git a/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx
new file mode 100644
index 00000000..7f7d0440
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/modals/add-theme-modal.tsx
@@ -0,0 +1,40 @@
+import { Modal } from "@renderer/components/modal/modal";
+import { TextField } from "@renderer/components/text-field/text-field";
+import { useTranslation } from "react-i18next";
+import "./modals.scss";
+import { Button } from "@renderer/components/button/button";
+import { useState } from "react";
+
+interface AddThemeModalProps {
+ visible: boolean;
+ onClose: () => void;
+}
+
+export const AddThemeModal = ({ visible, onClose }: AddThemeModalProps) => {
+ const { t } = useTranslation("settings");
+
+ const [themeName, setThemeName] = useState("");
+
+ return (
+
+
+ setThemeName(e.target.value)}
+ />
+
+
+
+
+ );
+};
diff --git a/src/renderer/src/pages/settings/aparence/modals/delete-all-themes-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/delete-all-themes-modal.tsx
new file mode 100644
index 00000000..48c17efb
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/modals/delete-all-themes-modal.tsx
@@ -0,0 +1,35 @@
+import { Button } from "@renderer/components/button/button";
+import { Modal } from "@renderer/components/modal/modal";
+import { useTranslation } from "react-i18next";
+import "./modals.scss";
+
+interface DeleteAllThemesModalProps {
+ visible: boolean;
+ onClose: () => void;
+}
+
+export const DeleteAllThemesModal = ({
+ visible,
+ onClose,
+}: DeleteAllThemesModalProps) => {
+ const { t } = useTranslation("settings");
+
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/renderer/src/pages/settings/aparence/modals/delete-theme-modal.tsx b/src/renderer/src/pages/settings/aparence/modals/delete-theme-modal.tsx
new file mode 100644
index 00000000..4b0f8be4
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/modals/delete-theme-modal.tsx
@@ -0,0 +1,36 @@
+import { Button } from "@renderer/components/button/button";
+import { Modal } from "@renderer/components/modal/modal";
+import { useTranslation } from "react-i18next";
+import "./modals.scss";
+
+interface DeleteThemeModalProps {
+ visible: boolean;
+ onClose: () => void;
+ themeId: string;
+}
+
+export const DeleteThemeModal = ({
+ visible,
+ onClose,
+}: DeleteThemeModalProps) => {
+ const { t } = useTranslation("settings");
+
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/renderer/src/pages/settings/aparence/modals/modals.scss b/src/renderer/src/pages/settings/aparence/modals/modals.scss
new file mode 100644
index 00000000..ff8d0bbc
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/modals/modals.scss
@@ -0,0 +1,15 @@
+.add-theme-modal {
+ &__container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ }
+}
+
+.delete-all-themes-modal__container {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ width: 100%;
+ justify-content: flex-end;
+}
diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.scss b/src/renderer/src/pages/settings/aparence/settings-appearance.scss
new file mode 100644
index 00000000..af4f4dc8
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/settings-appearance.scss
@@ -0,0 +1,154 @@
+@use "../../../scss/globals.scss";
+
+.settings-appearance {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ &__actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ &-left {
+ display: flex;
+ gap: 8px;
+ }
+ }
+
+ &__themes {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ &__theme {
+ width: 100%;
+ min-height: 160px;
+ display: flex;
+ flex-direction: column;
+ background-color: rgba(globals.$border-color, 0.01);
+ border: 1px solid globals.$border-color;
+ border-radius: 12px;
+ gap: 4px;
+ transition: background-color 0.2s ease;
+ padding: 16px;
+ position: relative;
+
+ &--active {
+ background-color: rgba(globals.$border-color, 0.04);
+ }
+
+ &__header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-direction: row;
+ gap: 16px;
+
+ &__title {
+ font-size: 18px;
+ font-weight: 600;
+ color: globals.$muted-color;
+ text-transform: capitalize;
+ }
+
+ &__colors {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+
+ &__color {
+ width: 16px;
+ height: 16px;
+ border-radius: 4px;
+ border: 1px solid globals.$border-color;
+ }
+ }
+ }
+
+ &__author {
+ font-size: 12px;
+ color: globals.$body-color;
+ font-weight: 400;
+
+ &__name {
+ font-weight: 600;
+ color: rgba(globals.$muted-color, 0.8);
+ margin-left: 4px;
+
+ &:hover {
+ color: globals.$muted-color;
+ cursor: pointer;
+ text-decoration: underline;
+ text-underline-offset: 2px;
+ }
+ }
+ }
+
+ &__actions {
+ display: flex;
+ flex-direction: row;
+ position: absolute;
+ bottom: 16px;
+ left: 16px;
+ right: 16px;
+ gap: 8px;
+ justify-content: space-between;
+
+ &__left {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ }
+
+ &__right {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+
+ Button {
+ padding: 8px 11px;
+ }
+ }
+ }
+ }
+ }
+
+ &__no-themes {
+ width: 100%;
+ min-height: 160px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 40px 24px;
+ background-color: rgba(globals.$border-color, 0.01);
+ cursor: pointer;
+ border: 1px dashed globals.$border-color;
+ border-radius: 12px;
+ gap: 12px;
+ transition: background-color 0.2s ease;
+
+ &:hover {
+ background-color: rgba(globals.$border-color, 0.03);
+ }
+
+ &__icon {
+ svg {
+ width: 32px;
+ height: 32px;
+ color: globals.$body-color;
+ opacity: 0.7;
+ }
+ }
+
+ &__text {
+ text-align: center;
+ max-width: 400px;
+ font-size: 14.5px;
+ line-height: 1.6;
+ font-weight: 400;
+ color: rgba(globals.$body-color, 0.85);
+ }
+ }
+}
diff --git a/src/renderer/src/pages/settings/aparence/settings-appearance.tsx b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx
new file mode 100644
index 00000000..d0ca8e2d
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/settings-appearance.tsx
@@ -0,0 +1,12 @@
+import "./settings-appearance.scss";
+import { ThemeActions } from "./index";
+
+export const SettingsAppearance = () => {
+ return (
+
+ );
+};
diff --git a/src/renderer/src/pages/settings/aparence/themes-manager.ts b/src/renderer/src/pages/settings/aparence/themes-manager.ts
new file mode 100644
index 00000000..0f2b82e7
--- /dev/null
+++ b/src/renderer/src/pages/settings/aparence/themes-manager.ts
@@ -0,0 +1,19 @@
+export interface Theme {
+ id: string;
+ name: string;
+ isActive: boolean;
+ description: string;
+ author: string | null;
+ authorId: string | null;
+ version: string;
+ code: string;
+ colors: {
+ accent: string;
+ surface: string;
+ background: string;
+ optional1?: string;
+ optional2?: string;
+ };
+}
+
+export class ThemesManager {}
diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx
index 75989aed..6bc5c157 100644
--- a/src/renderer/src/pages/settings/settings.tsx
+++ b/src/renderer/src/pages/settings/settings.tsx
@@ -12,6 +12,7 @@ import { SettingsAccount } from "./settings-account";
import { useUserDetails } from "@renderer/hooks";
import { useMemo } from "react";
import "./settings.scss";
+import { SettingsAppearance } from "./aparence/settings-appearance";
export default function Settings() {
const { t } = useTranslation("settings");
@@ -22,6 +23,7 @@ export default function Settings() {
t("general"),
t("behavior"),
t("download_sources"),
+ t("appearance"),
"Real-Debrid",
];
@@ -47,6 +49,10 @@ export default function Settings() {
}
if (currentCategoryIndex === 3) {
+ return ;
+ }
+
+ if (currentCategoryIndex === 4) {
return ;
}