mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 21:31:43 +01:00
allow bakery url to be changed without reload
This commit is contained in:
parent
dabf5c5061
commit
05942d7f68
70
src/app.tsx
70
src/app.tsx
@ -2,13 +2,15 @@ import { lazy, Suspense } from "react";
|
||||
import { createBrowserRouter, Outlet, RouterProvider, ScrollRestoration } from "react-router-dom";
|
||||
import { Spinner } from "@chakra-ui/react";
|
||||
|
||||
import GlobalStyles from "./styles";
|
||||
|
||||
import { ErrorBoundary } from "./components/error-boundary";
|
||||
import AppLayout from "./components/layout";
|
||||
import DrawerSubViewProvider from "./providers/drawer-sub-view-provider";
|
||||
import useSetColorMode from "./hooks/use-set-color-mode";
|
||||
import { RouteProviders } from "./providers/route";
|
||||
import RequireCurrentAccount from "./components/router/require-current-account";
|
||||
import GlobalStyles from "./styles";
|
||||
import RequireBakery from "./components/router/require-bakery";
|
||||
|
||||
import HomeView from "./views/home/index";
|
||||
const DiscoveryHomeView = lazy(() => import("./views/discovery/index"));
|
||||
@ -99,7 +101,6 @@ import UserMediaPostsTab from "./views/user/media-posts";
|
||||
import NewView from "./views/new";
|
||||
import NewNoteView from "./views/new/note";
|
||||
import NewMediaPostView from "./views/new/media";
|
||||
import ConnectionStatus from "./components/bakery/connection-status";
|
||||
const TracksView = lazy(() => import("./views/tracks"));
|
||||
const UserTracksTab = lazy(() => import("./views/user/tracks"));
|
||||
const UserVideosTab = lazy(() => import("./views/user/videos"));
|
||||
@ -146,11 +147,7 @@ const PodcastsHomeView = lazy(() => import("./views/podcasts"));
|
||||
const PodcastView = lazy(() => import("./views/podcasts/podcast"));
|
||||
const EpisodeView = lazy(() => import("./views/podcasts/podcast/episode"));
|
||||
|
||||
// bakery views
|
||||
const ConnectView = lazy(() => import("./views/bakery/connect"));
|
||||
const RequireBakery = lazy(() => import("./components/router/require-bakery"));
|
||||
const BakerySetupView = lazy(() => import("./views/bakery/setup"));
|
||||
const BakeryAuthView = lazy(() => import("./views/bakery/connect/auth"));
|
||||
const BakerySetupView = lazy(() => import("./views/settings/bakery/setup"));
|
||||
const RequireBakeryAuth = lazy(() => import("./components/router/require-bakery-auth"));
|
||||
|
||||
// setting views
|
||||
@ -162,6 +159,8 @@ import PrivacySettings from "./views/settings/privacy";
|
||||
import PostSettings from "./views/settings/post";
|
||||
import AccountSettings from "./views/settings/accounts";
|
||||
import MediaServersView from "./views/settings/media-servers";
|
||||
const BakeryConnectView = lazy(() => import("./views/settings/bakery/connect"));
|
||||
const BakeryAuthView = lazy(() => import("./views/settings/bakery/connect/auth"));
|
||||
const NotificationSettingsView = lazy(() => import("./views/settings/bakery/notifications"));
|
||||
const BakeryGeneralSettingsView = lazy(() => import("./views/settings/bakery/general-settings"));
|
||||
const BakeryNetworkSettingsView = lazy(() => import("./views/settings/bakery/network"));
|
||||
@ -298,10 +297,21 @@ const router = createBrowserRouter([
|
||||
{ path: "privacy", element: <PrivacySettings /> },
|
||||
{ path: "lightning", element: <LightningSettings /> },
|
||||
{ path: "performance", element: <PerformanceSettings /> },
|
||||
|
||||
{ path: "bakery/connect", element: <BakeryConnectView /> },
|
||||
{
|
||||
path: "bakery",
|
||||
element: (
|
||||
<RequireBakery>
|
||||
<Outlet />
|
||||
</RequireBakery>
|
||||
),
|
||||
children: [
|
||||
{ path: "", element: <BakeryGeneralSettingsView /> },
|
||||
{
|
||||
path: "auth",
|
||||
element: <BakeryAuthView />,
|
||||
},
|
||||
{ path: "notifications", element: <NotificationSettingsView /> },
|
||||
{
|
||||
path: "network",
|
||||
@ -562,52 +572,6 @@ const router = createBrowserRouter([
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/bakery",
|
||||
children: [
|
||||
{
|
||||
path: "connect",
|
||||
children: [
|
||||
{ path: "", element: <ConnectView /> },
|
||||
{
|
||||
path: "auth",
|
||||
element: (
|
||||
<RequireBakery>
|
||||
<ConnectionStatus />
|
||||
<BakeryAuthView />
|
||||
</RequireBakery>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "setup",
|
||||
element: <BakerySetupView />,
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
element: (
|
||||
<RequireBakery>
|
||||
<RequireCurrentAccount>
|
||||
<RequireBakeryAuth>
|
||||
<AppLayout />
|
||||
</RequireBakeryAuth>
|
||||
</RequireCurrentAccount>
|
||||
</RequireBakery>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
path: "search",
|
||||
element: <SearchView />,
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
element: <HomeView />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
export const App = () => (
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Alert, AlertDescription, AlertTitle, Button, Flex, Text } from "@chakra-ui/react";
|
||||
import { Link as RouterLink, useLocation } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import WifiOff from "../icons/wifi-off";
|
||||
import bakery from "../../services/bakery";
|
||||
import useReconnectAction from "../../hooks/use-reconnect-action";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { bakery$ } from "../../services/bakery";
|
||||
|
||||
function ReconnectPrompt() {
|
||||
const location = useLocation();
|
||||
@ -32,6 +32,7 @@ function ReconnectPrompt() {
|
||||
}
|
||||
|
||||
export default function ConnectionStatus() {
|
||||
const bakery = useObservable(bakery$);
|
||||
const connected = useObservable(bakery?.connectedSub);
|
||||
|
||||
if (!bakery || connected) return null;
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { PropsWithChildren, useState } from "react";
|
||||
import { Code, Flex, FormControl, FormLabel } from "@chakra-ui/react";
|
||||
import { ButtonGroup, Code, Flex, FormControl, FormLabel, IconButton } from "@chakra-ui/react";
|
||||
|
||||
import QrCodeSvg from "../qr-code/qr-code-svg";
|
||||
import TextButton from "./text-button";
|
||||
import { CopyIconButton } from "../copy-icon-button";
|
||||
import { QrCodeIcon } from "../icons";
|
||||
|
||||
export default function PanelItemString({
|
||||
children,
|
||||
@ -20,16 +21,20 @@ export default function PanelItemString({
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<Code bg="none" userSelect="all" fontFamily="monospace" mr="auto" maxW="full">
|
||||
{value}
|
||||
</Code>
|
||||
<Flex gap="2" w="full" alignItems="center" wrap="wrap">
|
||||
<CopyIconButton variant="link" value={value} fontFamily="monospace" aria-label="Copy value" />
|
||||
{qr && (
|
||||
<TextButton variant="link" onClick={() => setShowQR((v) => !v)}>
|
||||
[qr]
|
||||
</TextButton>
|
||||
)}
|
||||
<Flex gap="2">
|
||||
<Code bg="none" userSelect="all" fontFamily="monospace" maxW="full" whiteSpace="pre" overflow="auto" p="1">
|
||||
{value}
|
||||
</Code>
|
||||
<ButtonGroup size="sm" variant="ghost">
|
||||
<CopyIconButton value={value} fontFamily="monospace" aria-label="Copy value" />
|
||||
{qr && (
|
||||
<IconButton
|
||||
onClick={() => setShowQR((v) => !v)}
|
||||
icon={<QrCodeIcon boxSize={5} />}
|
||||
aria-label="show qrcode"
|
||||
/>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
{showQR && <QrCodeSvg content={value} style={{ maxWidth: "3in", marginTop: "1em" }} />}
|
||||
{children}
|
||||
|
@ -14,17 +14,18 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { IconButton } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { UserAvatar } from "../../user/user-avatar";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import UserName from "../../user/user-name";
|
||||
import UserDnsIdentity from "../../user/user-dns-identity";
|
||||
// import ColorModeButton from '../../color-mode-button';
|
||||
import bakery from "../../../services/bakery";
|
||||
import { DirectMessagesIcon, RelayIcon, SearchIcon, SettingsIcon } from "../../icons";
|
||||
import { bakery$ } from "../../../services/bakery";
|
||||
|
||||
export default function DrawerNav({ isOpen, onClose, ...props }: Omit<ModalProps, "children">) {
|
||||
const account = useCurrentAccount();
|
||||
const bakery = useObservable(bakery$);
|
||||
|
||||
return (
|
||||
<Drawer placement="left" onClose={onClose} isOpen={isOpen} {...props}>
|
||||
|
@ -20,6 +20,7 @@ export default function SimpleView({
|
||||
<Flex
|
||||
direction="column"
|
||||
overflowY="auto"
|
||||
overflowX="hidden"
|
||||
px={flush ? 0 : "4"}
|
||||
pt={flush ? 0 : "4"}
|
||||
pb={flush ? 0 : "max(1rem, var(--safe-bottom))"}
|
||||
|
@ -3,11 +3,12 @@ import { Button, Flex, Heading, Spinner } from "@chakra-ui/react";
|
||||
import { To, useLocation, Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import bakery from "../../services/bakery";
|
||||
import { useSigningContext } from "../../providers/global/signing-provider";
|
||||
import { bakery$ } from "../../services/bakery";
|
||||
|
||||
export default function RequireBakeryAuth({ children }: PropsWithChildren) {
|
||||
const location = useLocation();
|
||||
const bakery = useObservable(bakery$);
|
||||
const isFirstAuthentication = useObservable(bakery?.isFirstAuthentication);
|
||||
const connected = useObservable(bakery?.connectedSub);
|
||||
const authenticated = useObservable(bakery?.authenticated);
|
||||
|
@ -4,11 +4,12 @@ import { Navigate, To, useLocation } from "react-router-dom";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import bakery from "../../services/bakery";
|
||||
import useReconnectAction from "../../hooks/use-reconnect-action";
|
||||
import { bakery$ } from "../../services/bakery";
|
||||
|
||||
function InitialConnectionOverlay() {
|
||||
const location = useLocation();
|
||||
const bakery = useObservable(bakery$);
|
||||
|
||||
const { error } = useReconnectAction();
|
||||
|
||||
@ -24,7 +25,7 @@ function InitialConnectionOverlay() {
|
||||
variant="link"
|
||||
mt="4"
|
||||
as={RouterLink}
|
||||
to="/bakery/connect"
|
||||
to="/settings/bakery/connect"
|
||||
replace
|
||||
state={{ back: (location.state?.back ?? location) satisfies To }}
|
||||
>
|
||||
@ -36,12 +37,19 @@ function InitialConnectionOverlay() {
|
||||
|
||||
export default function RequireBakery({ children }: PropsWithChildren & { requireConnection?: boolean }) {
|
||||
const location = useLocation();
|
||||
const bakery = useObservable(bakery$);
|
||||
const connected = useObservable(bakery?.connectedSub);
|
||||
const isFirstConnection = useObservable(bakery?.isFirstConnection);
|
||||
|
||||
// if there is no node connection, setup a connection
|
||||
if (!bakery)
|
||||
return <Navigate to="/bakery/connect" replace state={{ back: (location.state?.back ?? location) satisfies To }} />;
|
||||
return (
|
||||
<Navigate
|
||||
to="/settings/bakery/connect"
|
||||
replace
|
||||
state={{ back: (location.state?.back ?? location) satisfies To }}
|
||||
/>
|
||||
);
|
||||
|
||||
if (bakery && isFirstConnection && connected === false) return <InitialConnectionOverlay />;
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import bakery from "../services/bakery";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { bakery$ } from "../services/bakery";
|
||||
|
||||
const steps = [2, 2, 3, 3, 5, 5, 10, 20, 30, 60];
|
||||
|
||||
/** @deprecated */
|
||||
export default function useReconnectAction() {
|
||||
const bakery = useObservable(bakery$);
|
||||
const [tries, setTries] = useState(0);
|
||||
const [count, setCount] = useState(steps[0]);
|
||||
const [error, setError] = useState<Error>();
|
||||
|
@ -1,31 +1,34 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ReportArguments } from '@satellite-earth/core/types';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { ReportArguments } from "@satellite-earth/core/types";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import reportManagerService from '../services/reports';
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import reportManager$ from "../services/reports";
|
||||
|
||||
export default function useReport<T extends keyof ReportArguments>(type: T, id?: string, args?: ReportArguments[T]) {
|
||||
const [hookId] = useState(() => nanoid());
|
||||
const argsKey = JSON.stringify(args);
|
||||
const [hookId] = useState(() => nanoid());
|
||||
const argsKey = JSON.stringify(args);
|
||||
|
||||
const report = useMemo(() => {
|
||||
if (id && args) return reportManagerService?.getOrCreateReport(type, id, args);
|
||||
}, [type, id, argsKey]);
|
||||
const reportManager = useObservable(reportManager$);
|
||||
|
||||
useEffect(() => {
|
||||
if (args && report) {
|
||||
// @ts-expect-error
|
||||
report.setArgs(args);
|
||||
report.fireThrottle();
|
||||
}
|
||||
}, [argsKey, report]);
|
||||
const report = useMemo(() => {
|
||||
if (id && args) return reportManager?.getOrCreateReport(type, id, args);
|
||||
}, [type, id, argsKey, reportManager]);
|
||||
|
||||
useEffect(() => {
|
||||
if (report) {
|
||||
reportManagerService?.addDependency(hookId, report);
|
||||
return () => reportManagerService?.removeDependency(hookId, report);
|
||||
}
|
||||
}, [report]);
|
||||
useEffect(() => {
|
||||
if (args && report) {
|
||||
// @ts-expect-error
|
||||
report.setArgs(args);
|
||||
report.fireThrottle();
|
||||
}
|
||||
}, [argsKey, report]);
|
||||
|
||||
return report;
|
||||
useEffect(() => {
|
||||
if (report) {
|
||||
reportManager?.addDependency(hookId, report);
|
||||
return () => reportManager?.removeDependency(hookId, report);
|
||||
}
|
||||
}, [report, reportManager]);
|
||||
|
||||
return report;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { BehaviorSubject, filter, mergeMap } from "rxjs";
|
||||
|
||||
import { logger } from "../helpers/debug";
|
||||
import BakeryConnection from "../classes/bakery/bakery-connection";
|
||||
import BakeryControlApi from "../classes/bakery/control-api";
|
||||
@ -10,61 +12,67 @@ const log = logger.extend("bakery");
|
||||
|
||||
export function setBakeryURL(url: string) {
|
||||
localSettings.bakeryURL.next(url);
|
||||
location.reload();
|
||||
}
|
||||
export function clearBakeryURL() {
|
||||
localSettings.bakeryURL.clear();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
let bakery: BakeryConnection | null = null;
|
||||
export const bakery$ = new BehaviorSubject<BakeryConnection | null>(null);
|
||||
|
||||
localSettings.bakeryURL.subscribe((url) => {
|
||||
if (!URL.canParse(url)) return bakery$.next(null);
|
||||
|
||||
if (localSettings.bakeryURL.value) {
|
||||
try {
|
||||
log("Using URL from localStorage");
|
||||
bakery = new BakeryConnection(localSettings.bakeryURL.value);
|
||||
const bakery = new BakeryConnection(localSettings.bakeryURL.value);
|
||||
|
||||
// add the bakery to the relay pool and connect
|
||||
relayPoolService.relays.set(bakery.url, bakery);
|
||||
relayPoolService.requestConnect(bakery);
|
||||
|
||||
bakery$.next(bakery);
|
||||
} catch (err) {
|
||||
log("Failed to create bakery connection, clearing storage");
|
||||
localSettings.bakeryURL.clear();
|
||||
}
|
||||
} else {
|
||||
log("Unable to find private node URL");
|
||||
}
|
||||
});
|
||||
|
||||
if (bakery) {
|
||||
// add the bakery to the relay pool and connect
|
||||
relayPoolService.relays.set(bakery.url, bakery);
|
||||
relayPoolService.requestConnect(bakery);
|
||||
// automatically authenticate with bakery
|
||||
bakery$
|
||||
.pipe(
|
||||
filter((r) => r !== null),
|
||||
mergeMap((r) => r.onChallenge),
|
||||
)
|
||||
.subscribe(async () => {
|
||||
if (!bakery$.value) return;
|
||||
|
||||
const account = accountService.current.value;
|
||||
if (!account) return;
|
||||
|
||||
// automatically authenticate with bakery
|
||||
bakery.onChallenge.subscribe(async () => {
|
||||
try {
|
||||
const savedAuth = localStorage.getItem("personal-node-auth");
|
||||
if (savedAuth) {
|
||||
if (savedAuth === "nostr") {
|
||||
const account = accountService.current.value;
|
||||
if (!account) return;
|
||||
|
||||
await bakery.authenticate((draft) => signingService.requestSignature(draft, account));
|
||||
} else {
|
||||
await bakery.authenticate(savedAuth);
|
||||
}
|
||||
}
|
||||
await bakery$.value.authenticate((draft) => signingService.requestSignature(draft, account));
|
||||
} catch (err) {
|
||||
console.log("Failed to authenticate with bakery", err);
|
||||
localStorage.removeItem("personal-node-auth");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const controlApi = bakery ? new BakeryControlApi(bakery) : undefined;
|
||||
export const controlApi$ = new BehaviorSubject<BakeryControlApi | null>(null);
|
||||
|
||||
// create a control api for the bakery
|
||||
bakery$.subscribe((relay) => {
|
||||
if (!relay) return controlApi$.next(null);
|
||||
else controlApi$.next(new BakeryControlApi(relay));
|
||||
});
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-expect-error
|
||||
window.bakery = bakery;
|
||||
window.bakery = bakery$;
|
||||
// @ts-expect-error
|
||||
window.controlApi = controlApi;
|
||||
window.controlApi = controlApi$;
|
||||
}
|
||||
|
||||
export { controlApi };
|
||||
export default bakery;
|
||||
export function getControlApi() {
|
||||
return controlApi$.value;
|
||||
}
|
||||
export function getBakery() {
|
||||
return bakery$.value;
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ import { ReportArguments, ControlResponse } from "@satellite-earth/core/types";
|
||||
import _throttle from "lodash.throttle";
|
||||
|
||||
import BakeryControlApi from "../classes/bakery/control-api";
|
||||
import { controlApi } from "./bakery";
|
||||
import { controlApi$ } from "./bakery";
|
||||
import Report from "../classes/bakery/reports/report";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import { logger } from "../helpers/debug";
|
||||
import { ReportClasses, ReportTypes } from "../classes/bakery/reports";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
class ReportManager {
|
||||
log = logger.extend("ReportManager");
|
||||
@ -86,11 +87,16 @@ class ReportManager {
|
||||
}
|
||||
}
|
||||
|
||||
const reportManagerService = controlApi ? new ReportManager(controlApi) : undefined;
|
||||
const reportManager$ = new BehaviorSubject<ReportManager | null>(null);
|
||||
|
||||
controlApi$.subscribe((api) => {
|
||||
if (api) reportManager$.next(new ReportManager(api));
|
||||
else reportManager$.next(null);
|
||||
});
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-expect-error
|
||||
window.reportManagerService = reportManagerService;
|
||||
window.reportManager$ = reportManager$;
|
||||
}
|
||||
|
||||
export default reportManagerService;
|
||||
export default reportManager$;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { type WebPushChannel } from "@satellite-earth/core/types/control-api/notifications.js";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { controlApi } from "./bakery";
|
||||
import { getControlApi } from "./bakery";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { serviceWorkerRegistration } from "./worker";
|
||||
import localSettings from "./local-settings";
|
||||
@ -14,6 +14,8 @@ serviceWorkerRegistration.subscribe(async (registration) => {
|
||||
});
|
||||
|
||||
export async function enableNotifications() {
|
||||
const controlApi = getControlApi();
|
||||
|
||||
if (!controlApi) throw new Error("Missing control api");
|
||||
const subscription = await serviceWorkerRegistration.value?.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
@ -42,6 +44,8 @@ export async function enableNotifications() {
|
||||
}
|
||||
|
||||
export async function disableNotifications() {
|
||||
const controlApi = getControlApi();
|
||||
|
||||
if (pushSubscription.value) {
|
||||
const key = pushSubscription.value.toJSON().keys?.p256dh;
|
||||
if (key) controlApi?.send(["CONTROL", "NOTIFICATIONS", "UNREGISTER", key]);
|
||||
|
@ -1,48 +0,0 @@
|
||||
import { Divider, Flex, Text } from "@chakra-ui/react";
|
||||
import { Outlet, useMatch } from "react-router-dom";
|
||||
|
||||
import SimpleHeader from "../../../components/layout/presets/simple-header";
|
||||
import { useBreakpointValue } from "../../../providers/global/breakpoint-provider";
|
||||
import SimpleNavItem from "../../../components/layout/presets/simple-nav-item";
|
||||
import { ErrorBoundary } from "../../../components/error-boundary";
|
||||
|
||||
export default function SettingsView() {
|
||||
const match = useMatch("/settings");
|
||||
const isMobile = useBreakpointValue({ base: true, lg: false });
|
||||
const showMenu = !isMobile || !!match;
|
||||
|
||||
if (showMenu) {
|
||||
return (
|
||||
<Flex overflow="hidden" flex={1} direction={{ base: "column", lg: "row" }}>
|
||||
<Flex overflowY="auto" overflowX="hidden" h="full" minW="xs" direction="column">
|
||||
<SimpleHeader title="Settings" />
|
||||
<Flex direction="column" p="2" gap="2">
|
||||
<SimpleNavItem to="/settings/display">Display</SimpleNavItem>
|
||||
<SimpleNavItem to="/settings/notifications">Notifications</SimpleNavItem>
|
||||
<Flex alignItems="center" gap="2">
|
||||
<Divider />
|
||||
<Text fontWeight="bold" fontSize="md">
|
||||
Node
|
||||
</Text>
|
||||
<Divider />
|
||||
</Flex>
|
||||
<SimpleNavItem to="/settings/general">General</SimpleNavItem>
|
||||
<SimpleNavItem to="/settings/networking">Network</SimpleNavItem>
|
||||
<SimpleNavItem to="/settings/logs">Service Logs</SimpleNavItem>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{!isMobile && (
|
||||
<ErrorBoundary>
|
||||
<Outlet />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Outlet />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
@ -14,15 +14,15 @@ import {
|
||||
useDisclosure,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import bakery, { setBakeryURL } from "../../../services/bakery";
|
||||
import Panel from "../../../components/dashboard/panel";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import accountService from "../../../services/account";
|
||||
import { useSigningContext } from "../../../providers/global/signing-provider";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
export function PersonalNodeAuthPage() {
|
||||
import useCurrentAccount from "../../../../hooks/use-current-account";
|
||||
import { useSigningContext } from "../../../../providers/global/signing-provider";
|
||||
import { bakery$, setBakeryURL } from "../../../../services/bakery";
|
||||
import accountService from "../../../../services/account";
|
||||
import Panel from "../../../../components/dashboard/panel";
|
||||
|
||||
export function BakeryAuthPage() {
|
||||
const toast = useToast();
|
||||
const navigate = useNavigate();
|
||||
const account = useCurrentAccount();
|
||||
@ -30,6 +30,7 @@ export function PersonalNodeAuthPage() {
|
||||
const [search] = useSearchParams();
|
||||
const remember = useDisclosure({ defaultIsOpen: true });
|
||||
const location = useLocation();
|
||||
const bakery = useObservable(bakery$);
|
||||
|
||||
const { register, handleSubmit, formState } = useForm({
|
||||
defaultValues: { auth: search.get("auth") ?? "" },
|
||||
@ -87,7 +88,7 @@ export function PersonalNodeAuthPage() {
|
||||
<Checkbox isChecked={remember.isOpen} onChange={remember.onToggle}>
|
||||
Remember Me
|
||||
</Checkbox>
|
||||
<Button type="submit" size="sm" colorScheme="brand">
|
||||
<Button type="submit" size="sm" colorScheme="primary">
|
||||
Login
|
||||
</Button>
|
||||
</Flex>
|
||||
@ -125,11 +126,12 @@ export function PersonalNodeAuthPage() {
|
||||
|
||||
export default function BakeryAuthView() {
|
||||
const location = useLocation();
|
||||
const bakery = useObservable(bakery$);
|
||||
const authenticated = useObservable(bakery?.authenticated);
|
||||
|
||||
if (authenticated) {
|
||||
return <Navigate to={location.state?.back ?? "/"} replace />;
|
||||
}
|
||||
|
||||
return <PersonalNodeAuthPage />;
|
||||
return <BakeryAuthPage />;
|
||||
}
|
@ -1,30 +1,21 @@
|
||||
import { Navigate, useLocation, useNavigate, useSearchParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Code,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
Heading,
|
||||
Input,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { Box, Button, Code, Flex, FormControl, FormLabel, Heading, Input, Text } from "@chakra-ui/react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import bakery, { setBakeryURL } from "../../../services/bakery";
|
||||
import QRCodeScannerButton from "../../../components/qr-code/qr-code-scanner-button";
|
||||
import TextButton from "../../../components/dashboard/text-button";
|
||||
import SimpleView from "../../../../components/layout/presets/simple-view";
|
||||
import { bakery$, setBakeryURL } from "../../../../services/bakery";
|
||||
import QRCodeScannerButton from "../../../../components/qr-code/qr-code-scanner-button";
|
||||
import TextButton from "../../../../components/dashboard/text-button";
|
||||
|
||||
function ConnectForm() {
|
||||
const [params] = useSearchParams();
|
||||
const bakery = useObservable(bakery$);
|
||||
const { register, handleSubmit, formState, setValue } = useForm({
|
||||
defaultValues: {
|
||||
url: params.get("relay") ?? bakery?.url ?? "",
|
||||
},
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
const handleScanData = (data: string) => {
|
||||
@ -42,31 +33,24 @@ function ConnectForm() {
|
||||
|
||||
return (
|
||||
<Flex as="form" onSubmit={submit} gap="2" direction="column">
|
||||
<Heading size="lg">Bakery</Heading>
|
||||
<FormControl>
|
||||
<FormLabel>Bakery URL</FormLabel>
|
||||
<Flex gap="2">
|
||||
<Input type="text" {...register("url", { required: true })} isRequired placeholder="ws://127.0.0.1:2012" />
|
||||
<Input type="text" {...register("url", { required: true })} isRequired placeholder="ws://localhost:2012" />
|
||||
<QRCodeScannerButton onData={handleScanData} />
|
||||
</Flex>
|
||||
<FormHelperText>This is the URL to your bakery</FormHelperText>
|
||||
</FormControl>
|
||||
<Flex>
|
||||
{params.has("config") && (
|
||||
<Button as={RouterLink} to="/" p="2" variant="link">
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
<Button isLoading={formState.isSubmitting} type="submit" ml="auto" colorScheme="brand">
|
||||
Connect
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<Button isLoading={formState.isSubmitting} type="submit" ml="auto" colorScheme="primary">
|
||||
Connect
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
function ConnectConfirmation() {
|
||||
const [params] = useSearchParams();
|
||||
const bakery = useObservable(bakery$);
|
||||
const relay = params.get("relay");
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -95,8 +79,9 @@ function ConnectConfirmation() {
|
||||
);
|
||||
}
|
||||
|
||||
export default function ConnectView() {
|
||||
export default function BakeryConnectView() {
|
||||
const location = useLocation();
|
||||
const bakery = useObservable(bakery$);
|
||||
const connected = useObservable(bakery?.connectedSub);
|
||||
|
||||
const [params] = useSearchParams();
|
||||
@ -110,10 +95,8 @@ export default function ConnectView() {
|
||||
if (isRelayParamEqual) return <Navigate replace to="/" />;
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" alignItems="center" justifyContent="center">
|
||||
<Flex direction="column" gap="2" w="full" maxW="sm" m="4">
|
||||
{relayParam ? <ConnectConfirmation /> : <ConnectForm />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<SimpleView title="Connect bakery" maxW="4xl">
|
||||
<ConnectForm />
|
||||
</SimpleView>
|
||||
);
|
||||
}
|
@ -3,10 +3,13 @@ import { useForm } from "react-hook-form";
|
||||
import { Button, Flex, FormControl, FormHelperText, FormLabel, Heading, Input, Textarea } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import personalNode, { controlApi, clearBakeryURL } from "../../../../services/bakery";
|
||||
import { controlApi$, clearBakeryURL, bakery$ } from "../../../../services/bakery";
|
||||
import SimpleView from "../../../../components/layout/presets/simple-view";
|
||||
import { Navigate } from "react-router-dom";
|
||||
|
||||
function NodeGeneralSettingsPage() {
|
||||
function BakeryGeneralSettingsPage() {
|
||||
const bakery = useObservable(bakery$);
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const { register, handleSubmit, formState, reset } = useForm({
|
||||
defaultValues: config || {},
|
||||
@ -30,22 +33,19 @@ function NodeGeneralSettingsPage() {
|
||||
});
|
||||
|
||||
const disconnect = () => {
|
||||
if (confirm("Disconnect from personal node?")) {
|
||||
clearBakeryURL();
|
||||
}
|
||||
if (confirm("Disconnect from bakery?")) clearBakeryURL();
|
||||
};
|
||||
|
||||
return (
|
||||
<SimpleView title="Node Settings">
|
||||
<FormControl>
|
||||
<FormLabel>Bakery URL</FormLabel>
|
||||
<Flex gap="2">
|
||||
<Input readOnly value={personalNode!.url} maxW="xs" />
|
||||
<Button isDisabled>Change</Button>
|
||||
<Flex maxW="lg" gap="2">
|
||||
<Input readOnly value={bakery!.url} />
|
||||
<Button colorScheme="red" onClick={disconnect} variant="ghost" flexShrink={0}>
|
||||
disconnect
|
||||
</Button>
|
||||
</Flex>
|
||||
<Button variant="link" colorScheme="red" mt="2" onClick={disconnect}>
|
||||
disconnect
|
||||
</Button>
|
||||
</FormControl>
|
||||
|
||||
<Flex as="form" onSubmit={submit} direction="column" maxW="lg" gap="4">
|
||||
@ -75,5 +75,9 @@ function NodeGeneralSettingsPage() {
|
||||
}
|
||||
|
||||
export default function BakeryGeneralSettingsView() {
|
||||
return <>{personalNode ? <NodeGeneralSettingsPage /> : <Heading>Missing personal node connection</Heading>}</>;
|
||||
const bakery = useObservable(bakery$);
|
||||
|
||||
if (!bakery) return <Navigate to="/settings/bakery/connect" />;
|
||||
|
||||
return <BakeryGeneralSettingsPage />;
|
||||
}
|
||||
|
@ -5,10 +5,11 @@ import { safeRelayUrl } from "applesauce-core/helpers";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useAsyncErrorHandler from "../../../../hooks/use-async-error-handler";
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import { RelayFavicon } from "../../../../components/relay-favicon";
|
||||
|
||||
function BroadcastRelay({ relay }: { relay: string }) {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const remove = useAsyncErrorHandler(async () => {
|
||||
if (!config) return;
|
||||
@ -37,6 +38,7 @@ function BroadcastRelay({ relay }: { relay: string }) {
|
||||
}
|
||||
|
||||
function AddRelayForm() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const { register, handleSubmit, reset } = useForm({ defaultValues: { url: "" } });
|
||||
|
||||
@ -59,6 +61,7 @@ function AddRelayForm() {
|
||||
}
|
||||
|
||||
function IntervalSelect() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
|
||||
return (
|
||||
@ -81,6 +84,7 @@ function IntervalSelect() {
|
||||
}
|
||||
|
||||
export default function GossipSettings() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
|
||||
return (
|
||||
|
@ -3,9 +3,10 @@ import { Alert, AlertIcon, FormControl, FormHelperText, Switch } from "@chakra-u
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
|
||||
export default function HyperOutboundStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
|
||||
|
@ -5,9 +5,10 @@ import { useObservable } from "applesauce-react/hooks";
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
import HyperInboundStatus from "./hyper-inbound";
|
||||
import HyperOutboundStatus from "./hyper-outbound";
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
|
||||
export default function HyperNetworkStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
|
||||
|
@ -2,10 +2,11 @@ import { ReactNode } from "react";
|
||||
import { Alert, AlertIcon, FormControl, FormHelperText, Switch } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
|
||||
export default function I2POutboundStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
|
||||
|
@ -4,10 +4,11 @@ import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import I2POutboundStatus from "./i2p-outbound";
|
||||
import I2PInboundStatus from "./i2p-inbound";
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
|
||||
export default function I2PNetworkStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
|
||||
|
@ -2,10 +2,11 @@ import { ReactNode } from "react";
|
||||
import { Alert, AlertIcon, FormControl, FormHelperText, Switch } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
|
||||
export default function TorOutboundStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
|
||||
|
@ -4,10 +4,11 @@ import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import TorOutboundStatus from "./tor-outbound";
|
||||
import TorInboundStatus from "./tor-inbound";
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNetworkOverviewReport from "../../../../hooks/reports/use-network-status-report";
|
||||
|
||||
export default function TorNetworkStatus() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const status = useNetworkOverviewReport();
|
||||
|
||||
|
@ -6,11 +6,12 @@ import { useObservable } from "applesauce-react/hooks";
|
||||
import NtfyNotificationSettings from "./ntfy";
|
||||
import OtherSubscriptions from "./other";
|
||||
import WebPushNotificationSettings from "./web-push";
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import SimpleView from "../../../../components/layout/presets/simple-view";
|
||||
import { CAP_IS_NATIVE, CAP_IS_WEB } from "../../../../env";
|
||||
|
||||
function EmailForm() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
const { register, handleSubmit, reset } = useForm({
|
||||
defaultValues: { email: config?.notificationEmail ?? "" },
|
||||
|
@ -5,13 +5,15 @@ import { kinds, NostrEvent } from "nostr-tools";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useCurrentAccount from "../../../../hooks/use-current-account";
|
||||
import bakery, { controlApi } from "../../../../services/bakery";
|
||||
import { bakery$, controlApi$ } from "../../../../services/bakery";
|
||||
import localSettings from "../../../../services/local-settings";
|
||||
import useNotificationChannelsReport from "../../../../hooks/reports/use-notification-channels";
|
||||
import { CopyIconButton } from "../../../../components/copy-icon-button";
|
||||
import { ExternalLinkIcon } from "../../../../components/icons";
|
||||
|
||||
export default function NtfyNotificationSettings() {
|
||||
const bakery = useObservable(bakery$);
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const account = useCurrentAccount();
|
||||
|
||||
const device = useObservable(localSettings.deviceId);
|
||||
|
@ -2,10 +2,12 @@ import { ReactNode } from "react";
|
||||
import { Badge, Button, Flex, Heading, Text } from "@chakra-ui/react";
|
||||
import { NotificationChannel } from "@satellite-earth/core/types/control-api/notifications.js";
|
||||
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import useNotificationChannelsReport from "../../../../hooks/reports/use-notification-channels";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
function Channel({ channel }: { channel: NotificationChannel }) {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
let details: ReactNode = null;
|
||||
|
||||
switch (channel.type) {
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
enableNotifications,
|
||||
pushSubscription,
|
||||
} from "../../../../services/web-push-notifications";
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
|
||||
function WebPushNotificationStatus() {
|
||||
const toast = useToast();
|
||||
@ -74,9 +74,10 @@ function WebPushNotificationStatus() {
|
||||
}
|
||||
|
||||
export default function WebPushNotificationSettings() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
useEffect(() => {
|
||||
controlApi?.send(["CONTROL", "NOTIFICATIONS", "GET-VAPID-KEY"]);
|
||||
}, []);
|
||||
}, [controlApi]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -14,16 +14,18 @@ import {
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import Convert from "ansi-to-html";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import useLogsReport from "../../../../hooks/reports/use-logs-report";
|
||||
import Timestamp from "../../../../components/timestamp";
|
||||
import SimpleView from "../../../../components/layout/presets/simple-view";
|
||||
import { controlApi } from "../../../../services/bakery";
|
||||
import { controlApi$ } from "../../../../services/bakery";
|
||||
import ServicesTree from "./service-tree";
|
||||
|
||||
const convert = new Convert();
|
||||
|
||||
export default function BakeryServiceLogsView() {
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const [service, setService] = useState<string | undefined>(undefined);
|
||||
const { report, logs } = useLogsReport(service);
|
||||
const raw = useDisclosure();
|
||||
|
@ -5,15 +5,17 @@ import { Navigate } from "react-router-dom";
|
||||
import { isHexKey } from "applesauce-core/helpers";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
|
||||
import personalNode, { controlApi, setBakeryURL } from "../../../services/bakery";
|
||||
import useRouteSearchValue from "../../../hooks/use-route-search-value";
|
||||
import dnsIdentityService from "../../../services/dns-identity";
|
||||
import QRCodeScannerButton from "../../../components/qr-code/qr-code-scanner-button";
|
||||
import { bakery$, controlApi$, setBakeryURL } from "../../../../services/bakery";
|
||||
import useRouteSearchValue from "../../../../hooks/use-route-search-value";
|
||||
import dnsIdentityService from "../../../../services/dns-identity";
|
||||
import QRCodeScannerButton from "../../../../components/qr-code/qr-code-scanner-button";
|
||||
|
||||
export default function BakerySetupView() {
|
||||
const toast = useToast();
|
||||
const relayParam = useRouteSearchValue("relay");
|
||||
const authParam = useRouteSearchValue("auth");
|
||||
const bakery = useObservable(bakery$);
|
||||
const controlApi = useObservable(controlApi$);
|
||||
const config = useObservable(controlApi?.config);
|
||||
|
||||
const { register, setValue, formState, handleSubmit } = useForm({
|
||||
@ -23,7 +25,7 @@ export default function BakerySetupView() {
|
||||
|
||||
const submit = handleSubmit(async (values) => {
|
||||
try {
|
||||
if (!personalNode) throw new Error("Missing personal node connection");
|
||||
if (!bakery) throw new Error("Missing personal node connection");
|
||||
if (!controlApi) throw new Error("Missing control api connection");
|
||||
let pubkey: string = "";
|
||||
|
||||
@ -63,7 +65,7 @@ export default function BakerySetupView() {
|
||||
if (!pubkey) throw new Error("Unable to find nostr public key");
|
||||
if (!authParam.value) throw new Error("Missing auth code");
|
||||
|
||||
await personalNode.authenticate(authParam.value);
|
||||
await bakery.authenticate(authParam.value);
|
||||
|
||||
controlApi.send(["CONTROL", "CONFIG", "SET", "owner", pubkey]);
|
||||
} catch (error) {
|
||||
@ -76,8 +78,8 @@ export default function BakerySetupView() {
|
||||
}
|
||||
|
||||
if (relayParam.value) {
|
||||
if (personalNode) {
|
||||
if (new URL(personalNode.url).toString() !== new URL(relayParam.value).toString()) {
|
||||
if (bakery) {
|
||||
if (new URL(bakery.url).toString() !== new URL(relayParam.value).toString()) {
|
||||
setBakeryURL(relayParam.value);
|
||||
}
|
||||
} else setBakeryURL(relayParam.value);
|
||||
@ -95,7 +97,7 @@ export default function BakerySetupView() {
|
||||
</Flex>
|
||||
<FormHelperText>Enter the NIP-05, npub, or hex pubkey of the owner of this node</FormHelperText>
|
||||
</FormControl>
|
||||
<Button type="submit" colorScheme="brand" isLoading={formState.isSubmitting} ml="auto">
|
||||
<Button type="submit" colorScheme="primary" isLoading={formState.isSubmitting} ml="auto">
|
||||
Setup
|
||||
</Button>
|
||||
</Flex>
|
@ -19,12 +19,13 @@ import Image01 from "../../components/icons/image-01";
|
||||
import UserAvatar from "../../components/user/user-avatar";
|
||||
import VersionButton from "../../components/version-button";
|
||||
import SimpleHeader from "../../components/layout/presets/simple-header";
|
||||
import bakery from "../../services/bakery";
|
||||
import SimpleNavItem from "../../components/layout/presets/simple-nav-item";
|
||||
import Bell01 from "../../components/icons/bell-01";
|
||||
import Share07 from "../../components/icons/share-07";
|
||||
import Database01 from "../../components/icons/database-01";
|
||||
import Mail02 from "../../components/icons/mail-02";
|
||||
import { useObservable } from "applesauce-react/hooks";
|
||||
import { bakery$ } from "../../services/bakery";
|
||||
|
||||
function DividerHeader({ title }: { title: string }) {
|
||||
return (
|
||||
@ -44,6 +45,8 @@ export default function SettingsView() {
|
||||
const isMobile = useBreakpointValue({ base: true, lg: false });
|
||||
const showMenu = !isMobile || !!match;
|
||||
|
||||
const bakery = useObservable(bakery$)
|
||||
|
||||
if (showMenu)
|
||||
return (
|
||||
<Flex overflow="hidden" flex={1} direction={{ base: "column", lg: "row" }}>
|
||||
@ -87,10 +90,10 @@ export default function SettingsView() {
|
||||
Database Tools
|
||||
</SimpleNavItem>
|
||||
|
||||
{bakery && (
|
||||
{bakery ? (
|
||||
<>
|
||||
<DividerHeader title="Relay" />
|
||||
<SimpleNavItem to="/settings/bakery">Bakery</SimpleNavItem>
|
||||
<DividerHeader title="bakery" />
|
||||
<SimpleNavItem to="/settings/bakery">General</SimpleNavItem>
|
||||
<SimpleNavItem to="/settings/bakery/notifications" leftIcon={<Bell01 boxSize={5} />}>
|
||||
Notifications
|
||||
</SimpleNavItem>
|
||||
@ -101,6 +104,11 @@ export default function SettingsView() {
|
||||
Service Logs
|
||||
</SimpleNavItem>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DividerHeader title="bakery" />
|
||||
<SimpleNavItem to="/settings/bakery/connect">Connect</SimpleNavItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Flex alignItems="center">
|
||||
|
Loading…
x
Reference in New Issue
Block a user