From 334d90e69105f72160a1aab07c66f9254c9e4977 Mon Sep 17 00:00:00 2001 From: Khairul Hidayat Date: Fri, 8 Nov 2024 18:53:30 +0000 Subject: [PATCH] feat: update ui --- frontend/app/(drawer)/_layout.tsx | 26 ++++ frontend/app/(drawer)/hosts.tsx | 3 + frontend/app/(drawer)/terminal.tsx | 3 + frontend/app/_layout.tsx | 7 +- frontend/app/_providers.tsx | 45 +++++-- frontend/app/auth/login.tsx | 14 ++ frontend/app/hosts/create.tsx | 2 +- frontend/app/hosts/edit.tsx | 2 +- frontend/app/hosts/index.tsx | 80 ----------- frontend/app/index.tsx | 9 +- frontend/app/term/index.tsx | 91 ------------- frontend/app/terminal/sessions.tsx | 3 + .../containers/interactive-session.tsx | 9 +- frontend/components/containers/terminal.tsx | 72 +++------- frontend/components/containers/xtermjs.tsx | 31 ++++- frontend/components/ui/pager-view.tsx | 35 ++++- frontend/components/ui/pager-view.web.tsx | 13 +- frontend/components/ui/pressable.tsx | 60 ++++++++- frontend/components/ui/search-input.tsx | 25 ++++ frontend/hooks/useDebounce.ts | 28 ++++ frontend/package.json | 8 +- .../hosts/components/form.tsx} | 0 .../pages/hosts/components/hosts-list.tsx | 124 ++++++++++++++++++ frontend/pages/hosts/page.tsx | 25 ++++ .../terminal/components/session-tabs.tsx | 63 +++++++++ frontend/pages/terminal/page.tsx | 48 +++++++ frontend/pages/terminal/sessions-page.tsx | 93 +++++++++++++ .../patches/react-native-drawer-layout.patch | 25 ++++ frontend/pnpm-lock.yaml | 76 ++++++++++- frontend/stores/auth.ts | 26 ++++ frontend/stores/terminal-sessions.ts | 43 ++++++ frontend/stores/theme.ts | 2 +- 32 files changed, 829 insertions(+), 262 deletions(-) create mode 100644 frontend/app/(drawer)/_layout.tsx create mode 100644 frontend/app/(drawer)/hosts.tsx create mode 100644 frontend/app/(drawer)/terminal.tsx create mode 100644 frontend/app/auth/login.tsx delete mode 100644 frontend/app/hosts/index.tsx delete mode 100644 frontend/app/term/index.tsx create mode 100644 frontend/app/terminal/sessions.tsx create mode 100644 frontend/components/ui/search-input.tsx create mode 100644 frontend/hooks/useDebounce.ts rename frontend/{app/hosts/_comp/host-form.tsx => pages/hosts/components/form.tsx} (100%) create mode 100644 frontend/pages/hosts/components/hosts-list.tsx create mode 100644 frontend/pages/hosts/page.tsx create mode 100644 frontend/pages/terminal/components/session-tabs.tsx create mode 100644 frontend/pages/terminal/page.tsx create mode 100644 frontend/pages/terminal/sessions-page.tsx create mode 100644 frontend/patches/react-native-drawer-layout.patch create mode 100644 frontend/stores/auth.ts create mode 100644 frontend/stores/terminal-sessions.ts diff --git a/frontend/app/(drawer)/_layout.tsx b/frontend/app/(drawer)/_layout.tsx new file mode 100644 index 0000000..935daab --- /dev/null +++ b/frontend/app/(drawer)/_layout.tsx @@ -0,0 +1,26 @@ +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { Drawer } from "expo-router/drawer"; +import React from "react"; +import { useMedia } from "tamagui"; + +export default function Layout() { + const media = useMedia(); + + return ( + + null, + }} + > + + + + + ); +} diff --git a/frontend/app/(drawer)/hosts.tsx b/frontend/app/(drawer)/hosts.tsx new file mode 100644 index 0000000..e5d45c3 --- /dev/null +++ b/frontend/app/(drawer)/hosts.tsx @@ -0,0 +1,3 @@ +import HostsPage from "@/pages/hosts/page"; + +export default HostsPage; diff --git a/frontend/app/(drawer)/terminal.tsx b/frontend/app/(drawer)/terminal.tsx new file mode 100644 index 0000000..a4c65c7 --- /dev/null +++ b/frontend/app/(drawer)/terminal.tsx @@ -0,0 +1,3 @@ +import TerminalPage from "@/pages/terminal/page"; + +export default TerminalPage; diff --git a/frontend/app/_layout.tsx b/frontend/app/_layout.tsx index ae1b170..182f93f 100644 --- a/frontend/app/_layout.tsx +++ b/frontend/app/_layout.tsx @@ -27,7 +27,12 @@ export default function RootLayout() { return ( - + + + diff --git a/frontend/app/_providers.tsx b/frontend/app/_providers.tsx index 2eea6a0..8824aae 100644 --- a/frontend/app/_providers.tsx +++ b/frontend/app/_providers.tsx @@ -1,4 +1,4 @@ -import React, { PropsWithChildren, useMemo, useState } from "react"; +import React, { PropsWithChildren, useEffect, useMemo, useState } from "react"; import tamaguiConfig from "@/tamagui.config"; import { DarkTheme, @@ -8,6 +8,8 @@ import { import { TamaguiProvider, Theme } from "@tamagui/core"; import useThemeStore from "@/stores/theme"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { router, usePathname, useRootNavigationState } from "expo-router"; +import { useAuthStore } from "@/stores/auth"; type Props = PropsWithChildren; @@ -33,16 +35,39 @@ const Providers = ({ children }: Props) => { }, [theme, colorScheme]); return ( - - - - - {children} - - - - + <> + + + + + + {children} + + + + + ); }; +const AuthProvider = () => { + const pathname = usePathname(); + const rootNavigationState = useRootNavigationState(); + const { isLoggedIn } = useAuthStore(); + + useEffect(() => { + if (!rootNavigationState?.key) { + return; + } + + if (!pathname.startsWith("/auth") && !isLoggedIn) { + router.replace("/auth/login"); + } else if (pathname.startsWith("/auth") && isLoggedIn) { + router.replace("/"); + } + }, [pathname, rootNavigationState, isLoggedIn]); + + return null; +}; + export default Providers; diff --git a/frontend/app/auth/login.tsx b/frontend/app/auth/login.tsx new file mode 100644 index 0000000..0a0da13 --- /dev/null +++ b/frontend/app/auth/login.tsx @@ -0,0 +1,14 @@ +import { View, Text, Button } from "tamagui"; +import React from "react"; +import authStore from "@/stores/auth"; + +export default function LoginPage() { + return ( + + LoginPage + + + ); +} diff --git a/frontend/app/hosts/create.tsx b/frontend/app/hosts/create.tsx index 76e475d..db54d54 100644 --- a/frontend/app/hosts/create.tsx +++ b/frontend/app/hosts/create.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Stack } from "expo-router"; -import HostForm from "./_comp/host-form"; +import HostForm from "@/pages/hosts/components/form"; export default function CreateHostPage() { return ( diff --git a/frontend/app/hosts/edit.tsx b/frontend/app/hosts/edit.tsx index 268a502..abff198 100644 --- a/frontend/app/hosts/edit.tsx +++ b/frontend/app/hosts/edit.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Stack } from "expo-router"; -import HostForm from "./_comp/host-form"; +import HostForm from "@/pages/hosts/components/form"; export default function EditHostPage() { return ( diff --git a/frontend/app/hosts/index.tsx b/frontend/app/hosts/index.tsx deleted file mode 100644 index d8d0490..0000000 --- a/frontend/app/hosts/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { View, Text, Button, ScrollView, Spinner, Card, XStack } from "tamagui"; -import React from "react"; -import useThemeStore from "@/stores/theme"; -import { useQuery } from "@tanstack/react-query"; -import api from "@/lib/api"; -import { Stack } from "expo-router"; -import Pressable from "@/components/ui/pressable"; -import Icons from "@/components/ui/icons"; - -export default function Hosts() { - const { toggle } = useThemeStore(); - - const hosts = useQuery({ - queryKey: ["hosts"], - queryFn: () => api("/hosts"), - }); - - return ( - <> - ( - - ), - }} - /> - - {hosts.isLoading ? ( - - - Loading... - - ) : ( - - {hosts.data.rows?.map((host: any) => ( - - - - - {host.label} - - {host.host} - - - - - - - - ))} - - )} - - ); -} diff --git a/frontend/app/index.tsx b/frontend/app/index.tsx index 9fd40d9..fac85db 100644 --- a/frontend/app/index.tsx +++ b/frontend/app/index.tsx @@ -1,6 +1,13 @@ import React from "react"; import { Redirect } from "expo-router"; +import { useTermSession } from "@/stores/terminal-sessions"; export default function index() { - return ; + const { sessions, curSession } = useTermSession(); + + return ( + 0 && curSession >= 0 ? "/terminal" : "/hosts"} + /> + ); } diff --git a/frontend/app/term/index.tsx b/frontend/app/term/index.tsx deleted file mode 100644 index c53ba69..0000000 --- a/frontend/app/term/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { View, ScrollView, Button } from "react-native"; -import React, { useState } from "react"; -import { Stack } from "expo-router"; -import InteractiveSession, { - InteractiveSessionProps, -} from "@/components/containers/interactive-session"; -import PagerView from "@/components/ui/pager-view"; - -type Session = InteractiveSessionProps & { id: string }; - -const HomePage = () => { - const [sessions, setSessions] = useState([ - { - id: "1", - type: "ssh", - params: { hostId: "01jc3v9w609f8e2wzw60amv195" }, - }, - // { - // id: "2", - // type: "pve", - // params: { client: "vnc", hostId: "01jc3wp2b3zvgr777f4e3caw4w" }, - // }, - // { - // id: "3", - // type: "pve", - // params: { client: "xtermjs", hostId: "01jc3z3yyn2fgb77tyfxc1tkfy" }, - // }, - // { - // id: "4", - // type: "incus", - // params: { - // client: "xtermjs", - // hostId: "01jc3xz9db0v54dg10hk70a13b", - // shell: "fish", - // }, - // }, - ]); - const [curSession, setSession] = useState(0); - - return ( - - - - - {sessions.map((session, idx) => ( - - + + + + ))} + + )} + + ); +}; + +export default HostsList; diff --git a/frontend/pages/hosts/page.tsx b/frontend/pages/hosts/page.tsx new file mode 100644 index 0000000..93cebf1 --- /dev/null +++ b/frontend/pages/hosts/page.tsx @@ -0,0 +1,25 @@ +import { Button } from "tamagui"; +import React from "react"; +import useThemeStore from "@/stores/theme"; +import Drawer from "expo-router/drawer"; +import HostsList from "./components/hosts-list"; + +export default function HostsPage() { + const { toggle } = useThemeStore(); + + return ( + <> + ( + + ), + }} + /> + + + + ); +} diff --git a/frontend/pages/terminal/components/session-tabs.tsx b/frontend/pages/terminal/components/session-tabs.tsx new file mode 100644 index 0000000..ce43237 --- /dev/null +++ b/frontend/pages/terminal/components/session-tabs.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import { useTermSession } from "@/stores/terminal-sessions"; +import { Button, ScrollView, View } from "tamagui"; +import Icons from "@/components/ui/icons"; + +const SessionTabs = () => { + const { sessions, curSession, setSession, remove } = useTermSession(); + + return ( + + {sessions.map((session, idx) => ( + + + + ), + }} + /> + + + + + + + {sessionList.map((session, idx) => ( + + + + + + ))} + + + ); +}; + +export default SessionsPage; diff --git a/frontend/patches/react-native-drawer-layout.patch b/frontend/patches/react-native-drawer-layout.patch new file mode 100644 index 0000000..e89b246 --- /dev/null +++ b/frontend/patches/react-native-drawer-layout.patch @@ -0,0 +1,25 @@ +diff --git a/lib/commonjs/views/Drawer.native.js b/lib/commonjs/views/Drawer.native.js +index 7bac11bd3e76fc78284aabd81b6a94446c64813f..6cf9d65503f5ec35cea276bdc6803adfc60d2c0e 100644 +--- a/lib/commonjs/views/Drawer.native.js ++++ b/lib/commonjs/views/Drawer.native.js +@@ -159,16 +159,20 @@ function Drawer({ + React.useEffect(() => toggleDrawer(open), [open, toggleDrawer]); + const startX = (0, _reactNativeReanimated.useSharedValue)(0); + let pan = _GestureHandler.Gesture?.Pan().onBegin(event => { ++ 'worklet'; + startX.value = translationX.value; + gestureState.value = event.state; + touchStartX.value = event.x; + }).onStart(() => { ++ 'worklet'; + (0, _reactNativeReanimated.runOnJS)(onGestureBegin)(); + }).onChange(event => { ++ 'worklet'; + touchX.value = event.x; + translationX.value = startX.value + event.translationX; + gestureState.value = event.state; + }).onEnd((event, success) => { ++ 'worklet'; + gestureState.value = event.state; + if (!success) { + (0, _reactNativeReanimated.runOnJS)(onGestureAbort)(); diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 9f5f4aa..be61f5a 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + react-native-drawer-layout: + hash: ipghvwpiqcl5liuijnfvmjzcvq + path: patches/react-native-drawer-layout.patch + importers: .: @@ -20,6 +25,9 @@ importers: '@react-navigation/bottom-tabs': specifier: 7.0.0-rc.36 version: 7.0.0-rc.36(@react-navigation/native@7.0.0-rc.21(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native-screens@4.0.0-beta.16(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + '@react-navigation/drawer': + specifier: ^7.0.0 + version: 7.0.0(s2kwfzlicenreg74lts3a6znsu) '@react-navigation/native': specifier: 7.0.0-rc.21 version: 7.0.0-rc.21(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) @@ -58,7 +66,7 @@ importers: version: 7.0.2(expo@52.0.0-preview.19(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) expo-router: specifier: ~4.0.0-preview.12 - version: 4.0.0-preview.12(yd2wh2xxaopmvq6w6kpgmfoxyy) + version: 4.0.0-preview.12(dw2oobptqthz2eo3bvppba5ugq) expo-splash-screen: specifier: ~0.29.1 version: 0.29.1(expo-modules-autolinking@2.0.0-preview.3)(expo@52.0.0-preview.19(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(react-native-webview@13.12.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)) @@ -1399,6 +1407,29 @@ packages: peerDependencies: react: '*' + '@react-navigation/drawer@7.0.0': + resolution: {integrity: sha512-JbJ2ziSFVTV/qr2ffs2qMhziQJ8XHzRJhsF+PH2zFu4FCguRYaPqtf3Kl//tS4QWXVhpO4g5jweJQ7CfSousgw==} + peerDependencies: + '@react-navigation/native': ^7.0.0 + react: '>= 18.2.0' + react-native: '*' + react-native-gesture-handler: '>= 2.0.0' + react-native-reanimated: '>= 2.0.0' + react-native-safe-area-context: '>= 4.0.0' + react-native-screens: '>= 4.0.0' + + '@react-navigation/elements@2.0.0': + resolution: {integrity: sha512-kt2Q5WLJ9jjJMA/Jt8S3z3Jub2V+HIJ2LM4z+dZqL00FVsTfa4rSk3BTktI3MmBiUCgzUo6jPOxkxsUbjoL/ig==} + peerDependencies: + '@react-native-masked-view/masked-view': '>= 0.2.0' + '@react-navigation/native': ^7.0.0 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + peerDependenciesMeta: + '@react-native-masked-view/masked-view': + optional: true + '@react-navigation/elements@2.0.0-rc.26': resolution: {integrity: sha512-omtEkb2E8j3dYLq08YGsDykQoVLtTLWAQXp0ql6cB8qjtMhP7rMhoBU50veh0Tes/96Sm3X0e3WZPQMVBKrSSg==} peerDependencies: @@ -4611,6 +4642,14 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-native-drawer-layout@4.0.0: + resolution: {integrity: sha512-l9xu7YDXHImg3wpLjD12CokWV2H4Nu/Uc9EVxg/DFqEwgyDbZqE/8IGhQhN32TiZPgelAZNj5c3MBE2yTR1ivw==} + peerDependencies: + react: '>= 18.2.0' + react-native: '*' + react-native-gesture-handler: '>= 2.0.0' + react-native-reanimated: '>= 2.0.0' + react-native-gesture-handler@2.20.2: resolution: {integrity: sha512-HqzFpFczV4qCnwKlvSAvpzEXisL+Z9fsR08YV5LfJDkzuArMhBu2sOoSPUF/K62PCoAb+ObGlTC83TKHfUd0vg==} peerDependencies: @@ -7456,6 +7495,30 @@ snapshots: use-latest-callback: 0.2.1(react@18.3.1) use-sync-external-store: 1.2.2(react@18.3.1) + '@react-navigation/drawer@7.0.0(s2kwfzlicenreg74lts3a6znsu)': + dependencies: + '@react-navigation/elements': 2.0.0(@react-navigation/native@7.0.0-rc.21(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + '@react-navigation/native': 7.0.0-rc.21(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + color: 4.2.3 + react: 18.3.1 + react-native: 0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1) + react-native-drawer-layout: 4.0.0(patch_hash=ipghvwpiqcl5liuijnfvmjzcvq)(react-native-gesture-handler@2.20.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native-reanimated@3.16.1(@babel/core@7.26.0)(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + react-native-gesture-handler: 2.20.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + react-native-reanimated: 3.16.1(@babel/core@7.26.0)(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + react-native-safe-area-context: 4.12.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + react-native-screens: 4.0.0-beta.16(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + use-latest-callback: 0.2.1(react@18.3.1) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + + '@react-navigation/elements@2.0.0(@react-navigation/native@7.0.0-rc.21(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-navigation/native': 7.0.0-rc.21(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + color: 4.2.3 + react: 18.3.1 + react-native: 0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1) + react-native-safe-area-context: 4.12.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + '@react-navigation/elements@2.0.0-rc.26(@react-navigation/native@7.0.0-rc.21(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)': dependencies: '@react-navigation/native': 7.0.0-rc.21(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) @@ -9846,7 +9909,7 @@ snapshots: dependencies: invariant: 2.2.4 - expo-router@4.0.0-preview.12(yd2wh2xxaopmvq6w6kpgmfoxyy): + expo-router@4.0.0-preview.12(dw2oobptqthz2eo3bvppba5ugq): dependencies: '@expo/metro-runtime': 4.0.0-preview.1(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)) '@expo/server': 0.5.0-preview.0(typescript@5.6.3) @@ -9866,6 +9929,7 @@ snapshots: schema-utils: 4.2.0 server-only: 0.0.1 optionalDependencies: + '@react-navigation/drawer': 7.0.0(s2kwfzlicenreg74lts3a6znsu) react-native-reanimated: 3.16.1(@babel/core@7.26.0)(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - '@react-native-masked-view/masked-view' @@ -11706,6 +11770,14 @@ snapshots: react-is@18.3.1: {} + react-native-drawer-layout@4.0.0(patch_hash=ipghvwpiqcl5liuijnfvmjzcvq)(react-native-gesture-handler@2.20.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native-reanimated@3.16.1(@babel/core@7.26.0)(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-native: 0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1) + react-native-gesture-handler: 2.20.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + react-native-reanimated: 3.16.1(@babel/core@7.26.0)(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + use-latest-callback: 0.2.1(react@18.3.1) + react-native-gesture-handler@2.20.2(react-native@0.76.1(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1): dependencies: '@egjs/hammerjs': 2.0.17 diff --git a/frontend/stores/auth.ts b/frontend/stores/auth.ts new file mode 100644 index 0000000..d4facf4 --- /dev/null +++ b/frontend/stores/auth.ts @@ -0,0 +1,26 @@ +import { createStore, useStore } from "zustand"; +import { persist, createJSONStorage } from "zustand/middleware"; +import AsyncStorage from "@react-native-async-storage/async-storage"; + +type AuthStore = { + token?: string | null; +}; + +const authStore = createStore( + persist( + () => ({ + token: null, + }), + { + name: "auth", + storage: createJSONStorage(() => AsyncStorage), + } + ) +); + +export const useAuthStore = () => { + const state = useStore(authStore); + return { ...state, isLoggedIn: state.token != null }; +}; + +export default authStore; diff --git a/frontend/stores/terminal-sessions.ts b/frontend/stores/terminal-sessions.ts new file mode 100644 index 0000000..992bb2f --- /dev/null +++ b/frontend/stores/terminal-sessions.ts @@ -0,0 +1,43 @@ +import { InteractiveSessionProps } from "@/components/containers/interactive-session"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { create } from "zustand"; +import { createJSONStorage, persist } from "zustand/middleware"; + +export type Session = InteractiveSessionProps & { id: string }; + +type TerminalSessionsStore = { + sessions: Session[]; + curSession: number; + push: (session: Session) => void; + remove: (idx: number) => void; + setSession: (idx: number) => void; +}; + +export const useTermSession = create( + persist( + (set) => ({ + sessions: [], + curSession: 0, + push: (session: Session) => { + set((state) => ({ + sessions: [ + ...state.sessions, + { ...session, id: session.id + "." + Date.now() }, + ], + curSession: state.sessions.length, + })); + }, + remove: (idx: number) => { + set((state) => { + const sessions = [...state.sessions]; + sessions.splice(idx, 1); + return { sessions, curSession: Math.min(idx, sessions.length - 1) }; + }); + }, + setSession: (idx: number) => { + set({ curSession: idx }); + }, + }), + { name: "term-sessions", storage: createJSONStorage(() => AsyncStorage) } + ) +); diff --git a/frontend/stores/theme.ts b/frontend/stores/theme.ts index 8ea5ef2..105fef7 100644 --- a/frontend/stores/theme.ts +++ b/frontend/stores/theme.ts @@ -11,7 +11,7 @@ type Store = { const useThemeStore = create( persist( (set) => ({ - theme: "light", + theme: "dark", setTheme: (theme: "light" | "dark") => { set({ theme }); },