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 = () => {
-
+
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 (
+
+ -
+ GitHub
+
+ -
+
+ GitLab (soon)
+
+
+ -
+
+ Bitbucket (soon)
+
+
+
+ );
+}
+
+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 (
+
+ );
+}
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, []);
+};