Skip to content

Commit 6b9e07a

Browse files
drew-harrisskeptrunedev
authored andcommitted
feat: org layout
1 parent f789c7f commit 6b9e07a

File tree

11 files changed

+296
-20
lines changed

11 files changed

+296
-20
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useContext } from "solid-js";
2+
import { UserContext } from "../contexts/UserContext";
3+
import { FiChevronDown, FiUser } from "solid-icons/fi";
4+
5+
export const NavbarOrgWidget = () => {
6+
const userInfo = useContext(UserContext);
7+
return (
8+
<div class="relative">
9+
<div class="flex items-center gap-2 rounded-md border border-neutral-200 bg-neutral-100 p-1 px-2 text-sm">
10+
<FiUser class="text-neutral-500" />
11+
<div>{userInfo.user().email}</div>
12+
<FiChevronDown />
13+
</div>
14+
</div>
15+
);
16+
};
Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
1-
import { createMemo, useContext } from "solid-js";
1+
import { useContext } from "solid-js";
22
import { UserContext } from "../contexts/UserContext";
33

44
export const OrgName = () => {
55
const userContext = useContext(UserContext);
66

7-
const selectedOrganization = createMemo(() => {
8-
const selectedOrgId = userContext.selectedOrganizationId?.();
9-
if (!selectedOrgId) return null;
10-
return userContext.user?.()?.orgs.find((org) => org.id === selectedOrgId);
11-
});
12-
137
return (
148
<h3 class="text-xl font-semibold text-neutral-600">
15-
{selectedOrganization()?.name} Organization
9+
{userContext.selectedOrganization().name}
1610
</h3>
1711
);
1812
};

