diff --git a/packages/manager/src/managers/git/GitManager.ts b/packages/manager/src/managers/git/GitManager.ts index dfa81bae06..b260b66237 100644 --- a/packages/manager/src/managers/git/GitManager.ts +++ b/packages/manager/src/managers/git/GitManager.ts @@ -10,14 +10,14 @@ import { UnauthorizedError, UnexpectedDataError } from "../../errors"; import { BaseManager } from "../BaseManager"; -import { GitRepo, GitRepoSpcifier, Namespace } from "./types"; +import { GitRepo, GitRepoSpcifier, Owner } from "./types"; type CreateGitHubAuthStateReturnType = { key: string; expiresAt: Date; }; -type GitManagerFetchOwnersReturnType = Namespace[]; +type GitManagerFetchOwnersReturnType = Owner[]; type GitManagerFetchReposReturnType = GitRepo[]; diff --git a/packages/manager/src/managers/git/types.ts b/packages/manager/src/managers/git/types.ts index ca02175bc3..f1b6ce68aa 100644 --- a/packages/manager/src/managers/git/types.ts +++ b/packages/manager/src/managers/git/types.ts @@ -1,10 +1,12 @@ -export type Namespace = { +export type Owner = { + provider: "gitHub"; id: string; name: string; type: "user" | "team" | null; }; export type GitRepo = { + provider: "gitHub"; id: string; owner: string; name: string; @@ -13,6 +15,7 @@ export type GitRepo = { }; export type GitRepoSpcifier = { + provider: "gitHub"; owner: string; name: string; }; diff --git a/packages/slice-machine/pages/settings.tsx b/packages/slice-machine/pages/settings.tsx index 23686f25fc..0328b190fe 100644 --- a/packages/slice-machine/pages/settings.tsx +++ b/packages/slice-machine/pages/settings.tsx @@ -7,27 +7,9 @@ import { AppLayoutContent, AppLayoutHeader, } from "@components/AppLayout"; -import { Button } from "@prismicio/editor-ui"; -import { managerClient } from "@src/managerClient"; +import { ConnectGitRepository } from "@src/features/git/ConnectGitRepository/ConnectGitRepository"; const Settings: React.FC = () => { - const connect = async (provider: "gitHub") => { - switch (provider) { - case "gitHub": { - const state = await managerClient.git.createGitHubAuthState(); - - const url = new URL( - "https://github.com/apps/prismic-push-models-poc/installations/new", - ); - url.searchParams.set("state", state.key); - - window.open(url, "git-hub-app-installation"); - - return; - } - } - }; - return ( <> @@ -39,21 +21,7 @@ const Settings: React.FC = () => { -
- Connected Git Repository -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-

Learn more about Prismic for Git

-
+
diff --git a/packages/slice-machine/src/features/git/ConnectGitRepository/ConnectGitRepository.module.css b/packages/slice-machine/src/features/git/ConnectGitRepository/ConnectGitRepository.module.css new file mode 100644 index 0000000000..c3ba8bfad8 --- /dev/null +++ b/packages/slice-machine/src/features/git/ConnectGitRepository/ConnectGitRepository.module.css @@ -0,0 +1,15 @@ +.root { + max-width: 600px; +} + +.buttons { + display: grid; + grid-auto-columns: 1fr; + grid-auto-flow: column; + gap: 8px; +} + +.owners { + display: grid; + gap: 8px; +} diff --git a/packages/slice-machine/src/features/git/ConnectGitRepository/ConnectGitRepository.tsx b/packages/slice-machine/src/features/git/ConnectGitRepository/ConnectGitRepository.tsx new file mode 100644 index 0000000000..d17035e9da --- /dev/null +++ b/packages/slice-machine/src/features/git/ConnectGitRepository/ConnectGitRepository.tsx @@ -0,0 +1,286 @@ +import { PropsWithChildren, Suspense, useState } from "react"; +import { + Button, + ErrorBoundary, + ProgressCircle, + Select, + SelectItem, + Table, + TableBody, + TableCell, + TableRow, + Text, +} from "@prismicio/editor-ui"; + +import useSliceMachineActions from "@src/modules/useSliceMachineActions"; +import { managerClient } from "@src/managerClient"; +import { useSliceMachineConfig } from "@src/hooks/useSliceMachineConfig"; +import { useUser } from "@src/hooks/useUser"; + +import { useGitOwners } from "../useGitOwners"; +import { useGitRepos } from "../useGitRepos"; +import { useLinkedGitRepos } from "../useLinkedGitRepos"; +import { useLinkedGitReposActions } from "../useLinkedGitReposActions"; + +import * as styles from "./ConnectGitRepository.module.css"; + +// TODO: Export types from `@slicemachine/manager` +type GitOwner = Awaited< + ReturnType<(typeof managerClient)["git"]["fetchOwners"]> +>[number]; +type GitRepo = Awaited< + ReturnType<(typeof managerClient)["git"]["fetchRepos"]> +>[number]; + +type ConnectButtonBaseProps = PropsWithChildren<{ + provider: "gitHub" | "gitLab" | "bitbucket"; + disabled?: boolean; +}>; + +function ConnectButton(props: ConnectButtonBaseProps) { + const { provider, children, disabled } = props; + + const [isLoading, setIsLoading] = useState(false); + + const connect = async () => { + switch (provider) { + case "gitHub": { + setIsLoading(true); + + const state = await managerClient.git.createGitHubAuthState(); + + const url = new URL( + "https://github.com/apps/prismic-push-models-poc/installations/new", + ); + url.searchParams.set("state", state.key); + + window.open(url, "git-hub-app-installation"); + + return; + } + } + }; + + return ( + + ); +} + +function ConnectButtons() { + return ( + + ); +} + +type SelectOwnerBaseProps = { + owners: GitOwner[]; + onSelect: (owner: GitOwner) => void; +}; + +function SelectOwnerBase(props: SelectOwnerBaseProps) { + const { owners, onSelect } = props; + + const onValueChange = (value: string) => { + const [provider, id] = value.split("@"); + const owner = owners.find((o) => o.provider === provider && o.id === id); + + if (owner) { + onSelect(owner); + } + }; + + return ( + + ); +} + +function SelectOwner(props: SelectOwnerBaseProps) { + return ( + }> + + + ); +} + +type SelectRepoBaseProps = { + owner?: { + provider: "gitHub"; + name: string; + }; + onSelect: (repo: GitRepo) => void; +}; + +function SelectRepoBase(props: SelectRepoBaseProps) { + const { onSelect } = props; + + const repos = useGitRepos( + props.owner + ? { + provider: props.owner?.provider, + owner: props.owner?.name, + } + : undefined, + ); + + return ( + + + {repos?.map((repo) => { + return ( + + + + {repo.name} + + + + + + + ); + })} + +
+ ); +} + +function SelectRepo(props: SelectRepoBaseProps) { + return ( + }> + + + ); +} + +function RepoSelector() { + const [config] = useSliceMachineConfig(); + const owners = useGitOwners(); + const { linkRepo } = useLinkedGitReposActions({ + prismic: { domain: config.repositoryName }, + }); + + const [selectedOwner, setSelectedOwner] = useState(); + + if (owners.length < 1) { + return ; + } + + return ( +
+ + setSelectedOwner(owner)} + /> + + {selectedOwner ? ( + void linkRepo(repo)} + /> + ) : ( +
Select a user/owner first
+ )} +
+ ); +} + +function LoggedInContents() { + const [config] = useSliceMachineConfig(); + const linkedGitRepos = useLinkedGitRepos({ + prismic: { domain: config.repositoryName }, + }); + const { unlinkRepo } = useLinkedGitReposActions({ + prismic: { domain: config.repositoryName }, + }); + + if ("error" in linkedGitRepos) { + return
TODO: Handle error
; + } + + if (linkedGitRepos.repos.length === 0) { + return ; + } + + const linkedRepo = linkedGitRepos.repos[0]; + + return ( +
+ Linked: [{linkedRepo.provider}] {linkedRepo.owner}/{linkedRepo.name} + +
+ ); +} + +function LoggedOutContents() { + const { openLoginModal } = useSliceMachineActions(); + + return ( +
+ You must be logged in to connect a Git repository. + +
+ ); +} + +function Contents() { + const { isLoggedIn } = useUser(); + + if (!isLoggedIn) { + return ; + } + + return ; +} + +export function ConnectGitRepository() { + return ( +
+ Connected Git Repository + { + return
Error
; + }} + > + }> + + +
+
+ ); +} diff --git a/packages/slice-machine/src/features/git/useGitOwners.ts b/packages/slice-machine/src/features/git/useGitOwners.ts new file mode 100644 index 0000000000..4ab5e7da9f --- /dev/null +++ b/packages/slice-machine/src/features/git/useGitOwners.ts @@ -0,0 +1,10 @@ +import { useRequest } from "@prismicio/editor-support/Suspense"; +import { managerClient } from "@src/managerClient"; + +const getGitOwners = async () => { + return await managerClient.git.fetchOwners(); +}; + +export const useGitOwners = () => { + return useRequest(getGitOwners, []); +}; diff --git a/packages/slice-machine/src/features/git/useGitRepos.ts b/packages/slice-machine/src/features/git/useGitRepos.ts new file mode 100644 index 0000000000..ca445d3723 --- /dev/null +++ b/packages/slice-machine/src/features/git/useGitRepos.ts @@ -0,0 +1,16 @@ +import { useRequest } from "@prismicio/editor-support/Suspense"; +import { managerClient } from "@src/managerClient"; + +type UseGitReposArgs = Parameters[0]; + +const getGitRepos = async (args?: UseGitReposArgs) => { + if (!args) { + return; + } + + return await managerClient.git.fetchRepos(args); +}; + +export const useGitRepos = (args?: UseGitReposArgs) => { + return useRequest(getGitRepos, [args]); +}; diff --git a/packages/slice-machine/src/features/git/useLinkedGitRepos.ts b/packages/slice-machine/src/features/git/useLinkedGitRepos.ts new file mode 100644 index 0000000000..71188028bf --- /dev/null +++ b/packages/slice-machine/src/features/git/useLinkedGitRepos.ts @@ -0,0 +1,30 @@ +import { useRequest } from "@prismicio/editor-support/Suspense"; +import { managerClient } from "@src/managerClient"; + +type PrismicRepoSpecifier = { + domain: string; +}; + +type UseLinkedGitReposReturnType = + | { + repos: Awaited< + ReturnType<(typeof managerClient)["git"]["fetchLinkedRepos"]> + >; + } + | { error: unknown }; + +export const getLinkedGitRepos = async (args: { + prismic: PrismicRepoSpecifier; +}): Promise => { + try { + const repos = await managerClient.git.fetchLinkedRepos(args); + + return { repos }; + } catch (error) { + return { error }; + } +}; + +export const useLinkedGitRepos = (args: { prismic: PrismicRepoSpecifier }) => { + return useRequest(getLinkedGitRepos, [args]); +}; diff --git a/packages/slice-machine/src/features/git/useLinkedGitReposActions.ts b/packages/slice-machine/src/features/git/useLinkedGitReposActions.ts new file mode 100644 index 0000000000..f6d6ab8467 --- /dev/null +++ b/packages/slice-machine/src/features/git/useLinkedGitReposActions.ts @@ -0,0 +1,36 @@ +import { revalidateData } from "@prismicio/editor-support/Suspense"; +import { managerClient } from "@src/managerClient"; + +import { getLinkedGitRepos } from "./useLinkedGitRepos"; + +type PrismicRepoSpecifier = { + domain: string; +}; +type GitRepoSpecifier = { + provider: "gitHub"; + owner: string; + name: string; +}; + +export const useLinkedGitReposActions = (args: { + prismic: PrismicRepoSpecifier; +}) => { + return { + linkRepo: async (git: GitRepoSpecifier) => { + await managerClient.git.linkRepo({ + prismic: { domain: args.prismic.domain }, + git, + }); + + revalidateData(getLinkedGitRepos, [args]); + }, + unlinkRepo: async (git: GitRepoSpecifier) => { + await managerClient.git.unlinkRepo({ + prismic: { domain: args.prismic.domain }, + git, + }); + + revalidateData(getLinkedGitRepos, [args]); + }, + }; +}; diff --git a/packages/slice-machine/src/hooks/useUser.ts b/packages/slice-machine/src/hooks/useUser.ts new file mode 100644 index 0000000000..280b1f1af0 --- /dev/null +++ b/packages/slice-machine/src/hooks/useUser.ts @@ -0,0 +1,14 @@ +import { useRequest } from "@prismicio/editor-support/Suspense"; +import { managerClient } from "@src/managerClient"; + +const getUser = async () => { + const isLoggedIn = await managerClient.user.checkIsLoggedIn(); + + return { + isLoggedIn, + }; +}; + +export const useUser = () => { + return useRequest(getUser, []); +};