Skip to content

Commit

Permalink
feat: github public scope permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
haydencleary committed Nov 14, 2024
1 parent 57a16d3 commit 66a960b
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 18 deletions.
12 changes: 8 additions & 4 deletions core/application/auth0-client-adapter/auth0-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { AppState, Auth0Provider as Provider } from "@auth0/auth0-react";
import { useRouter } from "next/navigation";
import { PropsWithChildren } from "react";

import { useLocalScopeStorage } from "@/core/application/auth0-client-adapter/hooks/use-local-scope-storage";

const domain = process.env.NEXT_PUBLIC_AUTH0_PROVIDER_DOMAIN;
const clientId = process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID;
const redirectUri = process.env.NEXT_PUBLIC_AUTH0_CALLBACK_URL;
Expand All @@ -12,15 +14,17 @@ const audience = process.env.NEXT_PUBLIC_AUTH0_AUDIENCE;

export function Auth0Provider({ children }: PropsWithChildren) {
const router = useRouter();
const [scopeStorage] = useLocalScopeStorage();

if (!(domain && clientId && redirectUri && audience)) {
return null;
}

const onRedirectCallback = (state: AppState | undefined) => {
function onRedirectCallback(state: AppState | undefined) {
if (state?.returnTo) {
router.push(state.returnTo);
}
};
}

return (
<Provider
Expand All @@ -30,10 +34,10 @@ export function Auth0Provider({ children }: PropsWithChildren) {
redirect_uri: redirectUri,
connection: connectionName,
audience,
// connection_scope: scopeStorage,
connection_scope: scopeStorage,
}}
cacheLocation="localstorage"
useRefreshTokens={true}
useRefreshTokens
onRedirectCallback={onRedirectCallback}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useLocalStorage } from "react-use";

export function useLocalScopeStorage() {
return useLocalStorage("dynamic-github-public-repo-scope");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useMemo } from "react";

import { Auth0ClientAdapter } from "@/core/application/auth0-client-adapter";
import { useLocalScopeStorage } from "@/core/application/auth0-client-adapter/hooks/use-local-scope-storage";
import { MeReactQueryAdapter } from "@/core/application/react-query-adapter/me";
import { useClientBootstrapContext } from "@/core/bootstrap/client-bootstrap-context";

import { useAuthUser } from "@/shared/hooks/auth/use-auth-user";

export function usePublicRepoScope() {
const [scopeStorage, setScopeStorage] = useLocalScopeStorage();

const {
clientBootstrap: { authProvider },
} = useClientBootstrapContext();
const { isAuthenticated = false, loginWithRedirect, loginWithPopup } = authProvider ?? {};

const { user, refetch } = useAuthUser();
const isAuthorized = useMemo(() => user?.isAuthorizedToApplyOnGithubIssues, [user]);

const { mutateAsync: logoutUser } = MeReactQueryAdapter.client.useLogoutMe({});

async function getPermissions() {
if (!scopeStorage) {
setScopeStorage(process.env.NEXT_PUBLIC_GITHUB_PUBLIC_REPO_SCOPE);
}

await logoutUser({});

if (loginWithPopup) await Auth0ClientAdapter.helpers.handleLoginWithPopup(loginWithPopup);

await refetch();
}

async function handleVerifyPermissions(onSuccess?: () => void) {
if (!isAuthenticated) {
if (loginWithRedirect) Auth0ClientAdapter.helpers.handleLoginWithRedirect(loginWithRedirect);
return;
}

if (!isAuthorized) {
await getPermissions();
onSuccess?.();
return;
}

onSuccess?.();
}

return { handleVerifyPermissions, getPermissions, isAuthorized };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"title": "Grant permissions",
"description": "We need your permission to write comments on your behalf and apply to selected issues directly on OnlyDust. Only need to be accepted once.",
"moreInfo": "Click on \"Grant Permissions\". A GitHub popup will appear. Click on \"Authorize OnlyDust\", and you'll be redirected back here to submit your application.",
"grantPermissions": "Write all public repository data"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useState } from "react";

