diff --git a/apps/generic-issuance-client/.env.example b/apps/generic-issuance-client/.env.example index 782b383fe1..c6a140dbf7 100644 --- a/apps/generic-issuance-client/.env.example +++ b/apps/generic-issuance-client/.env.example @@ -1,3 +1,6 @@ # This token is used to authenticate Stytch requests for user authentication. # If you are on the team, message @rrrliu to get one. -STYTCH_PUBLIC_TOKEN= \ No newline at end of file +STYTCH_PUBLIC_TOKEN= + +PASSPORT_CLIENT_URL="http://localhost:3000" +PASSPORT_SERVER_URL="http://localhost:3002" \ No newline at end of file diff --git a/apps/generic-issuance-client/build.ts b/apps/generic-issuance-client/build.ts index 82a7d1179d..ad9c1259e6 100644 --- a/apps/generic-issuance-client/build.ts +++ b/apps/generic-issuance-client/build.ts @@ -12,7 +12,13 @@ const genericIssuanceClientAppOpts: BuildOptions = { define: { "process.env.NODE_ENV": `'${process.env.NODE_ENV}'`, "process.env.STYTCH_PUBLIC_TOKEN": `'${process.env.STYTCH_PUBLIC_TOKEN}'`, - "process.env.GENERIC_ISSUANCE_CLIENT_URL": `'${process.env.GENERIC_ISSUANCE_CLIENT_URL}'` + "process.env.GENERIC_ISSUANCE_CLIENT_URL": `'${process.env.GENERIC_ISSUANCE_CLIENT_URL}'`, + "process.env.PASSPORT_SERVER_URL": JSON.stringify( + process.env.PASSPORT_SERVER_URL || "http://localhost:3002" + ), + "process.env.PASSPORT_CLIENT_URL": JSON.stringify( + process.env.PASSPORT_CLIENT_URL || "http://localhost:3000" + ) }, entryPoints: ["src/main.tsx"], plugins: [ diff --git a/apps/generic-issuance-client/package.json b/apps/generic-issuance-client/package.json index 6add00a144..29471575eb 100644 --- a/apps/generic-issuance-client/package.json +++ b/apps/generic-issuance-client/package.json @@ -12,11 +12,13 @@ "clean": "rm -rf node_modules public/js tsconfig.tsbuildinfo" }, "dependencies": { + "@pcd/passport-interface": "0.10.0", "@stytch/react": "^15.0.0", "@stytch/vanilla-js": "^4.4.2", "dotenv": "^16.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^9.1.0", "react-router-dom": "^6.9.0", "styled-components": "^5.3.6" }, diff --git a/apps/generic-issuance-client/src/constants.ts b/apps/generic-issuance-client/src/constants.ts index 293c34ca7b..257f5dbf05 100644 --- a/apps/generic-issuance-client/src/constants.ts +++ b/apps/generic-issuance-client/src/constants.ts @@ -1,16 +1,4 @@ export const PCD_GITHUB_URL = "https://github.com/proofcarryingdata/pcd"; -export const IS_PROD = process.env.NODE_ENV === "production"; -export const IS_STAGING = process.env.NODE_ENV === "staging"; - -export const ZUPASS_URL = IS_PROD - ? "https://zupass.org/" - : IS_STAGING - ? "https://staging.zupass.org/" - : "http://localhost:3000/"; - -export const ZUPASS_SERVER_URL = IS_PROD - ? "https://api.zupass.org/" - : IS_STAGING - ? "https://api-staging.zupass.org/" - : "http://localhost:3002/"; +export const ZUPASS_URL = process.env.PASSPORT_CLIENT_URL; +export const ZUPASS_SERVER_URL = process.env.PASSPORT_SERVER_URL; diff --git a/apps/generic-issuance-client/src/main.tsx b/apps/generic-issuance-client/src/main.tsx index f3065d0c99..d18376b4b8 100644 --- a/apps/generic-issuance-client/src/main.tsx +++ b/apps/generic-issuance-client/src/main.tsx @@ -6,12 +6,14 @@ import { createHashRouter, RouterProvider } from "react-router-dom"; import { GlobalStyle } from "./components/GlobalStyle"; import Dashboard from "./pages/Dashboard"; import Home from "./pages/Home"; +import Pipeline from "./pages/Pipeline"; const stytch = new StytchUIClient(process.env.STYTCH_PUBLIC_TOKEN); const router = createHashRouter([ { path: "/", element: }, - { path: "/dashboard", element: } + { path: "/dashboard", element: }, + { path: "/pipelines/:id", element: } ]); createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/apps/generic-issuance-client/src/pages/Dashboard.tsx b/apps/generic-issuance-client/src/pages/Dashboard.tsx index 29ecd90ff1..72fa5a2fc1 100644 --- a/apps/generic-issuance-client/src/pages/Dashboard.tsx +++ b/apps/generic-issuance-client/src/pages/Dashboard.tsx @@ -1,24 +1,75 @@ +import { + PipelineDefinition, + requestGenericIssuanceGetAllUserPipelines, + requestGenericIssuanceUpsertPipeline +} from "@pcd/passport-interface"; import { useStytch, useStytchUser } from "@stytch/react"; -import { ReactNode, useEffect, useState } from "react"; +import { ReactNode, useCallback, useEffect, useState } from "react"; +import { Link } from "react-router-dom"; import { ZUPASS_SERVER_URL } from "../constants"; +const SAMPLE_CREATE_PIPELINE_TEXT = JSON.stringify( + { + type: "Lemonade", + editorUserIds: [], + options: { + lemonadeApiKey: "your-lemonade-api-key", + events: [] + } + }, + null, + 2 +); + export default function Dashboard(): ReactNode { const stytchClient = useStytch(); const { user } = useStytchUser(); const [isLoggingOut, setLoggingOut] = useState(false); - const [userPingMessage, setUserPingMessage] = useState(""); + // TODO: After MVP, replace with RTK hooks or a more robust state management. + const [pipelines, setPipelines] = useState([]); + const [isLoading, setLoading] = useState(true); + const [isCreatingPipeline, setCreatingPipeline] = useState(false); + const [newPipelineRaw, setNewPipelineRaw] = useState( + SAMPLE_CREATE_PIPELINE_TEXT + ); const [error, setError] = useState(""); - useEffect(() => { - setUserPingMessage("Pinging server..."); - fetch(new URL("generic-issuance/api/user/ping", ZUPASS_SERVER_URL).href, { - credentials: "include" - }) - .then((res) => res.json()) - .then((message) => setUserPingMessage(`JWT valid, received ${message}.`)) - .catch((e) => setUserPingMessage(`Error: ${e}`)); + const fetchAllPipelines = useCallback(async () => { + setLoading(true); + const res = + await requestGenericIssuanceGetAllUserPipelines(ZUPASS_SERVER_URL); + if (res.success) { + setPipelines(res.value); + } else { + // TODO: Better errors + alert(`An error occurred while fetching user pipelines: ${res.error}`); + } + setLoading(false); }, []); + useEffect(() => { + fetchAllPipelines(); + }, [fetchAllPipelines]); + + const createPipeline = async (): Promise => { + if (!newPipelineRaw) return; + const res = await requestGenericIssuanceUpsertPipeline( + ZUPASS_SERVER_URL, + JSON.parse(newPipelineRaw) + ); + await fetchAllPipelines(); + if (res.success) { + setCreatingPipeline(false); + } else { + // TODO: Better errors + alert(`An error occurred while creating pipeline: ${res.error}`); + } + }; + + if (isLoading) { + return
Loading...
; + } + if (!user) { window.location.href = "/"; } @@ -36,20 +87,53 @@ export default function Dashboard(): ReactNode {

Congrats - you are now logged in as {user.emails?.[0]?.email}.

- {userPingMessage &&

{userPingMessage}

} + +

My Pipelines

+ {!pipelines.length &&

No pipelines right now - go create some!

} + {!!pipelines.length && ( +
    + {pipelines.map((p) => ( + +
  1. + id: {p.id}, type: {p.type} +
  2. + + ))} +
+ )} +

+ + {isCreatingPipeline && ( +

+