From 42c2d069eff11588bfec6bbafb96a670617ae1c6 Mon Sep 17 00:00:00 2001
From: niku <nvdpoel01@gmail.com>
Date: Tue, 21 Mar 2023 20:09:37 +0100
Subject: [PATCH] Split some code into seperate components

---
 app/nodes/[node]/index.tsx       | 209 +++++++++++--------------------
 app/nodes/index.tsx              |  63 ++++------
 components/Card/index.tsx        |  20 +++
 components/Gauge/index.tsx       |  16 +++
 components/Login/index.tsx       | 105 +++++++++++++---
 components/ProgressBar/index.tsx |  42 +++++++
 6 files changed, 264 insertions(+), 191 deletions(-)
 create mode 100644 components/Card/index.tsx
 create mode 100644 components/Gauge/index.tsx
 create mode 100644 components/ProgressBar/index.tsx

diff --git a/app/nodes/[node]/index.tsx b/app/nodes/[node]/index.tsx
index b4d2953..3628fc0 100644
--- a/app/nodes/[node]/index.tsx
+++ b/app/nodes/[node]/index.tsx
@@ -1,10 +1,13 @@
-import { Link, Stack, useSearchParams } from "expo-router";
-import { View, Text, SafeAreaView, ScrollView } from "react-native";
+import { Link, useSearchParams } from "expo-router";
+import { View, Text, SafeAreaView, ScrollView, Button } from "react-native";
 import { NodeResource, useNode } from "../../../hooks/useNode";
 import Icon from "@expo/vector-icons/Feather";
 import tw from "twrnc";
 import { formatBytes, formatPercentage } from "../../../lib/helper/format";
 import { useEffect, useMemo } from "react";
+import { Gauge } from "../../../components/Gauge";
+import ProgressBar from "../../../components/ProgressBar";
+import Card from "../../../components/Card";
 
 interface ResourceListItemProps {
   type: "LXC" | "QEMU";
@@ -42,54 +45,6 @@ export function ResourceListItem({ type, resource }: ResourceListItemProps) {
   );
 }
 