export function useGithubPublicScopePermissionModal() {
const [isOpen, setIsOpen] = useState(false);

return { isOpen, setIsOpen };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import githubPermissionImage from "@/public/images/github/github-permission.png";
import { Github, SquareArrowOutUpRight } from "lucide-react";
import Image from "next/image";

import { Button } from "@/design-system/atoms/button/variants/button-default";
import { Typo } from "@/design-system/atoms/typo";
import { Modal } from "@/design-system/molecules/modal";

import { GithubPublicScopePermissionModalProps } from "./github-public-scope-permission-modal.types";

export function GithubPublicScopePermissionModal({
isOpen,
onOpenChange,
onRedirect,
}: GithubPublicScopePermissionModalProps) {
return (
<Modal
isOpen={isOpen}
onOpenChange={onOpenChange}
titleProps={{
translate: { token: "modals:githubPublicScopePermission.title" },
}}
footer={{
endContent: (
<Button
translate={{ token: "modals:githubPublicScopePermission.grantPermissions" }}
startIcon={{ component: Github }}
endIcon={{ component: SquareArrowOutUpRight }}
onClick={onRedirect}
/>
),
}}
size="xl"
background="gradient"
>
<div className="flex flex-col gap-lg">
<Image
src={githubPermissionImage}
alt="github permission"
className="h-full w-full object-cover object-center"
loading={"lazy"}
width={320}
height={50}
quality={100}
/>
<Typo size="xs" color="primary" translate={{ token: "modals:githubPublicScopePermission.description" }} />
<Typo size="xs" color="tertiary" translate={{ token: "modals:githubPublicScopePermission.moreInfo" }} />
</div>
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ModalPort } from "@/design-system/molecules/modal";

export interface GithubPublicScopePermissionModalProps extends Pick<ModalPort<"div">, "isOpen" | "onOpenChange"> {
onRedirect: () => void;
}
25 changes: 25 additions & 0 deletions shared/features/github-permissions/github-permissions.context.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React, { PropsWithChildren, createContext, useContext, useEffect, useMemo, useState } from "react";

import { usePublicRepoScope } from "@/core/application/auth0-client-adapter/hooks/use-public-repo-scope";
import { GithubReactQueryAdapter } from "@/core/application/react-query-adapter/github";
import { ProjectReactQueryAdapter } from "@/core/application/react-query-adapter/project";

import { GithubPermissionModal } from "@/shared/features/github-permissions/_components/github-permission-modal/github-permission-modal";
import { useGithubPermissionModal } from "@/shared/features/github-permissions/_components/github-permission-modal/github-permission-modal.hooks";
import { GithubPublicScopePermissionModal } from "@/shared/features/github-permissions/_components/github-public-scope-permission-modal/github-public-scope-permission-modal";
import { useGithubPublicScopePermissionModal } from "@/shared/features/github-permissions/_components/github-public-scope-permission-modal/github-public-scope-permission-modal.hooks";
import { usePooling } from "@/shared/hooks/pooling/usePooling";

interface GithubPermissionsContextInterface {
Expand All @@ -14,6 +17,8 @@ interface GithubPermissionsContextInterface {
setIsGithubPermissionModalOpen: (isOpen: boolean) => void;
setEnablePooling: (enable: boolean) => void;
canCurrentUserUpdatePermissions: (repoId: number) => boolean;
isGithubPublicScopePermissionModalOpen: boolean;
setIsGithubPublicScopePermissionModalOpen: (isOpen: boolean) => void;
}

const GithubPermissionsContext = createContext<GithubPermissionsContextInterface>({
Expand All @@ -23,12 +28,17 @@ const GithubPermissionsContext = createContext<GithubPermissionsContextInterface
setIsGithubPermissionModalOpen: () => {},
setEnablePooling: () => {},
canCurrentUserUpdatePermissions: () => false,
isGithubPublicScopePermissionModalOpen: false,
setIsGithubPublicScopePermissionModalOpen: () => {},
});

export function GithubPermissionsProvider({ children, projectSlug }: PropsWithChildren & { projectSlug: string }) {
const [enablePooling, setEnablePooling] = useState(false);
const [repoId, setRepoId] = useState<number | undefined>();
const { isOpen: isGithubPermissionModalOpen, setIsOpen: setIsGithubPermissionModalOpen } = useGithubPermissionModal();
const { isOpen: isGithubPublicScopePermissionModalOpen, setIsOpen: setIsGithubPublicScopePermissionModalOpen } =
useGithubPublicScopePermissionModal();
const { handleVerifyPermissions } = usePublicRepoScope();

const { data: userOrganizations } = GithubReactQueryAdapter.client.useGetMyOrganizations({});

Expand Down Expand Up @@ -101,14 +111,29 @@ export function GithubPermissionsProvider({ children, projectSlug }: PropsWithCh
setIsGithubPermissionModalOpen,
setEnablePooling,
canCurrentUserUpdatePermissions,

isGithubPublicScopePermissionModalOpen,
setIsGithubPublicScopePermissionModalOpen,
}}
>
{children}

<GithubPermissionModal
onRedirect={handleRedirectToGithubFlow}
isOpen={isGithubPermissionModalOpen}
onOpenChange={setIsGithubPermissionModalOpen}
/>

<GithubPublicScopePermissionModal
onRedirect={() =>
handleVerifyPermissions(() => {
// Close the modal when the user has granted the permissions
setIsGithubPublicScopePermissionModalOpen(false);
})
}
isOpen={isGithubPublicScopePermissionModalOpen}
onOpenChange={setIsGithubPublicScopePermissionModalOpen}
/>
</GithubPermissionsContext.Provider>
);
}
Expand Down
2 changes: 2 additions & 0 deletions shared/modals/_translations/modals.translate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import enGithubPermission from "@/shared/features/github-permissions/_components/github-permission-modal/_translations/github-permission.en.json";
import enGithubPublicScopePermission from "@/shared/features/github-permissions/_components/github-public-scope-permission-modal/_translations/github-public-scope-permission.en.json";
import enManageApplicants from "@/shared/modals/manage-applicants-modal/_translations/manage-applicants.en.json";
import enManageRewards from "@/shared/panels/_flows/reward-flow/modals/manage-rewards-modal/_translations/manage-rewards.en.json";

Expand All @@ -7,5 +8,6 @@ export const enModalsTranslation = {
manageApplicants: enManageApplicants,
manageRewards: enManageRewards,
githubPermission: enGithubPermission,
githubPublicScopePermission: enGithubPublicScopePermission,
},
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from "react";

import { usePublicRepoScope } from "@/core/application/auth0-client-adapter/hooks/use-public-repo-scope";
import { ApplicationReactQueryAdapter } from "@/core/application/react-query-adapter/application";
import { ContributionAs } from "@/core/domain/contribution/models/contribution.types";

Expand Down Expand Up @@ -92,32 +93,43 @@ export const useContributionPanelFooterAsMaintainer = ({
export const useContributionPanelFooterAsContributor = ({ contribution }: UseContributionPanelFooter) => {
const [shouldDeleteComment, setShouldDeleteComment] = useState(false);
const { close } = useSidePanelsContext();
const { handleVerifyPermissions, isAuthorized } = usePublicRepoScope();
const { setIsGithubPublicScopePermissionModalOpen } = useGithubPermissionsContext();

const { githubUserId } = useAuthUser();

const applicationId =
contribution.applicants.find(applicant => applicant.githubUserId === githubUserId)?.applicationId ?? "";

// TODO handle Github permissions
const { mutate, isPending } = ApplicationReactQueryAdapter.client.useDeleteApplication({
pathParams: { applicationId },
options: {
onSuccess: () => {
close();
toast.success(<Translate token={"panels:contribution.footer.tooltip.cancelApplication.success"} />);
const { mutate: deleteApplication, isPending: isDeleteApplicationPending } =
ApplicationReactQueryAdapter.client.useDeleteApplication({
pathParams: { applicationId },
options: {
onSuccess: () => {
close();
toast.success(<Translate token={"panels:contribution.footer.tooltip.cancelApplication.success"} />);
},
onError: () => {
toast.error(<Translate token={"panels:contribution.footer.tooltip.cancelApplication.error"} />);
},
},
onError: () => {
toast.error(<Translate token={"panels:contribution.footer.tooltip.cancelApplication.error"} />);
},
},
});
});

if (contribution.isArchived()) {
return <div />;
}

function onCancelApplication() {
mutate({ deleteGithubComment: shouldDeleteComment });
if (isDeleteApplicationPending) return;

if (!isAuthorized) {
setIsGithubPublicScopePermissionModalOpen(true);
return;
}

handleVerifyPermissions(() => {
deleteApplication({ deleteGithubComment: shouldDeleteComment });
});
}

if (contribution?.isNotAssigned()) {
Expand All @@ -135,7 +147,7 @@ export const useContributionPanelFooterAsContributor = ({ contribution }: UseCon
size={"md"}
variant={"secondary"}
onClick={onCancelApplication}
isLoading={isPending}
isLoading={isDeleteApplicationPending}
translate={{ token: "panels:contribution.footer.actions.asContributor.cancelApplication" }}
/>
</div>
Expand Down

0 comments on commit 66a960b

Please sign in to comment.