|
| 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 | +}; |
0 commit comments