-interface ProgressBarProps {
-  label: string;
-  value: number;
-  max: number;
-  formatFn?: (input: number) => string;
-}
-
-export function ProgressBar({ label, value, max, formatFn }: ProgressBarProps) {
-  const percentage = formatPercentage(value / max);
-  return (
-    <View>
-      <View
-        style={tw.style("flex flex-row justify-between items-center mb-1 px-1")}
-      >
-        <Text>
-          {label} ({percentage})
-        </Text>
-        {formatFn && (
-          <Text>
-            {formatFn(value)}/{formatFn(max)}
-          </Text>
-        )}
-      </View>
-      <View style={tw.style("h-3 w-full bg-slate-300 rounded-lg")}>
-        <View
-          style={tw.style("h-full bg-slate-700 rounded-lg", {
-            width: percentage,
-          })}
-        />
-      </View>
-    </View>
-  );
-}
-
-interface GaugeProps {
-  label: string;
-  value: string;
-}
-
-export function Gauge({ label, value }: GaugeProps) {
-  return (
-    <View>
-      <Text>{label}</Text>
-      <Text style={tw.style("text-2xl")}>{value}</Text>
-    </View>
-  );
-}
-
 export default function NodePage() {
   const { node: nodeName } = useSearchParams<{ node: string }>();
   const node = useNode(nodeName);
@@ -105,14 +60,10 @@ export default function NodePage() {
   }, [node.qemu.data]);
 
   return (
-    <View>
-      <SafeAreaView>
-        <ScrollView>
-          <View
-            style={tw.style(
-              "bg-white m-2 p-3 rounded-lg border border-slate-200"
-            )}
-          >
+    <ScrollView>
+      <Card>
+        <View style={tw.style("p-1")}>
+          <View style={tw.style("px-1")}>
             <Text style={tw.style("text-2xl font-semibold")}>{nodeName}</Text>
             {node.status.isSuccess && (
               <View style={tw.style("mb-4")}>
@@ -120,88 +71,78 @@ export default function NodePage() {
                 <Text>{node.status.data.kversion}</Text>
               </View>
             )}
-            {node.rdd.isSuccess && (
-              <View>
-                <View style={tw.style("mb-2")}>
-                  <ProgressBar label="CPU" value={node.rdd.data.cpu} max={1} />
-                </View>
-                <View style={tw.style("mb-2")}>
-                  <ProgressBar
-                    label="Memory"
-                    value={node.rdd.data.memused}
-                    max={node.rdd.data.memtotal}
-                    formatFn={formatBytes}
+          </View>
+          {node.rdd.isSuccess && (
+            <View>
+              <View style={tw.style("mb-2")}>
+                <ProgressBar label="CPU" value={node.rdd.data.cpu} max={1} />
+              </View>
+              <View style={tw.style("mb-2")}>
+                <ProgressBar
+                  label="Memory"
+                  value={node.rdd.data.memused}
+                  max={node.rdd.data.memtotal}
+                  formatFn={formatBytes}
+                />
+              </View>
+              <View style={tw.style("mb-2")}>
+                <ProgressBar
+                  label="Storage"
+                  value={node.rdd.data.rootused}
+                  max={node.rdd.data.roottotal}
+                  formatFn={formatBytes}
+                />
+              </View>
+              <View
+                style={tw.style(
+                  "flex flex-row justify-evenly items-center p-2"
+                )}
+              >
+                <View style={tw.style("flex-1")}>
+                  <Gauge
+                    label="Up"
+                    value={`${formatBytes(node.rdd.data.netout)}/s`}
                   />
                 </View>
-                <View style={tw.style("mb-2")}>
-                  <ProgressBar
-                    label="Storage"
-                    value={node.rdd.data.rootused}
-                    max={node.rdd.data.roottotal}
-                    formatFn={formatBytes}
+                <View style={tw.style("flex-1")}>
+                  <Gauge
+                    label="Down"
+                    value={`${formatBytes(node.rdd.data.netin)}/s`}
                   />
                 </View>
-                <View
-                  style={tw.style(
-                    "flex flex-row justify-evenly items-center p-2"
-                  )}
-                >
-                  <View style={tw.style("flex-1")}>
-                    <Gauge
-                      label="Up"
-                      value={`${formatBytes(node.rdd.data.netout)}/s`}
-                    />
-                  </View>
-                  <View style={tw.style("flex-1")}>
-                    <Gauge
-                      label="Down"
-                      value={`${formatBytes(node.rdd.data.netin)}/s`}
-                    />
-                  </View>
-                </View>
               </View>
-            )}
-          </View>
-          <Text style={tw.style("ml-6 mt-4")}>LXC Containers</Text>
-          <View
-            style={tw.style(
-              "bg-white m-2 p-1 rounded-lg border border-slate-200"
-            )}
+            </View>
+          )}
+        </View>
+      </Card>
+      <Card label="LXC Containers">
+        {sortedLXCs.map((lxc) => (
+          <Link
+            key={lxc.vmid}
+            href={{
+              pathname: "/nodes/[name]/lxc/[vmid]",
+              params: { name: nodeName, vmid: lxc.vmid },
+            }}
+            style={tw.style("m-2")}
           >
-            {sortedLXCs.map((lxc) => (
-              <Link
-                key={lxc.vmid}
-                href={{
-                  pathname: "/nodes/[name]/lxc/[vmid]",
-                  params: { name: nodeName, vmid: lxc.vmid },
-                }}
-                style={tw.style("m-2")}
-              >
-                <ResourceListItem type="LXC" resource={lxc} />
-              </Link>
-            ))}
-          </View>
-          <Text style={tw.style("ml-6 mt-4")}>Virtual Machines</Text>
-          <View
-            style={tw.style(
-              "bg-white m-2 p-1 rounded-lg border border-slate-200"
-            )}
+            <ResourceListItem type="LXC" resource={lxc} />
+          </Link>
+        ))}
+      </Card>
+      <Card label="Virtual Machine">
+        {sortedVMs.map((vm) => (
+          <Link
+            key={vm.vmid}
+            href={{
+              pathname: "/nodes/[node]/lxc/[vmid]",
+              params: { node: nodeName, vmid: vm.vmid },
+            }}
+            style={tw.style("m-2")}
           >
-            {sortedVMs.map((vm) => (
-              <Link
-                key={vm.vmid}
-                href={{
-                  pathname: "/nodes/[node]/lxc/[vmid]",
-                  params: { node: nodeName, vmid: vm.vmid },
-                }}
-                style={tw.style("m-2")}
-              >
-                <ResourceListItem type="QEMU" resource={vm} />
-              </Link>
-            ))}
-          </View>
-        </ScrollView>
-      </SafeAreaView>
-    </View>
+            <ResourceListItem type="QEMU" resource={vm} />
+          </Link>
+        ))}
+      </Card>
+    </ScrollView>
   );
 }
