forked from github/plane
feat: edit state, help button, loader while login in
This commit is contained in:
parent
97544c1760
commit
b28dbaea30
@ -22,9 +22,9 @@ import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
|||||||
import type { IState } from "types";
|
import type { IState } from "types";
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
projectId: string;
|
projectId: string;
|
||||||
data?: IState;
|
data?: IState;
|
||||||
|
handleClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<IState> = {
|
const defaultValues: Partial<IState> = {
|
||||||
@ -33,14 +33,9 @@ const defaultValues: Partial<IState> = {
|
|||||||
color: "#000000",
|
color: "#000000",
|
||||||
};
|
};
|
||||||
|
|
||||||
const CreateUpdateStateModal: React.FC<Props> = ({
|
const CreateUpdateStateModal: React.FC<Props> = ({ isOpen, data, projectId, handleClose }) => {
|
||||||
isOpen,
|
const onClose = () => {
|
||||||
setIsOpen,
|
handleClose();
|
||||||
data,
|
|
||||||
projectId,
|
|
||||||
}) => {
|
|
||||||
const handleClose = () => {
|
|
||||||
setIsOpen(false);
|
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
reset(defaultValues);
|
reset(defaultValues);
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
@ -70,12 +65,8 @@ const CreateUpdateStateModal: React.FC<Props> = ({
|
|||||||
await stateService
|
await stateService
|
||||||
.createState(activeWorkspace.slug, projectId, payload)
|
.createState(activeWorkspace.slug, projectId, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate<IState[]>(
|
mutate<IState[]>(STATE_LIST(projectId), (prevData) => [...(prevData ?? []), res], false);
|
||||||
STATE_LIST(projectId),
|
onClose();
|
||||||
(prevData) => [...(prevData ?? []), res],
|
|
||||||
false
|
|
||||||
);
|
|
||||||
handleClose();
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
Object.keys(err).map((key) => {
|
Object.keys(err).map((key) => {
|
||||||
@ -101,7 +92,7 @@ const CreateUpdateStateModal: React.FC<Props> = ({
|
|||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
handleClose();
|
onClose();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
Object.keys(err).map((key) => {
|
Object.keys(err).map((key) => {
|
||||||
@ -115,16 +106,15 @@ const CreateUpdateStateModal: React.FC<Props> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
setIsOpen(true);
|
|
||||||
reset(data);
|
reset(data);
|
||||||
} else {
|
} else {
|
||||||
reset(defaultValues);
|
reset(defaultValues);
|
||||||
}
|
}
|
||||||
}, [data, setIsOpen, reset]);
|
}, [data, reset]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||||
<Dialog as="div" className="relative z-10" onClose={handleClose}>
|
<Dialog as="div" className="relative z-10" onClose={onClose}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
@ -152,10 +142,7 @@ const CreateUpdateStateModal: React.FC<Props> = ({
|
|||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-3 sm:mt-5">
|
<div className="mt-3 sm:mt-5">
|
||||||
<Dialog.Title
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-900">
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-gray-900"
|
|
||||||
>
|
|
||||||
{data ? "Update" : "Create"} State
|
{data ? "Update" : "Create"} State
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 space-y-3">
|
<div className="mt-2 space-y-3">
|
||||||
@ -188,8 +175,7 @@ const CreateUpdateStateModal: React.FC<Props> = ({
|
|||||||
<span
|
<span
|
||||||
className="w-4 h-4 ml-2 rounded"
|
className="w-4 h-4 ml-2 rounded"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor: watch("color") ?? "green",
|
||||||
watch("color") ?? "green",
|
|
||||||
}}
|
}}
|
||||||
></span>
|
></span>
|
||||||
)}
|
)}
|
||||||
@ -214,14 +200,10 @@ const CreateUpdateStateModal: React.FC<Props> = ({
|
|||||||
<Controller
|
<Controller
|
||||||
name="color"
|
name="color"
|
||||||
control={control}
|
control={control}
|
||||||
render={({
|
render={({ field: { value, onChange } }) => (
|
||||||
field: { value, onChange },
|
|
||||||
}) => (
|
|
||||||
<TwitterPicker
|
<TwitterPicker
|
||||||
color={value}
|
color={value}
|
||||||
onChange={(value) =>
|
onChange={(value) => onChange(value.hex)}
|
||||||
onChange(value.hex)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -245,7 +227,7 @@ const CreateUpdateStateModal: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5 sm:mt-6 sm:grid sm:grid-flow-row-dense sm:grid-cols-2 sm:gap-3">
|
<div className="mt-5 sm:mt-6 sm:grid sm:grid-flow-row-dense sm:grid-cols-2 sm:gap-3">
|
||||||
<Button theme="secondary" onClick={handleClose}>
|
<Button theme="secondary" onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" disabled={isSubmitting}>
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
|
@ -267,7 +267,7 @@ const CreateUpdateIssuesModal: React.FC<Props> = ({
|
|||||||
<>
|
<>
|
||||||
<CreateUpdateStateModal
|
<CreateUpdateStateModal
|
||||||
isOpen={isStateModalOpen}
|
isOpen={isStateModalOpen}
|
||||||
setIsOpen={setIsStateModalOpen}
|
handleClose={() => setIsStateModalOpen(false)}
|
||||||
projectId={activeProject?.id}
|
projectId={activeProject?.id}
|
||||||
/>
|
/>
|
||||||
<CreateUpdateCycleModal
|
<CreateUpdateCycleModal
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { FC, CSSProperties, useEffect, useState } from "react";
|
import { FC, CSSProperties } from "react";
|
||||||
|
// next
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
|
|
||||||
export interface IGoogleLoginButton {
|
export interface IGoogleLoginButton {
|
||||||
@ -28,7 +29,7 @@ export const GoogleLoginButton: FC<IGoogleLoginButton> = (props) => {
|
|||||||
size: "large",
|
size: "large",
|
||||||
logo_alignment: "center",
|
logo_alignment: "center",
|
||||||
width: document.getElementById("googleSignInButton")?.offsetWidth,
|
width: document.getElementById("googleSignInButton")?.offsetWidth,
|
||||||
text: props.text || "Continue with Google",
|
text: "continue_with",
|
||||||
} as GsiButtonConfiguration // customization attributes
|
} as GsiButtonConfiguration // customization attributes
|
||||||
);
|
);
|
||||||
window?.google?.accounts.id.prompt(); // also display the One Tap dialog
|
window?.google?.accounts.id.prompt(); // also display the One Tap dialog
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
UserIcon,
|
UserIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
ArrowLongLeftIcon,
|
ArrowLongLeftIcon,
|
||||||
|
QuestionMarkCircleIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
// constants
|
// constants
|
||||||
import { classNames } from "constants/common";
|
import { classNames } from "constants/common";
|
||||||
@ -488,7 +489,7 @@ const Sidebar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2 py-2 bg-gray-50 w-full self-baseline">
|
<div className="px-2 py-2 bg-gray-50 w-full self-baseline flex items-center gap-x-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`flex items-center gap-3 px-2 py-2 text-xs font-medium rounded-md text-gray-500 hover:bg-gray-100 hover:text-gray-900 w-full ${
|
className={`flex items-center gap-3 px-2 py-2 text-xs font-medium rounded-md text-gray-500 hover:bg-gray-100 hover:text-gray-900 w-full ${
|
||||||
@ -504,6 +505,19 @@ const Sidebar: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", {
|
||||||
|
ctrlKey: true,
|
||||||
|
key: "h",
|
||||||
|
});
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
title="Help"
|
||||||
|
>
|
||||||
|
<QuestionMarkCircleIcon className="h-4 w-4 text-gray-500" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,11 +27,13 @@ class UserService extends APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async currentUser(): Promise<any> {
|
async currentUser(): Promise<any> {
|
||||||
|
if (!this.getAccessToken()) return null;
|
||||||
return this.get(USER_ENDPOINT)
|
return this.get(USER_ENDPOINT)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
return response?.data;
|
return response?.data;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
this.purgeAccessToken();
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,14 @@ import CreateUpdateStateModal from "components/project/issues/BoardView/state/Cr
|
|||||||
import { Spinner, Button, Input, TextArea, Select } from "ui";
|
import { Spinner, Button, Input, TextArea, Select } from "ui";
|
||||||
import { Breadcrumbs, BreadcrumbItem } from "ui/Breadcrumbs";
|
import { Breadcrumbs, BreadcrumbItem } from "ui/Breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
import { ChevronDownIcon, CheckIcon, PlusIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
|
ChevronDownIcon,
|
||||||
|
CheckIcon,
|
||||||
|
PlusIcon,
|
||||||
|
PencilSquareIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { IProject, IWorkspace, WorkspaceMember } from "types";
|
import type { IProject, IState, IWorkspace, WorkspaceMember } from "types";
|
||||||
|
|
||||||
const defaultValues: Partial<IProject> = {
|
const defaultValues: Partial<IProject> = {
|
||||||
name: "",
|
name: "",
|
||||||
@ -52,6 +57,7 @@ const ProjectSettings: NextPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [isCreateStateModalOpen, setIsCreateStateModalOpen] = useState(false);
|
const [isCreateStateModalOpen, setIsCreateStateModalOpen] = useState(false);
|
||||||
|
const [selectedState, setSelectedState] = useState<string | undefined>();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -135,9 +141,13 @@ const ProjectSettings: NextPage = () => {
|
|||||||
<ProjectLayout>
|
<ProjectLayout>
|
||||||
<div className="w-full h-full space-y-5">
|
<div className="w-full h-full space-y-5">
|
||||||
<CreateUpdateStateModal
|
<CreateUpdateStateModal
|
||||||
isOpen={isCreateStateModalOpen}
|
isOpen={isCreateStateModalOpen || Boolean(selectedState)}
|
||||||
setIsOpen={setIsCreateStateModalOpen}
|
handleClose={() => {
|
||||||
|
setSelectedState(undefined);
|
||||||
|
setIsCreateStateModalOpen(false);
|
||||||
|
}}
|
||||||
projectId={projectId as string}
|
projectId={projectId as string}
|
||||||
|
data={selectedState ? states?.find((state) => state.id === selectedState) : undefined}
|
||||||
/>
|
/>
|
||||||
<Breadcrumbs>
|
<Breadcrumbs>
|
||||||
<BreadcrumbItem title="Projects" link="/projects" />
|
<BreadcrumbItem title="Projects" link="/projects" />
|
||||||
@ -404,16 +414,23 @@ const ProjectSettings: NextPage = () => {
|
|||||||
<div className="w-full space-y-5">
|
<div className="w-full space-y-5">
|
||||||
{states?.map((state) => (
|
{states?.map((state) => (
|
||||||
<div
|
<div
|
||||||
className="border p-1 px-4 rounded flex items-center gap-x-2"
|
className="border p-1 px-4 rounded flex justify-between items-center"
|
||||||
key={state.id}
|
key={state.id}
|
||||||
>
|
>
|
||||||
<div
|
<div className="flex items-center gap-x-2">
|
||||||
className="w-3 h-3 rounded-full"
|
<div
|
||||||
style={{
|
className="w-3 h-3 rounded-full"
|
||||||
backgroundColor: state.color,
|
style={{
|
||||||
}}
|
backgroundColor: state.color,
|
||||||
></div>
|
}}
|
||||||
<h4>{addSpaceIfCamelCase(state.name)}</h4>
|
></div>
|
||||||
|
<h4>{addSpaceIfCamelCase(state.name)}</h4>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" onClick={() => setSelectedState(state.id)}>
|
||||||
|
<PencilSquareIcon className="h-5 w-5 text-gray-400" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
|
import React, { useCallback, useState, useEffect } from "react";
|
||||||
// next
|
// next
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
// react
|
|
||||||
import React, { useCallback, useState } from "react";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "lib/hooks/useUser";
|
import useUser from "lib/hooks/useUser";
|
||||||
// services
|
// services
|
||||||
@ -37,8 +36,10 @@ const SignIn: NextPage = () => {
|
|||||||
|
|
||||||
const { mutateUser, mutateWorkspaces } = useUser();
|
const { mutateUser, mutateWorkspaces } = useUser();
|
||||||
|
|
||||||
const [githubToken, setGithubToken] = React.useState(undefined);
|
const [githubToken, setGithubToken] = useState(undefined);
|
||||||
const [loginCallBackURL, setLoginCallBackURL] = React.useState(undefined);
|
const [loginCallBackURL, setLoginCallBackURL] = useState(undefined);
|
||||||
|
|
||||||
|
const [isGoogleAuthenticationLoading, setIsGoogleAuthenticationLoading] = useState(false);
|
||||||
|
|
||||||
const onSignInSuccess = useCallback(
|
const onSignInSuccess = useCallback(
|
||||||
async (res: any) => {
|
async (res: any) => {
|
||||||
@ -54,7 +55,7 @@ const SignIn: NextPage = () => {
|
|||||||
return githubToken;
|
return githubToken;
|
||||||
}, [githubToken]);
|
}, [githubToken]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const {
|
const {
|
||||||
query: { code },
|
query: { code },
|
||||||
} = router;
|
} = router;
|
||||||
@ -63,7 +64,7 @@ const SignIn: NextPage = () => {
|
|||||||
}
|
}
|
||||||
}, [router, githubTokenMemo]);
|
}, [router, githubTokenMemo]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (githubToken) {
|
if (githubToken) {
|
||||||
authenticationService
|
authenticationService
|
||||||
.socialAuth({
|
.socialAuth({
|
||||||
@ -80,10 +81,12 @@ const SignIn: NextPage = () => {
|
|||||||
}
|
}
|
||||||
}, [githubToken, mutateUser, mutateWorkspaces, router, onSignInSuccess]);
|
}, [githubToken, mutateUser, mutateWorkspaces, router, onSignInSuccess]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const origin =
|
const origin =
|
||||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||||
setLoginCallBackURL(`${origin}/signin` as any);
|
setLoginCallBackURL(`${origin}/signin` as any);
|
||||||
|
|
||||||
|
return () => setIsGoogleAuthenticationLoading(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -92,6 +95,11 @@ const SignIn: NextPage = () => {
|
|||||||
title: "Plane - Sign In",
|
title: "Plane - Sign In",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{isGoogleAuthenticationLoading && (
|
||||||
|
<div className="absolute top-0 left-0 w-full h-full bg-white z-50 flex items-center justify-center">
|
||||||
|
<h2 className="text-2xl text-black">Sign in with Google. Please wait...</h2>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="w-full h-screen flex justify-center items-center bg-gray-50 overflow-auto">
|
<div className="w-full h-screen flex justify-center items-center bg-gray-50 overflow-auto">
|
||||||
<div className="min-h-full w-full flex flex-col justify-center py-12 px-6 lg:px-8">
|
<div className="min-h-full w-full flex flex-col justify-center py-12 px-6 lg:px-8">
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
@ -129,6 +137,7 @@ const SignIn: NextPage = () => {
|
|||||||
</button>
|
</button>
|
||||||
<GoogleLoginButton
|
<GoogleLoginButton
|
||||||
onSuccess={({ clientId, credential }) => {
|
onSuccess={({ clientId, credential }) => {
|
||||||
|
setIsGoogleAuthenticationLoading(true);
|
||||||
authenticationService
|
authenticationService
|
||||||
.socialAuth({
|
.socialAuth({
|
||||||
medium: "google",
|
medium: "google",
|
||||||
@ -140,6 +149,7 @@ const SignIn: NextPage = () => {
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
setIsGoogleAuthenticationLoading(false);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onFailure={(err) => {
|
onFailure={(err) => {
|
||||||
|
@ -119,10 +119,10 @@ const WorkspaceSettings = () => {
|
|||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
<div className="text-gray-500 mb-2">Logo</div>
|
<div className="text-gray-500 mb-2">Logo</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="h-60 bg-blue-50" {...getRootProps()}>
|
<div className="w-1/2 aspect-square bg-blue-50" {...getRootProps()}>
|
||||||
{((watch("logo") && watch("logo") !== null && watch("logo") !== "") ||
|
{((watch("logo") && watch("logo") !== null && watch("logo") !== "") ||
|
||||||
(image && image !== null)) && (
|
(image && image !== null)) && (
|
||||||
<div className="relative flex mx-auto h-60">
|
<div className="relative flex mx-auto h-full">
|
||||||
<Image
|
<Image
|
||||||
src={image ? URL.createObjectURL(image) : watch("logo") ?? ""}
|
src={image ? URL.createObjectURL(image) : watch("logo") ?? ""}
|
||||||
alt="Workspace Logo"
|
alt="Workspace Logo"
|
||||||
|
Loading…
Reference in New Issue
Block a user