Skip to content

Commit

Permalink
feat: add GitHub team import to new list page (#2006)
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonroberts authored Oct 26, 2023
1 parent 4586408 commit 3113a74
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 7 deletions.
5 changes: 4 additions & 1 deletion components/molecules/AuthSection/auth-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ const AuthSection: React.FC = ({}) => {
<Button
variant="primary"
onClick={async () =>
await signIn({ provider: "github", options: { redirectTo: `${host}${currentPath}` } })
await signIn({
provider: "github",
options: { redirectTo: `${host}${currentPath}` },
})
}
className="flex items-center"
>
Expand Down
132 changes: 132 additions & 0 deletions components/organisms/GitHubTeamSyncDialog/github-team-sync-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { useEffect, useState } from "react";
import { FiGithub } from "react-icons/fi";
import { Dialog, DialogContent } from "components/molecules/Dialog/dialog";

import Title from "components/atoms/Typography/title";
import Text from "components/atoms/Typography/text";
import Button from "components/atoms/Button/button";
import ToggleSwitch from "components/atoms/ToggleSwitch/toggle-switch";
import SingleSelect from "components/atoms/Select/single-select";

import { useUserOrganizations } from "lib/hooks/useUserOrganizations";
import { fetchGithubOrgTeams } from "lib/hooks/fetchGithubOrgTeams";
import { useToast } from "lib/hooks/useToast";

interface GitHubTeamSyncDialogProps {
username: string | null;
open: boolean;
handleSync: (props: { follow: boolean; organization: string; teamSlug: string }) => Promise<void>;
handleClose: () => void;
loading: boolean;
}

const GitHubTeamSyncDialog = ({
open,
handleClose,
handleSync: handleImport,
loading,
username,
}: GitHubTeamSyncDialogProps) => {
const [follow, setFollow] = useState(false);
const [selectedOrg, setSelectedOrg] = useState("");
const [teamSlug, setTeamSlug] = useState("");
const [teams, setTeams] = useState<GhOrgTeam[]>([]);

const { data: userOrgs } = useUserOrganizations(username);
const { toast } = useToast();

async function loadTeams(org: string) {
const response = await fetchGithubOrgTeams(org);

setTeams([]);
setTeamSlug("");

if (response.isError) {
toast({
description:
"There was error loading teams. Check your organization permissions and try logging out and re-connecting.",
variant: "warning",
});

return;
}

setTeams(response.data);
}

useEffect(() => {
if (selectedOrg) {
loadTeams(selectedOrg);
}
}, [selectedOrg]);

return (
<Dialog open={open}>
<DialogContent>
<div className="flex flex-col max-w-xs gap-6 w-max">
<div className="flex flex-col gap-2">
<span className="flex items-center justify-center p-2 bg-black rounded-full w-max">
<span className="flex items-center justify-center w-10 h-10 bg-white rounded-full">
<FiGithub size={24} className="text-blue-12" />
</span>
</span>
<Title level={3} className="text-lg">
Sync a GitHub Team
</Title>
<Text className="leading-tight text-light-slate-9">
We will sync contributors from your team on GitHub to create your new list.
</Text>
<label className="flex mt-4 gap-4 items-center">
<span>Organization</span>
<SingleSelect
options={userOrgs.map((userOrg) => ({
label: userOrg.organization_user.login,
value: `${userOrg.organization_user.login}`,
}))}
position="popper"
value={selectedOrg}
placeholder="Select an organization"
onValueChange={(value) => setSelectedOrg(value)}
/>
</label>
<label className="flex gap-4 items-center">
<span>Team</span>
<SingleSelect
options={teams.map((team) => ({ label: team.name, value: team.slug }))}
position="popper"
value={teamSlug}
placeholder="Select a team"
onValueChange={(value) => setTeamSlug(value)}
/>
</label>
<label className="flex">
<ToggleSwitch
name="followImported"
classNames="mr-2"
checked={follow}
handleToggle={() => setFollow((toFollow) => !toFollow)}
/>
<Text className="leading-tight text-light-slate-9">Follow everyone imported.</Text>
</label>
</div>
<div className="flex gap-3">
<Button onClick={() => handleClose()} className="justify-center flex-1" variant="text">
Go Back
</Button>
<Button
onClick={() => handleImport({ follow, organization: selectedOrg, teamSlug })}
loading={loading}
className="justify-center flex-1"
variant="primary"
disabled={!teamSlug}
>
Sync Team
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
};

export default GitHubTeamSyncDialog;
42 changes: 42 additions & 0 deletions lib/hooks/fetchGithubOrgTeams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Octokit } from "octokit";
import { supabase } from "lib/utils/supabase";

interface GhOrgTeamResponse {
data: GhOrgTeam[];
status: number;
headers: {};
}

const fetchGithubOrgTeams = async (orgName: string | null) => {
const sessionResponse = await supabase.auth.getSession();
const sessionToken = sessionResponse?.data.session?.provider_token;

if (!sessionToken) {
return {
data: [],
isError: true,
};
}

try {
const octokit = new Octokit({ auth: sessionToken });

const response: GhOrgTeamResponse = await octokit.request("GET /orgs/{orgName}/teams", {
orgName,
});

return {
data: response.data ?? [],
isError: null,
};
} catch (e) {
console.log(e);

return {
data: [],
isError: true,
};
}
};

export { fetchGithubOrgTeams };
43 changes: 43 additions & 0 deletions lib/hooks/fetchGithubTeamMembers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Octokit } from "octokit";
import { supabase } from "lib/utils/supabase";

interface GhOrgTeamMembersResponse {
data: GhOrgTeamMember[];
status: number;
headers: {};
}

const fetchGithubOrgTeamMembers = async (orgName: string, teamSlug: string) => {
const sessionResponse = await supabase.auth.getSession();
const sessionToken = sessionResponse?.data.session?.provider_token;

if (!sessionToken) {
return {
data: [],
isError: true,
};
}

try {
const octokit = new Octokit({ auth: sessionToken });

const response: GhOrgTeamMembersResponse = await octokit.request("GET /orgs/{orgName}/teams/{teamSlug}/members", {
orgName,
teamSlug,
});

return {
data: response.data ?? [],
isError: null,
};
} catch (e) {
console.log(e);

return {
data: [],
isError: true,
};
}
};

export { fetchGithubOrgTeamMembers };
14 changes: 11 additions & 3 deletions lib/hooks/useSupabaseAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const useSupabaseAuth = (loadSession = false) => {
const sessionToken = useStore((state) => state.sessionToken);
const providerToken = useStore((state) => state.providerToken);
const userId = useStore((state) => state.userId);
const username: string | null = useStore((state) => state.user?.user_metadata.user_name);

useEffect(() => {
async function getUserSession() {
Expand Down Expand Up @@ -42,13 +43,20 @@ const useSupabaseAuth = (loadSession = false) => {
signIn: (data: SignInWithOAuthCredentials) => {
supabase.auth.signInWithOAuth({
...data,
options: data.options ?? {
redirectTo: process.env.NEXT_PUBLIC_BASE_URL ?? "/",
},
options: data.options
? {
...data.options,
scopes: "read:org",
}
: {
redirectTo: process.env.NEXT_PUBLIC_BASE_URL ?? "/",
scopes: "read:org",
},
});
},
signOut: () => supabase.auth.signOut(),
user,
username,
sessionToken,
providerToken,
userId,
Expand Down
22 changes: 22 additions & 0 deletions lib/hooks/useUserOrganizations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import useSWR, { Fetcher } from "swr";
import publicApiFetcher from "lib/utils/public-api-fetcher";

interface UserConnectionResponse {
data?: DbUserOrganization[];
meta: Meta;
}

const useUserOrganizations = (username: string | null) => {
const { data, error } = useSWR<UserConnectionResponse, Error>(
username ? `users/${username}/organizations` : null,
publicApiFetcher as Fetcher<UserConnectionResponse, Error>
);

return {
data: data?.data ?? [],
isLoading: !error && !data,
isError: !!error,
};
};

export { useUserOrganizations };
19 changes: 19 additions & 0 deletions next-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,22 @@ interface DBProjectContributor {
issues_created: number;
comments: number;
}

interface DbUserOrganization {
id: number;
user_id: number;
organization_id: number;
user: DbUser;
organization_user: DbUser;
}

interface GhOrgTeam {
id: number;
name: string;
slug: string;
}

interface GhOrgTeamMember {
id: number;
login: string;
}
Loading

0 comments on commit 3113a74

Please sign in to comment.