diff --git a/app/nodes/index.tsx b/app/nodes/index.tsx
index a2cdb79..1cc298b 100644
--- a/app/nodes/index.tsx
+++ b/app/nodes/index.tsx
@@ -5,6 +5,7 @@ import Icon from "@expo/vector-icons/Feather";
 import tw from "twrnc";
 import { formatPercentage } from "../../lib/helper/format";
 import { ScrollView } from "react-native-gesture-handler";
+import Card from "../../components/Card";
 
 export function NodeListItem({ node }: { node: ProxmoxNode }) {
   return (
@@ -38,43 +39,31 @@ export default function HomePage() {
   const nodes = useNodes();
 
   return (
-    <View>
-      <ScrollView>
-        <SafeAreaView>
-          <Text style={tw.style("ml-6 mt-4")}>Nodes</Text>
-          <View
-            style={tw.style(
-              "bg-white m-2 p-1 rounded-lg border border-slate-200"
-            )}
-          >
-            {nodes.isSuccess &&
-              nodes.data.map((node) => (
-                <Link
-                  key={node.node}
-                  href={{
-                    pathname: "/nodes/[node]",
-                    params: { node: node.node },
-                  }}
-                  disabled={node.status !== "online"}
-                  style={tw.style("p-2")}
-                >
-                  <NodeListItem node={node} />
-                </Link>
-              ))}
-          </View>
+    <ScrollView>
+      <Card label="Nodes">
+        {nodes.isSuccess &&
+          nodes.data.map((node) => (
+            <Link
+              key={node.node}
+              href={{
+                pathname: "/nodes/[node]",
+                params: { node: node.node },
+              }}
+              disabled={node.status !== "online"}
+              style={tw.style("p-2")}
+            >
+              <NodeListItem node={node} />
+            </Link>
+          ))}
+      </Card>
 
-          <Text style={tw.style("ml-6 mt-4")}>Storage</Text>
-          <View
-            style={tw.style(
-              "bg-white m-2 p-1 rounded-lg border border-slate-200"
-            )}
-          >
-            <Text style={tw.style("px-2 py-7 flex flex-row text-center")}>
-              Currently not supported
-            </Text>
-          </View>
-        </SafeAreaView>
-      </ScrollView>
-    </View>
+      <Card label="Storage">
+        <View
+          style={tw.style("flex flex-row justify-center items-center h-16")}
+        >
+          <Text>Currently not supported</Text>
+        </View>
+      </Card>
+    </ScrollView>
   );
 }
diff --git a/components/Card/index.tsx b/components/Card/index.tsx
new file mode 100644
index 0000000..d958b40
--- /dev/null
+++ b/components/Card/index.tsx
@@ -0,0 +1,20 @@
+import { View, Text } from "react-native";
+import tw from "twrnc";
+
+interface CardProps {
+  label?: string;
+  children: React.ReactNode;
+}
+
+export default function Card({ label, children }: CardProps) {
+  return (
+    <>
+      {label && <Text style={tw.style("ml-6 mt-4")}>{label}</Text>}
+      <View
+        style={tw.style("bg-white m-2 p-1 rounded-lg border border-slate-200")}
+      >
+        {children}
+      </View>
+    </>
+  );
+}
diff --git a/components/Gauge/index.tsx b/components/Gauge/index.tsx
new file mode 100644
index 0000000..bc33c6c
--- /dev/null
+++ b/components/Gauge/index.tsx
@@ -0,0 +1,16 @@
+import { View, Text } from "react-native";
+import tw from "twrnc";
+
+interface GaugeProps {
+  label: string;
+  value: string;
+}
+
+export function Gauge({ label, value }: GaugeProps) {
+  return (
+    <View>
+      <Text>{label}</Text>
+      <Text style={tw.style("text-2xl")}>{value}</Text>
+    </View>
+  );
+}
diff --git a/components/Login/index.tsx b/components/Login/index.tsx
index 5ab9524..f72c4e2 100644
--- a/components/Login/index.tsx
+++ b/components/Login/index.tsx
@@ -1,12 +1,49 @@
-import { View, Button, Text, TextInput } from "react-native";
+import {
+  View,
+  TouchableOpacity,
+  Text,
+  TextInput,
+  SafeAreaView,
+  TextInputProps,
+} from "react-native";
 import React, { useState } from "react";
+import tw from "twrnc";
 import useAuthStore from "../../stores/useAuthStore";
 import { useTicketMut } from "../../hooks/useTicket";
 
+interface FormFieldProps extends TextInputProps {
+  label: string;
+}
+
+export function FormField({
+  label,
+  value,
+  onChangeText,
+  placeholder,
+  secureTextEntry,
+  inputMode,
+}: FormFieldProps) {
+  return (
+    <>
+      <Text style={tw.style("ml-2 mb-2 text-slate-500")}>{label}</Text>
+      <TextInput
+        style={tw.style(
+          "border rounded-md border-slate-200 bg-slate-100 px-4 py-2 text-base"
+        )}
+        value={value}
+        onChangeText={onChangeText}
+        placeholder={placeholder}
+        secureTextEntry={secureTextEntry}
+        inputMode={inputMode}
+      />
+    </>
+  );
+}
+
 export default function Login() {
-  const [domain, setDomain] = useState("");
+  const [domain, setDomain] = useState("https://pve.holowaif.us:8006");
   const [username, setUsername] = useState("root@pam");
-  const [password, setPassword] = useState("");
+  const [password, setPassword] = useState("1Absolut3.lyAd0r3Vtube.rs!");
 
   const authStore = useAuthStore();
   const ticketMut = useTicketMut({
@@ -17,23 +54,51 @@ export default function Login() {
   });
 
   return (
-    <View>
-      <Text>Hello</Text>
-      <TextInput value={domain} onChangeText={setDomain} placeholder="Domain" />
-      <TextInput
-        value={username}
-        onChangeText={setUsername}
-        placeholder="Username"
-      />
-      <TextInput
-        value={password}
-        onChangeText={setPassword}
-        placeholder="Password"
-      />
-      <Button
-        title="Login"
-        onPress={() => ticketMut.mutate({ domain, username, password })}
-      />
+    <View style={tw.style("flex-1 justify-center")}>
+      <SafeAreaView style={tw.style("mx-4")}>
+        <View style={tw.style("ml-2 mb-8")}>
+          <Text style={tw.style("text-3xl font-semibold text-slate-700 mb-2")}>
+            PVERN
+          </Text>
+          <Text style={tw.style("text-xl font-semibold text-slate-600")}>
+            Sign In
+          </Text>
+        </View>
+        <View style={tw.style("mb-5")}>
+          <FormField
+            inputMode="url"
+            label="Server URL"
+            placeholder="https://pve.example.com"
+            value={domain}
+            onChangeText={setDomain}
+          />
+        </View>
+        <View style={tw.style("mb-5")}>
+          <FormField
+            autoComplete="username"
+            label="Username"
+            value={username}
+            onChangeText={setUsername}
+          />
+        </View>
+        <View style={tw.style("mb-10")}>
+          <FormField
+            autoComplete="password"
+            label="Password"
+            value={password}
+            onChangeText={setPassword}
+            secureTextEntry
+          />
+        </View>
+        <TouchableOpacity
+          style={tw.style(
+            "flex bg-slate-700 rounded-md flex flex-row items-center justify-center"
+          )}
+          onPress={() => ticketMut.mutate({ domain, username, password })}
+        >
+          <Text style={tw.style("text-white font-semibold py-3")}>Sign In</Text>
+        </TouchableOpacity>
+      </SafeAreaView>
     </View>
   );
 }
diff --git a/components/ProgressBar/index.tsx b/components/ProgressBar/index.tsx
new file mode 100644
index 0000000..641fee9
--- /dev/null
+++ b/components/ProgressBar/index.tsx
@@ -0,0 +1,42 @@
+import { View, Text } from "react-native";
+import tw from "twrnc";
+import { formatPercentage } from "../../lib/helper/format";
+
+interface ProgressBarProps {
+  label: string;
+  value: number;
+  max: number;
+  formatFn?: (input: number) => string;
+}
+
+export default function ProgressBar({
+  label,
+  value,
+  max,
+  formatFn,
+}: ProgressBarProps) {
+  const percentage = formatPercentage(value / max);
+  return (
+    <View>
+      <View
+        style={tw.style("flex flex-row justify-between items-center mb-1 px-1")}
+      >
+        <Text>
+          {label} ({percentage})
+        </Text>
+        {formatFn && (
+          <Text>
+            {formatFn(value)}/{formatFn(max)}
+          </Text>
+        )}
+      </View>
+      <View style={tw.style("h-3 w-full bg-slate-300 rounded-lg")}>
+        <View
+          style={tw.style("h-full bg-slate-700 rounded-lg", {
+            width: percentage,
+          })}
+        />
+      </View>
+    </View>
+  );
+}