mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-25 20:11:52 +01:00
Fix sticky sidebar + new design for app information + gorgegous new dropdown + bunch of small bug fixes + fix encryption not supporting utf8
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
8cdedbfca6
commit
7bc3bb1416
@ -32,7 +32,7 @@
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-i18next": "^12.1.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-stickynode": "^4.1.0",
|
||||
"react-sticky-el": "^2.1.0",
|
||||
"react-use": "^17.4.0",
|
||||
"slugify": "^1.6.6",
|
||||
"subsrt-ts": "^2.1.1",
|
||||
|
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@ -95,9 +95,9 @@ dependencies:
|
||||
react-router-dom:
|
||||
specifier: ^5.2.0
|
||||
version: 5.3.4(react@17.0.2)
|
||||
react-stickynode:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(react-dom@17.0.2)(react@17.0.2)
|
||||
react-sticky-el:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0(react-dom@17.0.2)(react@17.0.2)
|
||||
react-use:
|
||||
specifier: ^17.4.0
|
||||
version: 17.4.0(react-dom@17.0.2)(react@17.0.2)
|
||||
@ -3657,10 +3657,6 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/eventemitter3@3.1.2:
|
||||
resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==}
|
||||
dev: false
|
||||
|
||||
/fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
@ -4595,6 +4591,7 @@ packages:
|
||||
|
||||
/lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: true
|
||||
|
||||
/loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
@ -4994,10 +4991,6 @@ packages:
|
||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||
dev: true
|
||||
|
||||
/performance-now@2.1.0:
|
||||
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
|
||||
dev: false
|
||||
|
||||
/picocolors@1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
dev: true
|
||||
@ -5165,12 +5158,6 @@ packages:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
dev: true
|
||||
|
||||
/raf@3.4.1:
|
||||
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
|
||||
dependencies:
|
||||
performance-now: 2.1.0
|
||||
dev: false
|
||||
|
||||
/randombytes@2.1.0:
|
||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||
dependencies:
|
||||
@ -5286,19 +5273,14 @@ packages:
|
||||
tiny-warning: 1.0.3
|
||||
dev: false
|
||||
|
||||
/react-stickynode@4.1.0(react-dom@17.0.2)(react@17.0.2):
|
||||
resolution: {integrity: sha512-zylWgfad75jLfh/gYIayDcDWIDwO4weZrsZqDpjZ/axhF06zRjdCWFBgUr33Pvv2+htKWqPSFksWTyB6aMQ1ZQ==}
|
||||
/react-sticky-el@2.1.0(react-dom@17.0.2)(react@17.0.2):
|
||||
resolution: {integrity: sha512-oo+a2GedF4QMfCfm20e9gD+RuuQp/ngvwGMUXAXpST+h4WnmKhuv7x6MQ4X/e3AHiLYgE0zDyJo1Pzo8m51KpA==}
|
||||
peerDependencies:
|
||||
react: ^0.14.2 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^0.14.2 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react: '>=16.3.0'
|
||||
react-dom: '>=16.3.0'
|
||||
dependencies:
|
||||
classnames: 2.3.2
|
||||
core-js: 3.32.1
|
||||
prop-types: 15.8.1
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2(react@17.0.2)
|
||||
shallowequal: 1.1.0
|
||||
subscribe-ui-event: 2.0.7
|
||||
dev: false
|
||||
|
||||
/react-universal-interface@0.6.2(react@17.0.2)(tslib@2.6.2):
|
||||
@ -5796,14 +5778,6 @@ packages:
|
||||
resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==}
|
||||
dev: false
|
||||
|
||||
/subscribe-ui-event@2.0.7:
|
||||
resolution: {integrity: sha512-Acrtf9XXl6lpyHAWYeRD1xTPUQHDERfL4GHeNuYAtZMc4Z8Us2iDBP0Fn3xiRvkQ1FO+hx+qRLmPEwiZxp7FDQ==}
|
||||
dependencies:
|
||||
eventemitter3: 3.1.2
|
||||
lodash: 4.17.21
|
||||
raf: 3.4.1
|
||||
dev: false
|
||||
|
||||
/subsrt-ts@2.1.1:
|
||||
resolution: {integrity: sha512-E+GiLNG4L82yRDswd4ys34OUfJLNN6ZBdtefE7ftn/WJchjvyJ9dNXuXYviNglrqiCqNyayGGUZE3v9aL7zIYg==}
|
||||
hasBin: true
|
||||
|
@ -70,7 +70,7 @@ export function base64ToBuffer(data: string) {
|
||||
return forge.util.binary.base64.decode(data);
|
||||
}
|
||||
|
||||
export function base64ToStringBugger(data: string) {
|
||||
export function base64ToStringBuffer(data: string) {
|
||||
return forge.util.createBuffer(base64ToBuffer(data));
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ export async function encryptData(data: string, secret: Uint8Array) {
|
||||
iv,
|
||||
tagLength: 128,
|
||||
});
|
||||
cipher.update(forge.util.createBuffer(data));
|
||||
cipher.update(forge.util.createBuffer(data, "utf8"));
|
||||
cipher.finish();
|
||||
|
||||
const encryptedData = cipher.output;
|
||||
@ -118,11 +118,11 @@ export function decryptData(data: string, secret: Uint8Array) {
|
||||
forge.util.createBuffer(secret)
|
||||
);
|
||||
decipher.start({
|
||||
iv: base64ToStringBugger(iv),
|
||||
tag: base64ToStringBugger(tag),
|
||||
iv: base64ToStringBuffer(iv),
|
||||
tag: base64ToStringBuffer(tag),
|
||||
tagLength: 128,
|
||||
});
|
||||
decipher.update(base64ToStringBugger(encryptedData));
|
||||
decipher.update(base64ToStringBuffer(encryptedData));
|
||||
const pass = decipher.finish();
|
||||
|
||||
if (!pass) throw new Error("Error decrypting data");
|
||||
|
@ -1,5 +1,7 @@
|
||||
import classNames from "classnames";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { UserIcon } from "@/components/UserIcon";
|
||||
import { AccountProfile } from "@/pages/parts/auth/AccountCreatePart";
|
||||
@ -42,16 +44,40 @@ export function UserAvatar(props: {
|
||||
sizeClass?: string;
|
||||
iconClass?: string;
|
||||
bottom?: React.ReactNode;
|
||||
withName?: boolean;
|
||||
}) {
|
||||
const auth = useAuthStore();
|
||||
if (!auth.account) return null;
|
||||
|
||||
const bufferSeed = useMemo(
|
||||
() =>
|
||||
auth.account && auth.account.seed
|
||||
? base64ToBuffer(auth.account.seed)
|
||||
: null,
|
||||
[auth]
|
||||
);
|
||||
|
||||
if (!auth.account || auth.account === null) return null;
|
||||
|
||||
const deviceName = bufferSeed
|
||||
? decryptData(auth.account.deviceName, bufferSeed)
|
||||
: "...";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Avatar
|
||||
profile={auth.account.profile}
|
||||
sizeClass={props.sizeClass ?? "w-[2rem] h-[2rem]"}
|
||||
iconClass={props.iconClass}
|
||||
bottom={props.bottom}
|
||||
/>
|
||||
{props.withName && bufferSeed ? (
|
||||
<span>
|
||||
{deviceName.length >= 20
|
||||
? `${deviceName.slice(0, 20 - 1)}…`
|
||||
: deviceName}
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -107,12 +107,19 @@ export function LinksDropdown(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="relative is-dropdown">
|
||||
<div
|
||||
className="cursor-pointer tabbable rounded-full"
|
||||
className="cursor-pointer tabbable rounded-full flex gap-2 text-white items-center py-2 px-3 bg-pill-background bg-opacity-50"
|
||||
tabIndex={0}
|
||||
onClick={toggleOpen}
|
||||
onKeyUp={(evt) => evt.key === "Enter" && toggleOpen()}
|
||||
>
|
||||
{props.children}
|
||||
<Icon
|
||||
className={classNames(
|
||||
"text-xl transition-transform duration-100",
|
||||
open ? "rotate-180" : ""
|
||||
)}
|
||||
icon={Icons.CHEVRON_DOWN}
|
||||
/>
|
||||
</div>
|
||||
<Transition animation="slide-down" show={open}>
|
||||
<div className="rounded-lg absolute w-64 bg-dropdown-altBackground top-full mt-3 right-0">
|
||||
|
@ -101,7 +101,7 @@ export function Navigation(props: NavigationProps) {
|
||||
</div>
|
||||
<div className="relative">
|
||||
<LinksDropdown>
|
||||
{loggedIn ? <UserAvatar /> : <NoUserAvatar />}
|
||||
{loggedIn ? <UserAvatar withName /> : <NoUserAvatar />}
|
||||
</LinksDropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,7 +10,7 @@ export function Heading1(props: TextProps) {
|
||||
return (
|
||||
<h1
|
||||
className={[
|
||||
"text-5xl font-bold text-white mb-9",
|
||||
"text-3xl lg:text-5xl font-bold text-white mb-9",
|
||||
props.border ? borderClass : null,
|
||||
props.className ?? "",
|
||||
].join(" ")}
|
||||
@ -24,7 +24,7 @@ export function Heading2(props: TextProps) {
|
||||
return (
|
||||
<h2
|
||||
className={[
|
||||
"text-3xl font-bold text-white mt-20 mb-9",
|
||||
"text-xl lg:text-3xl font-bold text-white mt-20 mb-9",
|
||||
props.border ? borderClass : null,
|
||||
props.className ?? "",
|
||||
].join(" ")}
|
||||
@ -38,7 +38,7 @@ export function Heading3(props: TextProps) {
|
||||
return (
|
||||
<h2
|
||||
className={[
|
||||
"text-xl font-bold text-white mb-3",
|
||||
"text-lg lg:text-xl font-bold text-white mb-3",
|
||||
props.border ? borderClass : null,
|
||||
props.className ?? "",
|
||||
].join(" ")}
|
||||
|
@ -164,7 +164,8 @@ export function useAuth() {
|
||||
const anyError: any = err;
|
||||
if (
|
||||
anyError?.response?.status === 401 ||
|
||||
anyError?.response?.status === 403
|
||||
anyError?.response?.status === 403 ||
|
||||
anyError?.response?.status === 400
|
||||
) {
|
||||
await logout();
|
||||
return;
|
||||
|
@ -87,7 +87,9 @@ function AuthWrapper() {
|
||||
if (status.error)
|
||||
return (
|
||||
<ErrorScreen showResetButton={backendUrl !== userBackendUrl}>
|
||||
Failed to fetch user data. Try resetting the backend URL.
|
||||
{backendUrl !== userBackendUrl
|
||||
? "Failed to fetch user data. Try resetting the backend URL"
|
||||
: "Failed to fetch user data."}
|
||||
</ErrorScreen>
|
||||
);
|
||||
return <App />;
|
||||
|
@ -42,7 +42,7 @@ function SettingsLayout(props: { children: React.ReactNode }) {
|
||||
<div
|
||||
className={classNames(
|
||||
"grid gap-12",
|
||||
isMobile ? "grid-cols-1" : "lg:grid-cols-[310px,1fr]"
|
||||
isMobile ? "grid-cols-1" : "lg:grid-cols-[280px,1fr]"
|
||||
)}
|
||||
>
|
||||
<SidebarPart />
|
||||
@ -240,16 +240,24 @@ export function SettingsPage() {
|
||||
</div>
|
||||
</SettingsLayout>
|
||||
<div
|
||||
className={`bg-settings-saveBar-background border-t border-settings-card-border/50 py-4 transition-opacity w-full fixed bottom-0 flex justify-between px-8 items-center ${
|
||||
className={`bg-settings-saveBar-background border-t border-settings-card-border/50 py-4 transition-opacity w-full fixed bottom-0 flex justify-between flex-col md:flex-row px-8 items-start md:items-center gap-3 ${
|
||||
state.changed ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
<p className="text-type-danger">You have unsaved changes</p>
|
||||
<div className="space-x-6">
|
||||
<Button theme="secondary" onClick={state.reset}>
|
||||
<div className="space-x-3 w-full md:w-auto flex">
|
||||
<Button
|
||||
className="w-full md:w-auto"
|
||||
theme="secondary"
|
||||
onClick={state.reset}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button theme="purple" onClick={saveChanges}>
|
||||
<Button
|
||||
className="w-full md:w-auto"
|
||||
theme="purple"
|
||||
onClick={saveChanges}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import Sticky from "react-stickynode";
|
||||
import Sticky from "react-sticky-el";
|
||||
|
||||
import { ThinContainer } from "@/components/layout/ThinContainer";
|
||||
import { SearchBarInput } from "@/components/SearchBar";
|
||||
@ -19,10 +19,9 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
|
||||
const [, setShowBg] = useState(false);
|
||||
const bannerSize = useBannerSize();
|
||||
const stickStateChanged = useCallback(
|
||||
({ status }: Sticky.Status) => {
|
||||
const val = status === Sticky.STATUS_FIXED;
|
||||
setShowBg(val);
|
||||
setIsSticky(val);
|
||||
(isFixed) => {
|
||||
setShowBg(isFixed);
|
||||
setIsSticky(isFixed);
|
||||
},
|
||||
[setShowBg, setIsSticky]
|
||||
);
|
||||
@ -40,11 +39,13 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
|
||||
<div className="relative z-10 mb-16">
|
||||
<HeroTitle className="mx-auto max-w-xs">{title}</HeroTitle>
|
||||
</div>
|
||||
<div className="relative z-30">
|
||||
<div className="relative h-20 z-30">
|
||||
<Sticky
|
||||
enabled
|
||||
top={16 + bannerSize}
|
||||
onStateChange={stickStateChanged}
|
||||
topOffset={-16 + bannerSize}
|
||||
stickyStyle={{
|
||||
paddingTop: `${16 + bannerSize}px`,
|
||||
}}
|
||||
onFixedToggle={stickStateChanged}
|
||||
>
|
||||
<SearchBarInput
|
||||
onChange={setSearch}
|
||||
|
@ -11,7 +11,7 @@ import { Menu } from "@/components/player/internals/ContextMenu";
|
||||
import { CaptionCue } from "@/components/player/Player";
|
||||
import { Transition } from "@/components/Transition";
|
||||
import { Heading1 } from "@/components/utils/Text";
|
||||
import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles";
|
||||
import { SubtitleStyling } from "@/stores/subtitles";
|
||||
|
||||
export function CaptionPreview(props: {
|
||||
fullscreen?: boolean;
|
||||
@ -24,7 +24,7 @@ export function CaptionPreview(props: {
|
||||
className={classNames({
|
||||
"pointer-events-none overflow-hidden w-full rounded": true,
|
||||
"aspect-video relative": !props.fullscreen,
|
||||
"fixed inset-0 z-50": props.fullscreen,
|
||||
"fixed inset-0 z-[60]": props.fullscreen,
|
||||
})}
|
||||
>
|
||||
<Transition animation="fade" show={props.show}>
|
||||
@ -71,7 +71,7 @@ export function CaptionsPart(props: {
|
||||
return (
|
||||
<div>
|
||||
<Heading1 border>Captions</Heading1>
|
||||
<div className="grid grid-cols-[1fr,356px] gap-8">
|
||||
<div className="grid md:grid-cols-[1fr,356px] gap-8">
|
||||
<div className="space-y-6">
|
||||
<CaptionSetting
|
||||
label="Background opacity"
|
||||
|
@ -44,7 +44,7 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
|
||||
|
||||
return (
|
||||
<SettingsCard>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex justify-between items-center gap-4">
|
||||
<div className="my-3">
|
||||
<p className="text-white font-bold mb-3">Use custom proxy workers</p>
|
||||
<p className="max-w-[20rem] font-medium">
|
||||
@ -103,7 +103,7 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
|
||||
function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
|
||||
return (
|
||||
<SettingsCard>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex justify-between items-center gap-4">
|
||||
<div className="my-3">
|
||||
<p className="text-white font-bold mb-3">Custom server</p>
|
||||
<p className="max-w-[20rem] font-medium">
|
||||
|
@ -1,6 +1,5 @@
|
||||
import classNames from "classnames";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Sticky from "react-stickynode";
|
||||
import Sticky from "react-sticky-el";
|
||||
import { useAsync } from "react-use";
|
||||
|
||||
import { getBackendMeta } from "@/backend/accounts/meta";
|
||||
@ -10,34 +9,25 @@ import { Divider } from "@/components/utils/Divider";
|
||||
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||
import { conf } from "@/setup/config";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
function BackendUrl(props: { url: string }) {
|
||||
const url = props.url.replace(/https?:\/\//, "");
|
||||
const rem = 16;
|
||||
|
||||
function SecureBadge(props: { url: string }) {
|
||||
const secure = props.url.startsWith("https://");
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
title={secure ? "Secure" : "Insecure"}
|
||||
className={classNames(
|
||||
"w-5 min-w-[1.25rem] h-5 rounded flex justify-center items-center",
|
||||
secure ? "bg-emerald-200/30 text-white" : "bg-red-600/20 text-white"
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
className="opacity-50"
|
||||
icon={secure ? Icons.LOCK : Icons.UNLOCK}
|
||||
/>
|
||||
</div>
|
||||
{url}
|
||||
<div className="flex items-center gap-1 -mx-1 ml-3 px-1 rounded bg-largeCard-background font-bold">
|
||||
<Icon icon={secure ? Icons.LOCK : Icons.UNLOCK} />
|
||||
Secure
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SidebarPart() {
|
||||
const { isMobile } = useIsMobile();
|
||||
const { account } = useAuthStore();
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const hostname = location.hostname;
|
||||
const rem = 16;
|
||||
const [activeLink, setActiveLink] = useState("");
|
||||
|
||||
const settingLinks = [
|
||||
@ -54,7 +44,6 @@ export function SidebarPart() {
|
||||
return getBackendMeta(backendUrl);
|
||||
}, [backendUrl]);
|
||||
|
||||
// TODO loading/error state for backend
|
||||
useEffect(() => {
|
||||
function recheck() {
|
||||
const windowHeight =
|
||||
@ -97,11 +86,13 @@ export function SidebarPart() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="text-settings-sidebar-type-inactive sidebar-boundary">
|
||||
<Sticky
|
||||
enabled={!isMobile}
|
||||
top={10 * rem} // 10rem
|
||||
className="text-settings-sidebar-type-inactive"
|
||||
topOffset={-6 * rem}
|
||||
stickyClassName="pt-[6rem]"
|
||||
disabled={isMobile}
|
||||
hideOnBoundaryHit={false}
|
||||
boundaryElement=".sidebar-boundary"
|
||||
>
|
||||
<div className="hidden lg:block">
|
||||
<SidebarSection title="Settings">
|
||||
@ -119,28 +110,56 @@ export function SidebarPart() {
|
||||
<Divider />
|
||||
</div>
|
||||
<SidebarSection className="text-sm" title="App information">
|
||||
<div className="flex justify-between items-center space-x-3">
|
||||
<span>Version</span>
|
||||
<span>{conf().APP_VERSION}</span>
|
||||
<div className="px-3 py-3.5 rounded-lg bg-largeCard-background bg-opacity-50 grid grid-cols-2 gap-4">
|
||||
{/* Hostname */}
|
||||
<div className="col-span-2 space-y-1">
|
||||
<p className="text-type-dimmed font-medium">Hostname</p>
|
||||
<p className="text-white">{hostname}</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-center space-x-3">
|
||||
<span>Domain</span>
|
||||
<span className="text-right">{hostname}</span>
|
||||
|
||||
{/* Backend URL */}
|
||||
<div className="col-span-2 space-y-1">
|
||||
<p className="text-type-dimmed font-medium flex items-center">
|
||||
Backend URL
|
||||
<SecureBadge url={backendUrl} />
|
||||
</p>
|
||||
<p className="text-white">
|
||||
{backendUrl.replace(/https?:\/\//, "")}
|
||||
</p>
|
||||
</div>
|
||||
{backendMeta.value ? (
|
||||
<>
|
||||
<div className="flex justify-between items-center space-x-3">
|
||||
<span>Backend Version</span>
|
||||
<span>{backendMeta.value.version}</span>
|
||||
|
||||
{/* User ID */}
|
||||
<div className="col-span-2 space-y-1">
|
||||
<p className="text-type-dimmed font-medium">User ID</p>
|
||||
<p className="text-white">{account?.userId ?? "Not logged in"}</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-center space-x-3">
|
||||
<span className="whitespace-nowrap">Backend URL</span>
|
||||
<span className="text-right">
|
||||
<BackendUrl url={backendUrl} />
|
||||
</span>
|
||||
|
||||
{/* App version */}
|
||||
<div className="col-span-1 space-y-1">
|
||||
<p className="text-type-dimmed font-medium">App version</p>
|
||||
<p className="text-type-dimmed px-2 py-1 rounded bg-settings-sidebar-badge inline-block">
|
||||
{conf().APP_VERSION}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
|
||||
{/* Backend version */}
|
||||
<div className="col-span-1 space-y-1">
|
||||
<p className="text-type-dimmed font-medium">Backend version</p>
|
||||
<p className="text-type-dimmed px-2 py-1 rounded bg-settings-sidebar-badge inline-flex items-center gap-1">
|
||||
{backendMeta.error ? (
|
||||
<Icon
|
||||
icon={Icons.WARNING}
|
||||
className="text-type-danger text-base"
|
||||
/>
|
||||
) : null}
|
||||
{backendMeta.loading ? (
|
||||
<div className="h-4 w-12 bg-type-dimmed/20 rounded" />
|
||||
) : (
|
||||
backendMeta?.value?.version || "Unknown"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarSection>
|
||||
</Sticky>
|
||||
</div>
|
||||
|
@ -120,7 +120,7 @@ export function ThemePart(props: {
|
||||
return (
|
||||
<div>
|
||||
<Heading1 border>Appearance</Heading1>
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-6 max-w-[700px]">
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-6 max-w-[700px]">
|
||||
{/* default theme */}
|
||||
<ThemePreview
|
||||
name="Default"
|
||||
|
@ -120,6 +120,7 @@ export const defaultTheme = {
|
||||
settings: {
|
||||
sidebar: {
|
||||
activeLink: "#171728",
|
||||
badge: "#0A0A12",
|
||||
|
||||
type: {
|
||||
secondary: "#4B395F",
|
||||
|
Loading…
Reference in New Issue
Block a user