diff --git a/package.json b/package.json
index 9ca808dc..b67d537d 100644
--- a/package.json
+++ b/package.json
@@ -15,9 +15,12 @@
},
"dependencies": {
"@stellar/design-system": "^2.0.0-beta.1",
+ "immer": "^10.0.3",
"next": "14.0.4",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "zustand": "^4.5.0",
+ "zustand-querystring": "^0.0.19"
},
"devDependencies": {
"@types/node": "^20",
diff --git a/src/app/(sidebar)/account/create/page.tsx b/src/app/(sidebar)/account/create/page.tsx
index 3aa44803..feb52d34 100644
--- a/src/app/(sidebar)/account/create/page.tsx
+++ b/src/app/(sidebar)/account/create/page.tsx
@@ -1,5 +1,14 @@
"use client";
+import { useStore } from "@/store/useStore";
+
export default function CreateAccount() {
- return
Create Account
;
+ const { network } = useStore();
+
+ return (
+
+ Create Account
+
{`Current network: ${network?.id}`}
+
+ );
}
diff --git a/src/app/(sidebar)/account/fund/page.tsx b/src/app/(sidebar)/account/fund/page.tsx
index afb29bdc..07df3f55 100644
--- a/src/app/(sidebar)/account/fund/page.tsx
+++ b/src/app/(sidebar)/account/fund/page.tsx
@@ -1,5 +1,74 @@
"use client";
+import { useStore } from "@/store/useStore";
+import { Input, Button, Select } from "@stellar/design-system";
+import { useState } from "react";
+
export default function FundAccount() {
- return Fund Account
;
+ const {
+ account: { value, update, updateNested, reset },
+ } = useStore();
+
+ const [testValue, setTestValue] = useState(value);
+
+ const handleUpdateNested = (event: any) => {
+ const val = event.target.value;
+ let submitValue = {
+ nestedValue1: "AAA",
+ nestedValue2: 111,
+ };
+
+ if (val === "Two") {
+ submitValue = {
+ nestedValue1: "BBB",
+ nestedValue2: 222,
+ };
+ }
+
+ updateNested(submitValue);
+ };
+
+ return (
+
+ Fund Account
+
+
{`Test value: ${value}`}
+
+
Store value is updated on blur
+
+
setTestValue(event.target.value)}
+ onBlur={(event) => update(event.target.value)}
+ label="Value"
+ />
+
+
Testing nested object update
+
+
+
+
+
+
+ );
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index d4fca4bc..c196d181 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { LayoutMain } from "@/components/layout/LayoutMain";
+import { StoreProvider } from "@/store/StoreProvider";
import "@stellar/design-system/build/styles.min.css";
import "@/styles/globals.scss";
@@ -20,7 +21,9 @@ export default function RootLayout({
- {children}
+
+ {children}
+
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
index 0625ac2c..356ffb86 100644
--- a/src/app/not-found.tsx
+++ b/src/app/not-found.tsx
@@ -6,6 +6,7 @@ import { Routes } from "@/constants/routes";
import { LayoutContentContainer } from "@/components/layout/LayoutContentContainer";
// TODO: update 404
+// TODO: keep searchParams
export default function NotFound() {
return (
diff --git a/src/components/MainNav.tsx b/src/components/MainNav.tsx
index 04bfdf74..f7ee0c26 100644
--- a/src/components/MainNav.tsx
+++ b/src/components/MainNav.tsx
@@ -34,6 +34,7 @@ export const MainNav = () => {
const pathname = usePathname();
const searchParams = useSearchParams();
+ // TODO: remove after testing
console.log(">>> pathname: ", pathname);
console.log(">>> searchParams: ", searchParams.toString());
diff --git a/src/components/NetworkSelector.tsx b/src/components/NetworkSelector.tsx
new file mode 100644
index 00000000..b38b2f2a
--- /dev/null
+++ b/src/components/NetworkSelector.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+// TODO: update Network selector. This is a demo for state/store.
+
+import { useCallback, useEffect } from "react";
+import { Select } from "@stellar/design-system";
+import { useStore } from "@/store/useStore";
+
+export const NetworkSelector = () => {
+ const { network, selectNetwork } = useStore();
+
+ const setNetwork = useCallback(() => {
+ if (!network?.id) {
+ // TODO: get from local storage
+ selectNetwork({
+ id: "testnet",
+ label: "testnet",
+ url: "",
+ passphrase: "",
+ });
+ }
+ }, [network?.id, selectNetwork]);
+
+ // Set default network on launch
+ useEffect(() => {
+ setNetwork();
+ }, [setNetwork]);
+
+ return (
+
+ {`Network: ${network?.id}`}
+
+
+ );
+};
diff --git a/src/components/layout/LayoutMain.tsx b/src/components/layout/LayoutMain.tsx
index 05fd6127..e89a4eb3 100644
--- a/src/components/layout/LayoutMain.tsx
+++ b/src/components/layout/LayoutMain.tsx
@@ -6,6 +6,7 @@ import Link from "next/link";
import { ProjectLogo, ThemeSwitch } from "@stellar/design-system";
import { MainNav } from "@/components/MainNav";
+import { NetworkSelector } from "@/components/NetworkSelector";
export const LayoutMain = ({ children }: { children: ReactNode }) => {
return (
@@ -20,6 +21,7 @@ export const LayoutMain = ({ children }: { children: ReactNode }) => {
+
diff --git a/src/store/StoreProvider.tsx b/src/store/StoreProvider.tsx
new file mode 100644
index 00000000..0d545854
--- /dev/null
+++ b/src/store/StoreProvider.tsx
@@ -0,0 +1,23 @@
+"use client";
+import { createContext, ReactNode, useState } from "react";
+import { usePathname, useSearchParams } from "next/navigation";
+import { createStore } from "@/store/createStore";
+
+export type StoreType = ReturnType;
+export const ZustandContext = createContext(null);
+
+export const StoreProvider = ({ children }: { children: ReactNode }) => {
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+ const url = `${pathname}?${searchParams}`;
+
+ const [store] = useState(() =>
+ createStore({
+ url,
+ }),
+ );
+
+ return (
+ {children}
+ );
+};
diff --git a/src/store/createStore.ts b/src/store/createStore.ts
new file mode 100644
index 00000000..f988df3b
--- /dev/null
+++ b/src/store/createStore.ts
@@ -0,0 +1,83 @@
+import { create } from "zustand";
+import { immer } from "zustand/middleware/immer";
+import { querystring } from "zustand-querystring";
+import { EmptyObj } from "@/types/types";
+
+type Network = {
+ id: string;
+ label: string;
+ url: string;
+ passphrase: string;
+};
+
+export interface Store {
+ // Shared
+ network: Network | EmptyObj;
+ selectNetwork: (network: Network) => void;
+
+ // Account
+ account: {
+ value: string;
+ nestedObject: {
+ nestedValue1: string;
+ nestedValue2: number;
+ };
+ update: (value: string) => void;
+ updateNested: (nestedVal: {
+ nestedValue1: string;
+ nestedValue2: number;
+ }) => void;
+ reset: () => void;
+ };
+}
+
+interface CreateStoreOptions {
+ url?: string;
+}
+
+// Store
+export const createStore = (options: CreateStoreOptions) =>
+ create()(
+ querystring(
+ immer((set) => ({
+ network: {},
+ selectNetwork: (network: Network) =>
+ set((state) => {
+ state.network = network;
+ }),
+ account: {
+ value: "",
+ nestedObject: {
+ nestedValue1: "",
+ nestedValue2: 0,
+ },
+ update: (value: string) =>
+ set((state) => {
+ state.account.value = value;
+ }),
+ updateNested: (nestedVal: {
+ nestedValue1: string;
+ nestedValue2: number;
+ }) =>
+ set((state) => {
+ state.account.nestedObject = nestedVal;
+ }),
+ reset: () =>
+ set((state) => {
+ state.account.value = "";
+ }),
+ },
+ })),
+ {
+ url: options.url,
+ // Select what to save in query string
+ select() {
+ return {
+ network: true,
+ account: true,
+ };
+ },
+ key: "||",
+ },
+ ),
+ );
diff --git a/src/store/useStore.ts b/src/store/useStore.ts
new file mode 100644
index 00000000..3545f3b2
--- /dev/null
+++ b/src/store/useStore.ts
@@ -0,0 +1,12 @@
+"use client";
+import { useContext } from "react";
+import { useStore as useZustandStore } from "zustand";
+import { Store } from "@/store/createStore";
+import { ZustandContext } from "@/store/StoreProvider";
+
+export const useStore = (selector?: (state: Store) => T) => {
+ selector ??= (state) => state as T;
+ const store = useContext(ZustandContext);
+ if (!store) throw new Error("Store is missing the provider");
+ return useZustandStore(store, selector);
+};
diff --git a/src/types/types.ts b/src/types/types.ts
new file mode 100644
index 00000000..f90e5711
--- /dev/null
+++ b/src/types/types.ts
@@ -0,0 +1 @@
+export type EmptyObj = Record;
diff --git a/yarn.lock b/yarn.lock
index a0e5370b..0fb2bea6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1352,6 +1352,11 @@ ignore@^5.2.0:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==
+immer@^10.0.3:
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/immer/-/immer-10.0.3.tgz#a8de42065e964aa3edf6afc282dfc7f7f34ae3c9"
+ integrity sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==
+
immutable@^4.0.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f"
@@ -1717,6 +1722,11 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
+lodash-es@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
+ integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
+
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -2583,6 +2593,11 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
+use-sync-external-store@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
+ integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
+
watchpack@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
@@ -2708,3 +2723,17 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+
+zustand-querystring@^0.0.19:
+ version "0.0.19"
+ resolved "https://registry.yarnpkg.com/zustand-querystring/-/zustand-querystring-0.0.19.tgz#18ac1c9258a21c6345aa09dbf8738133471c2ebd"
+ integrity sha512-+lLMisaJOgEiEochoCYKKwodR4Gd6RdIcJu4j2uuM5HE/8YzXVwKEAWc/XnU46ixXT8dj33wpedm0+iWqgjOSQ==
+ dependencies:
+ lodash-es "^4.17.21"
+
+zustand@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.0.tgz#141354af56f91de378aa6c4b930032ab338f3ef0"
+ integrity sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==
+ dependencies:
+ use-sync-external-store "1.2.0"