frontends/dashboard/src/components/OrgTabs.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,36 @@ export const OrgTabs = () => {
1212
return (
1313
<div class="flex space-x-4">
1414
<A
15-
href={`/dashboard/${userContext.selectedOrganizationId?.()}/overview?org=${currentOrgId()}`}
15+
end={true}
16+
href={`/org/`}
1617
activeClass="border-b-2 -mb-[1px] border-magenta-500"
1718
class="hover:text-fuchsia-800"
1819
>
1920
Overview
2021
</A>
2122
<A
22-
href={`/dashboard/${userContext.selectedOrganizationId?.()}/users?org=${currentOrgId()}`}
23+
href={"/org/users"}
2324
activeClass="border-b-2 -mb-[1px] border-magenta-500"
2425
class="hover:text-fuchsia-800"
2526
>
2627
Users
2728
</A>
2829
<A
29-
href={`/dashboard/${userContext.selectedOrganizationId?.()}/billing?org=${currentOrgId()}`}
30+
href="/org/billing"
3031
activeClass="border-b-2 -mb-[1px] border-magenta-500"
3132
class="hover:text-fuchsia-800"
3233
>
3334
Billing
3435
</A>
3536
<A
36-
href={`/dashboard/${userContext.selectedOrganizationId?.()}/api-keys?org=${currentOrgId()}`}
37+
href="/org/keys"
3738
activeClass="border-b-2 -mb-[1px] border-magenta-500"
3839
class="hover:text-fuchsia-800"
3940
>
4041
API Keys
4142
</A>
4243
<A
43-
href={`/dashboard/${userContext.selectedOrganizationId?.()}/settings?org=${currentOrgId()}`}
44+
href="/org/settings"
4445
activeClass="border-b-2 -mb-[1px] border-magenta-500"
4546
class="hover:text-fuchsia-800"
4647
>

frontends/dashboard/src/components/Sidebar.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { JSX } from "solid-js";
33
import { DatasetContext } from "../contexts/DatasetContext";
44
import { A, useLocation } from "@solidjs/router";
55
import {
6-
AiOutlineCiCircle,
76
AiOutlineHistory,
87
AiOutlineInfoCircle,
98
AiOutlineKey,
@@ -19,7 +18,6 @@ import { UserContext } from "../contexts/UserContext";
1918
import { IconTypes } from "solid-icons";
2019
import { IoOptionsOutline } from "solid-icons/io";
2120
import { TbSparkles } from "solid-icons/tb";
22-
import { FaSolidTrash } from "solid-icons/fa";
2321

2422
const searchUiURL = import.meta.env.VITE_SEARCH_UI_URL as string;
2523
const chatUiURL = import.meta.env.VITE_CHAT_UI_URL as string;

frontends/dashboard/src/contexts/DatasetContext.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type DatasetStore = {
1212

1313
export const DatasetContext = createContext<DatasetStore>({
1414
dataset: () => null as unknown as DatasetAndUsage,
15-
selectDataset: (id: string) => {},
15+
selectDataset: (_id: string) => {},
1616
datasetId: "",
1717
});
1818

@@ -37,6 +37,7 @@ export const DatasetContextProvider = (props: { children: JSX.Element }) => {
3737
// replace the pathname
3838
navigate(`/dataset/${id}`);
3939
};
40+
4041
return (
4142
<DatasetContext.Provider
4243
value={{
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import {
2+
Accessor,
3+
JSX,
4+
Resource,
5+
Show,
6+
createContext,
7+
createEffect,
8+
createResource,
9+
createSignal,
10+
useContext,
11+
} from "solid-js";
12+
import { createToast } from "../components/ShowToasts";
13+
import { SlimUser } from "shared/types";
14+
import { redirect, useSearchParams } from "@solidjs/router";
15+
import { ApiContext } from "..";
16+
import { DatasetAndUsage } from "trieve-ts-sdk";
17+
18+
export interface UserStoreContextProps {
19+
children?: JSX.Element;
20+
}
21+
22+
export interface Notification {
23+
message: string;
24+
type: "error" | "success" | "info";
25+
timeout?: number;
26+
}
27+
28+
export interface UserStore {
29+
user: Accessor<SlimUser>;
30+
isNewUser: Accessor<boolean>;
31+
selectedOrganization: Accessor<SlimUser["orgs"][0]>;
32+
orgDatasets: Resource<DatasetAndUsage[]>;
33+
login: () => void;
34+
logout: () => void;
35+
}
36+
37+
export const UserContext = createContext<UserStore>({
38+
user: () => null as unknown as SlimUser,
39+
isNewUser: () => false,
40+
login: () => {},
41+
orgDatasets: null as unknown as Resource<DatasetAndUsage[]>,
42+
logout: () => {},
43+
selectedOrganization: () => null as unknown as SlimUser["orgs"][0],
44+
});
45+
46+
const getInitalUser = () => {
47+
const user = window.localStorage.getItem("trieve:user");
48+
if (user) {
49+
return JSON.parse(user) as SlimUser;
50+
}
51+
};
52+
53+
export const UserContextWrapper = (props: UserStoreContextProps) => {
54+
const [searchParams] = useSearchParams();
55+
const trieve = useContext(ApiContext);
56+
57+
const [user, setUser] = createSignal<SlimUser | null>(
58+
getInitalUser() ?? null,
59+
);
60+
const [isNewUser, setIsNewUser] = createSignal(false);
61+
const [selectedOrganization, setSelectedOrganization] = createSignal<
62+
SlimUser["orgs"][0] | null
63+
>(null);
64+
65+
const apiHost = import.meta.env.VITE_API_HOST as string;
66+
67+
const logout = () => {
68+
void fetch(`${apiHost}/auth?redirect_uri=${window.origin}`, {
69+
method: "DELETE",
70+
credentials: "include",
71+
}).then((res) => {
72+
res
73+
.json()
74+
.then((res) => {
75+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
76+
window.location.href = res.logout_url;
77+
window.localStorage.removeItem("trieve:user");
78+
setUser(null);
79+
setSelectedOrganization(null);
80+
})
81+
.catch((error) => {
82+
console.error(error);
83+
});
84+
});
85+
};
86+
87+
const login = () => {
88+
fetch(`${apiHost}/auth/me`, {
89+
credentials: "include",
90+
})
91+
.then((res) => {
92+
if (res.status === 401) {
93+
window.location.href = `${apiHost}/auth?redirect_uri=${window.origin}/dashboard/foo`;
94+
}
95+
return res.json();
96+
})
97+
.then((data: SlimUser) => {
98+
// cache the user
99+
window.localStorage.setItem("trieve:user", JSON.stringify(data));
100+
101+
// Grab org id from localstorage
102+
const possibleOrgId = window.localStorage.getItem(
103+
`${data.id}:selectedOrg`,
104+
);
105+
if (possibleOrgId) {
106+
const matchingOrg = data.orgs.find((org) => org.id === possibleOrgId);
107+
if (matchingOrg) {
108+
setSelectedOrganization(matchingOrg);
109+
}
110+
} else {
111+
const firstOrg = data.orgs.at(0);
112+
if (firstOrg) {
113+
setSelectedOrganization(firstOrg);
114+
} else {
115+
redirect("/dashboard/new_user");
116+
}
117+
}
118+
119+
setUser(data);
120+
})
121+
.catch((err) => {
122+
setUser(null);
123+
console.error(err);
124+
createToast({
125+
title: "Error",
126+
type: "error",
127+
message: "Error logging in",
128+
});
129+
});
130+
};
131+
132+
createEffect(() => {
133+
if (searchParams["new_user"]) {
134+
setIsNewUser(true);
135+
}
136+
});
137+
138+
const [orgDatasets, _] = createResource(selectedOrganization, async (org) => {
139+
const result = await trieve.fetch(
140+
"/api/dataset/organization/{organization_id}",
141+
"get",
142+
{
143+
organizationId: org.id,
144+
},
145+
);
146+
return result;
147+
});
148+
149+
createEffect(() => {
150+
login();
151+
});
152+
153+
return (
154+
<>
155+
<Show
156+
fallback={
157+
<div class="mt-4 flex min-h-full w-full items-center justify-center">
158+
<div class="mb-28 h-10 w-10 animate-spin rounded-full border-b-2 border-t-2 border-fuchsia-300" />
159+
</div>
160+
}
161+
when={user()}
162+
>
163+
{(user) => (
164+
<Show when={selectedOrganization()}>
165+
{(org) => (
166+
<UserContext.Provider
167+
value={{
168+
user: user,
169+
orgDatasets: orgDatasets,
170+
selectedOrganization: org,
171+
logout,
172+
isNewUser: isNewUser,
173+
login,
174+
}}
175+
>
176+
{props.children}
177+
<Show when={isNewUser()}>
178+
<div>New user!!</div>
179+
</Show>
180+
</UserContext.Provider>
181+
)}
182+
</Show>
183+
)}
184+
</Show>
185+
</>
186+
);
187+
};

frontends/dashboard/src/index.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import { SolidQueryDevtools } from "@tanstack/solid-query-devtools";
99
import { UserContextWrapper } from "./contexts/UserContext.tsx";
1010
import { TrieveFetchClient } from "trieve-ts-sdk";
1111
import { NavbarLayout } from "./layouts/NavbarLayout.tsx";
12-
import { OrganizationHomepage } from "./pages/OrganizationHomepage.tsx";
1312
import { DatasetHomepage } from "./pages/dataset/DatasetHomepage.tsx";
1413
import { DatasetLayout } from "./layouts/DatasetSidebarLayout.tsx";
1514
import { DatasetContextProvider } from "./contexts/DatasetContext.tsx";
1615
import { DatasetEvents } from "./pages/dataset/Events.tsx";
1716
import { ApiKeys } from "./pages/dataset/ApiKeys.tsx";
17+
import { OrganizationLayout } from "./layouts/OrganizationLayout.tsx";
1818

1919
if (!DEV) {
2020
Sentry.init({
@@ -52,7 +52,13 @@ const routes: RouteDefinition[] = [
5252
children: [
5353
{
5454
path: "/",
55-
component: OrganizationHomepage,
55+
component: OrganizationLayout,
56+
children: [
57+
{
58+
path: "*404",
59+
component: () => <div>404</div>,
60+
},
61+
],
5662
},
5763
{
5864
path: "/dataset/:id",
@@ -85,6 +91,11 @@ const routes: RouteDefinition[] = [
8591
path: "/no-org",
8692
component: () => <div>No Org</div>,
8793
},
94+
95+
{
96+
path: "*404",
97+
component: () => <div>404</div>,
98+
},
8899
];
89100

90101
const apiHost = import.meta.env.VITE_API_HOST as string;

frontends/dashboard/src/layouts/DatasetSidebarLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface Props {
88
// Needs to ensure dataset and org don't desync
99
export const DatasetLayout = (props: Props) => {
1010
return (
11-
<div class="grid max-h-full grow grid-cols-[270px_1fr] overflow-hidden">
11+
<div class="grid max-h-full grow grid-cols-[300px_1fr] overflow-hidden">
1212
<DashboardSidebar />
1313
<div class="overflow-scroll p-4">{props.children}</div>
1414
</div>

frontends/dashboard/src/layouts/NavbarLayout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { JSX } from "solid-js";
22
import { A } from "@solidjs/router";
33
import { BiRegularLinkExternal } from "solid-icons/bi";
4+
import { NavbarOrgWidget } from "../components/NavbarOrgWidget";
45

56
interface NavbarLayoutProps {
67
children?: JSX.Element;
@@ -36,6 +37,7 @@ export const NavbarLayout = (props: NavbarLayoutProps) => {
3637
<p>API Docs</p>
3738
<BiRegularLinkExternal class="opacity-80" />
3839
</a>
40+
<NavbarOrgWidget />
3941
</div>
4042
</div>
4143
<div class="flex grow flex-col overflow-scroll bg-neutral-100">

0 commit comments

Comments
 (0)