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 ( +
+

Appearance

+ + +
+ ); +}; 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 ; }