forked from github/plane
Merge branch 'develop' of github.com:makeplane/plane into chore/breadcrumb_component_revamp
This commit is contained in:
commit
b0fd935141
@ -4,6 +4,7 @@ from django.conf import settings
|
|||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import plane.db.models.issue
|
import plane.db.models.issue
|
||||||
|
import uuid
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
@ -12,6 +13,26 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="issue_mentions",
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
||||||
|
('id', models.UUIDField(db_index=True, default=uuid.uuid4,editable=False, primary_key=True, serialize=False, unique=True)),
|
||||||
|
('mention', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_mention', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,related_name='issuemention_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||||
|
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_mention', to='db.issue')),
|
||||||
|
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_issuemention', to='db.project')),
|
||||||
|
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issuemention_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
||||||
|
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_issuemention', to='db.workspace')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'IssueMention',
|
||||||
|
'verbose_name_plural': 'IssueMentions',
|
||||||
|
'db_table': 'issue_mentions',
|
||||||
|
'ordering': ('-created_at',),
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='issueproperty',
|
model_name='issueproperty',
|
||||||
name='properties',
|
name='properties',
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
# Generated by Django 4.2.5 on 2023-10-25 05:01
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('db', '0046_alter_analyticview_created_by_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="issue_mentions",
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(
|
|
||||||
auto_now_add=True, verbose_name='Created At')),
|
|
||||||
('updated_at', models.DateTimeField(
|
|
||||||
auto_now=True, verbose_name='Last Modified At')),
|
|
||||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4,
|
|
||||||
editable=False, primary_key=True, serialize=False, unique=True)),
|
|
||||||
('mention', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name='issue_mention', to=settings.AUTH_USER_MODEL)),
|
|
||||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name='issuemention_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
|
||||||
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name='issue_mention', to='db.issue')),
|
|
||||||
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name='project_issuemention', to='db.project')),
|
|
||||||
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name='issuemention_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
|
||||||
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name='workspace_issuemention', to='db.workspace')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'IssueMention',
|
|
||||||
'verbose_name_plural': 'IssueMentions',
|
|
||||||
'db_table': 'issue_mentions',
|
|
||||||
'ordering': ('-created_at',),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]
|
|
@ -47,8 +47,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||||||
{isDisplayFilterEnabled("group_by") && (
|
{isDisplayFilterEnabled("group_by") && (
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterGroupBy
|
<FilterGroupBy
|
||||||
selectedGroupBy={displayFilters.group_by}
|
displayFilters={displayFilters}
|
||||||
selectedSubGroupBy={displayFilters.sub_group_by}
|
|
||||||
groupByOptions={layoutDisplayFiltersOptions?.display_filters.group_by ?? []}
|
groupByOptions={layoutDisplayFiltersOptions?.display_filters.group_by ?? []}
|
||||||
handleUpdate={(val) =>
|
handleUpdate={(val) =>
|
||||||
handleDisplayFiltersUpdate({
|
handleDisplayFiltersUpdate({
|
||||||
@ -65,8 +64,7 @@ export const DisplayFiltersSelection: React.FC<Props> = observer((props) => {
|
|||||||
displayFilters.layout === "kanban" && (
|
displayFilters.layout === "kanban" && (
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterSubGroupBy
|
<FilterSubGroupBy
|
||||||
selectedGroupBy={displayFilters.group_by}
|
displayFilters={displayFilters}
|
||||||
selectedSubGroupBy={displayFilters.sub_group_by}
|
|
||||||
handleUpdate={(val) =>
|
handleUpdate={(val) =>
|
||||||
handleDisplayFiltersUpdate({
|
handleDisplayFiltersUpdate({
|
||||||
sub_group_by: val,
|
sub_group_by: val,
|
||||||
|
@ -4,23 +4,23 @@ import { observer } from "mobx-react-lite";
|
|||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "components/issues";
|
import { FilterHeader, FilterOption } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { TIssueGroupByOptions } from "types";
|
import { IIssueDisplayFilterOptions, TIssueGroupByOptions } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue";
|
import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectedGroupBy: TIssueGroupByOptions | undefined;
|
displayFilters: IIssueDisplayFilterOptions;
|
||||||
selectedSubGroupBy: TIssueGroupByOptions | undefined;
|
|
||||||
groupByOptions: TIssueGroupByOptions[];
|
groupByOptions: TIssueGroupByOptions[];
|
||||||
handleUpdate: (val: TIssueGroupByOptions) => void;
|
handleUpdate: (val: TIssueGroupByOptions) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterGroupBy: React.FC<Props> = observer((props) => {
|
export const FilterGroupBy: React.FC<Props> = observer((props) => {
|
||||||
const { selectedGroupBy, selectedSubGroupBy, groupByOptions, handleUpdate } = props;
|
const { displayFilters, groupByOptions, handleUpdate } = props;
|
||||||
|
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
|
|
||||||
const activeGroupBy = selectedGroupBy ?? null;
|
const selectedGroupBy = displayFilters.group_by ?? null;
|
||||||
|
const selectedSubGroupBy = displayFilters.sub_group_by ?? null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -32,12 +32,13 @@ export const FilterGroupBy: React.FC<Props> = observer((props) => {
|
|||||||
{previewEnabled && (
|
{previewEnabled && (
|
||||||
<div>
|
<div>
|
||||||
{ISSUE_GROUP_BY_OPTIONS.filter((option) => groupByOptions.includes(option.key)).map((groupBy) => {
|
{ISSUE_GROUP_BY_OPTIONS.filter((option) => groupByOptions.includes(option.key)).map((groupBy) => {
|
||||||
if (selectedSubGroupBy !== null && groupBy.key === selectedSubGroupBy) return null;
|
if (displayFilters.layout === "kanban" && selectedSubGroupBy !== null && groupBy.key === selectedSubGroupBy)
|
||||||
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FilterOption
|
<FilterOption
|
||||||
key={groupBy?.key}
|
key={groupBy?.key}
|
||||||
isChecked={activeGroupBy === groupBy?.key ? true : false}
|
isChecked={selectedGroupBy === groupBy?.key ? true : false}
|
||||||
onClick={() => handleUpdate(groupBy.key)}
|
onClick={() => handleUpdate(groupBy.key)}
|
||||||
title={groupBy.title}
|
title={groupBy.title}
|
||||||
multiple={false}
|
multiple={false}
|
||||||
|
@ -4,22 +4,24 @@ import { observer } from "mobx-react-lite";
|
|||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "components/issues";
|
import { FilterHeader, FilterOption } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { TIssueGroupByOptions } from "types";
|
import { IIssueDisplayFilterOptions, TIssueGroupByOptions } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue";
|
import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectedGroupBy: TIssueGroupByOptions | undefined;
|
displayFilters: IIssueDisplayFilterOptions;
|
||||||
selectedSubGroupBy: TIssueGroupByOptions | undefined;
|
|
||||||
handleUpdate: (val: TIssueGroupByOptions) => void;
|
handleUpdate: (val: TIssueGroupByOptions) => void;
|
||||||
subGroupByOptions: TIssueGroupByOptions[];
|
subGroupByOptions: TIssueGroupByOptions[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterSubGroupBy: React.FC<Props> = observer((props) => {
|
export const FilterSubGroupBy: React.FC<Props> = observer((props) => {
|
||||||
const { selectedGroupBy, selectedSubGroupBy, handleUpdate, subGroupByOptions } = props;
|
const { displayFilters, handleUpdate, subGroupByOptions } = props;
|
||||||
|
|
||||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||||
|
|
||||||
|
const selectedGroupBy = displayFilters.group_by ?? null;
|
||||||
|
const selectedSubGroupBy = displayFilters.sub_group_by ?? null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FilterHeader
|
<FilterHeader
|
||||||
|
@ -178,8 +178,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
{/* TODO: have to implement */}
|
{group_by && group_by === "project" && (
|
||||||
{group_by && group_by === "projects" && (
|
|
||||||
<GroupByKanBan
|
<GroupByKanBan
|
||||||
issues={issues}
|
issues={issues}
|
||||||
group_by={group_by}
|
group_by={group_by}
|
||||||
|
@ -286,6 +286,28 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{sub_group_by && sub_group_by === "project" && (
|
||||||
|
<SubGroupSwimlane
|
||||||
|
issues={issues}
|
||||||
|
sub_group_by={sub_group_by}
|
||||||
|
group_by={group_by}
|
||||||
|
list={projects}
|
||||||
|
listKey={`id`}
|
||||||
|
handleIssues={handleIssues}
|
||||||
|
quickActions={quickActions}
|
||||||
|
displayProperties={displayProperties}
|
||||||
|
kanBanToggle={kanBanToggle}
|
||||||
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
|
showEmptyGroup={showEmptyGroup}
|
||||||
|
states={states}
|
||||||
|
stateGroups={stateGroups}
|
||||||
|
priorities={priorities}
|
||||||
|
labels={labels}
|
||||||
|
members={members}
|
||||||
|
projects={projects}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{sub_group_by && sub_group_by === "state" && (
|
{sub_group_by && sub_group_by === "state" && (
|
||||||
<SubGroupSwimlane
|
<SubGroupSwimlane
|
||||||
issues={issues}
|
issues={issues}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@ -22,17 +22,18 @@ import { Loader, Spinner } from "@plane/ui";
|
|||||||
// images
|
// images
|
||||||
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
||||||
// types
|
// types
|
||||||
import { IUserSettings } from "types";
|
import { IUser, IUserSettings } from "types";
|
||||||
|
|
||||||
const appConfigService = new AppConfigService();
|
const appConfigService = new AppConfigService();
|
||||||
const authService = new AuthService();
|
const authService = new AuthService();
|
||||||
|
|
||||||
export const SignInView = observer(() => {
|
export const SignInView = observer(() => {
|
||||||
const { user: userStore } = useMobxStore();
|
const {
|
||||||
|
user: { fetchCurrentUser, fetchCurrentUserSettings },
|
||||||
|
} = useMobxStore();
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { next: next_url } = router.query as { next: string };
|
const { next: next_url } = router.query as { next: string };
|
||||||
|
|
||||||
// states
|
// states
|
||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setLoading] = useState(false);
|
||||||
// toast
|
// toast
|
||||||
@ -44,44 +45,44 @@ export const SignInView = observer(() => {
|
|||||||
data &&
|
data &&
|
||||||
(data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github));
|
(data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github));
|
||||||
|
|
||||||
useEffect(() => {
|
const handleLoginRedirection = useCallback(
|
||||||
userStore.fetchCurrentUserSettings().then((settings) => {
|
(user: IUser) => {
|
||||||
setLoading(true);
|
// if the user is not onboarded, redirect them to the onboarding page
|
||||||
if (next_url) router.push(next_url);
|
if (!user.is_onboarded) {
|
||||||
else
|
|
||||||
router.push(
|
|
||||||
`/${
|
|
||||||
settings.workspace.last_workspace_slug
|
|
||||||
? settings.workspace.last_workspace_slug
|
|
||||||
: settings.workspace.fallback_workspace_slug
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, [userStore, router, next_url]);
|
|
||||||
|
|
||||||
const handleLoginRedirection = () => {
|
|
||||||
userStore.fetchCurrentUser().then((user) => {
|
|
||||||
const isOnboarded = user.is_onboarded;
|
|
||||||
|
|
||||||
if (isOnboarded) {
|
|
||||||
userStore
|
|
||||||
.fetchCurrentUserSettings()
|
|
||||||
.then((userSettings: IUserSettings) => {
|
|
||||||
const workspaceSlug =
|
|
||||||
userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug;
|
|
||||||
if (next_url) router.push(next_url);
|
|
||||||
else if (workspaceSlug) router.push(`/${workspaceSlug}`);
|
|
||||||
else if (userSettings.workspace.invites > 0) router.push("/invitations");
|
|
||||||
else router.push("/create-workspace");
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
router.push("/onboarding");
|
router.push("/onboarding");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
// if next_url is provided, redirect the user to that url
|
||||||
|
if (next_url) {
|
||||||
|
router.push(next_url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user is onboarded, fetch their last workspace details
|
||||||
|
fetchCurrentUserSettings()
|
||||||
|
.then((userSettings: IUserSettings) => {
|
||||||
|
const workspaceSlug =
|
||||||
|
userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug;
|
||||||
|
if (workspaceSlug) router.push(`/${workspaceSlug}`);
|
||||||
|
else if (userSettings.workspace.invites > 0) router.push("/invitations");
|
||||||
|
else router.push("/create-workspace");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[fetchCurrentUserSettings, router, next_url]
|
||||||
|
);
|
||||||
|
|
||||||
|
const mutateUserInfo = useCallback(() => {
|
||||||
|
fetchCurrentUser().then((user) => {
|
||||||
|
handleLoginRedirection(user);
|
||||||
});
|
});
|
||||||
};
|
}, [fetchCurrentUser, handleLoginRedirection]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
mutateUserInfo();
|
||||||
|
}, [mutateUserInfo]);
|
||||||
|
|
||||||
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
|
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
|
||||||
try {
|
try {
|
||||||
@ -94,7 +95,7 @@ export const SignInView = observer(() => {
|
|||||||
};
|
};
|
||||||
const response = await authService.socialAuth(socialAuthPayload);
|
const response = await authService.socialAuth(socialAuthPayload);
|
||||||
if (response) {
|
if (response) {
|
||||||
handleLoginRedirection();
|
mutateUserInfo();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -121,7 +122,7 @@ export const SignInView = observer(() => {
|
|||||||
};
|
};
|
||||||
const response = await authService.socialAuth(socialAuthPayload);
|
const response = await authService.socialAuth(socialAuthPayload);
|
||||||
if (response) {
|
if (response) {
|
||||||
handleLoginRedirection();
|
mutateUserInfo();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -142,13 +143,7 @@ export const SignInView = observer(() => {
|
|||||||
return authService
|
return authService
|
||||||
.emailLogin(formData)
|
.emailLogin(formData)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
userStore.fetchCurrentUser().then((user) => {
|
mutateUserInfo();
|
||||||
const isOnboard = user.onboarding_step.profile_complete;
|
|
||||||
if (isOnboard) handleLoginRedirection();
|
|
||||||
else {
|
|
||||||
router.push("/onboarding");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -164,7 +159,7 @@ export const SignInView = observer(() => {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (response) {
|
if (response) {
|
||||||
handleLoginRedirection();
|
mutateUserInfo();
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
@ -13,7 +13,7 @@ import { Button, CustomSelect, Input } from "@plane/ui";
|
|||||||
// types
|
// types
|
||||||
import { IWorkspace } from "types";
|
import { IWorkspace } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ORGANIZATION_SIZE } from "constants/workspace";
|
import { ORGANIZATION_SIZE, RESTRICTED_URLS } from "constants/workspace";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onSubmit?: (res: IWorkspace) => Promise<void>;
|
onSubmit?: (res: IWorkspace) => Promise<void>;
|
||||||
@ -30,22 +30,6 @@ type Props = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const restrictedUrls = [
|
|
||||||
"api",
|
|
||||||
"installations",
|
|
||||||
"404",
|
|
||||||
"create-workspace",
|
|
||||||
"error",
|
|
||||||
"invitations",
|
|
||||||
"magic-sign-in",
|
|
||||||
"onboarding",
|
|
||||||
"profile",
|
|
||||||
"reset-password",
|
|
||||||
"sign-up",
|
|
||||||
"spaces",
|
|
||||||
"workspace-member-invitation",
|
|
||||||
];
|
|
||||||
|
|
||||||
const workspaceService = new WorkspaceService();
|
const workspaceService = new WorkspaceService();
|
||||||
|
|
||||||
export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
||||||
@ -81,7 +65,7 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||||||
await workspaceService
|
await workspaceService
|
||||||
.workspaceSlugCheck(formData.slug)
|
.workspaceSlugCheck(formData.slug)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (res.status === true && !restrictedUrls.includes(formData.slug)) {
|
if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) {
|
||||||
setSlugError(false);
|
setSlugError(false);
|
||||||
|
|
||||||
await workspaceStore
|
await workspaceStore
|
||||||
@ -141,7 +125,6 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||||||
render={({ field: { value, ref, onChange } }) => (
|
render={({ field: { value, ref, onChange } }) => (
|
||||||
<Input
|
<Input
|
||||||
id="workspaceName"
|
id="workspaceName"
|
||||||
name="name"
|
|
||||||
type="text"
|
type="text"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@ -167,15 +150,15 @@ export const CreateWorkspaceForm: FC<Props> = observer((props) => {
|
|||||||
rules={{
|
rules={{
|
||||||
required: "Workspace URL is required",
|
required: "Workspace URL is required",
|
||||||
}}
|
}}
|
||||||
render={({ field: { value, ref } }) => (
|
render={({ field: { onChange, value, ref } }) => (
|
||||||
<Input
|
<Input
|
||||||
id="workspaceUrl"
|
id="workspaceUrl"
|
||||||
name="slug"
|
|
||||||
type="text"
|
type="text"
|
||||||
value={value.toLocaleLowerCase().trim().replace(/ /g, "-")}
|
value={value.toLocaleLowerCase().trim().replace(/ /g, "-")}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
/^[a-zA-Z0-9_-]+$/.test(e.target.value) ? setInvalidSlug(false) : setInvalidSlug(true)
|
/^[a-zA-Z0-9_-]+$/.test(e.target.value) ? setInvalidSlug(false) : setInvalidSlug(true);
|
||||||
}
|
onChange(e.target.value.toLowerCase());
|
||||||
|
}}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
hasError={Boolean(errors.slug)}
|
hasError={Boolean(errors.slug)}
|
||||||
placeholder="Enter workspace name..."
|
placeholder="Enter workspace name..."
|
||||||
|
@ -3,7 +3,6 @@ import { useRouter } from "next/router";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
import { Check, LogOut, Plus, Settings, UserCircle2 } from "lucide-react";
|
import { Check, LogOut, Plus, Settings, UserCircle2 } from "lucide-react";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
@ -55,8 +54,6 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
const { workspaces, currentWorkspace: activeWorkspace } = workspaceStore;
|
const { workspaces, currentWorkspace: activeWorkspace } = workspaceStore;
|
||||||
const user = userStore.currentUser;
|
const user = userStore.currentUser;
|
||||||
|
|
||||||
const { setTheme } = useTheme();
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const handleWorkspaceNavigation = (workspace: IWorkspace) => {
|
const handleWorkspaceNavigation = (workspace: IWorkspace) => {
|
||||||
@ -81,7 +78,6 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
.signOut()
|
.signOut()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
router.push("/");
|
router.push("/");
|
||||||
setTheme("system");
|
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
|
@ -90,3 +90,19 @@ export const DEFAULT_GLOBAL_VIEWS_LIST: {
|
|||||||
label: "Subscribed",
|
label: "Subscribed",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const RESTRICTED_URLS = [
|
||||||
|
"api",
|
||||||
|
"installations",
|
||||||
|
"404",
|
||||||
|
"create-workspace",
|
||||||
|
"error",
|
||||||
|
"invitations",
|
||||||
|
"magic-sign-in",
|
||||||
|
"onboarding",
|
||||||
|
"profile",
|
||||||
|
"reset-password",
|
||||||
|
"sign-up",
|
||||||
|
"spaces",
|
||||||
|
"workspace-member-invitation",
|
||||||
|
];
|
||||||
|
@ -9,7 +9,7 @@ import { CURRENT_USER } from "constants/fetch-keys";
|
|||||||
import { UserService } from "services/user.service";
|
import { UserService } from "services/user.service";
|
||||||
import { WorkspaceService } from "services/workspace.service";
|
import { WorkspaceService } from "services/workspace.service";
|
||||||
// types
|
// types
|
||||||
import type { IWorkspace, IUser } from "types";
|
import type { IUser } from "types";
|
||||||
|
|
||||||
const userService = new UserService();
|
const userService = new UserService();
|
||||||
const workspaceService = new WorkspaceService();
|
const workspaceService = new WorkspaceService();
|
||||||
@ -33,9 +33,7 @@ const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "adm
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleWorkSpaceRedirection = async () => {
|
const handleWorkSpaceRedirection = async () => {
|
||||||
workspaceService.userWorkspaces().then(async (userWorkspaces) => {
|
workspaceService.userWorkspaces().then(async (userWorkspaces) => {
|
||||||
const lastActiveWorkspace = userWorkspaces.find(
|
const lastActiveWorkspace = userWorkspaces.find((workspace) => workspace.id === user?.last_workspace_id);
|
||||||
(workspace: IWorkspace) => workspace.id === user?.last_workspace_id
|
|
||||||
);
|
|
||||||
if (lastActiveWorkspace) {
|
if (lastActiveWorkspace) {
|
||||||
router.push(`/${lastActiveWorkspace.slug}`);
|
router.push(`/${lastActiveWorkspace.slug}`);
|
||||||
return;
|
return;
|
||||||
|
@ -9,6 +9,7 @@ import { ProfileAuthWrapper } from "layouts/profile-layout";
|
|||||||
import { UserProfileHeader } from "components/headers";
|
import { UserProfileHeader } from "components/headers";
|
||||||
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
|
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
|
||||||
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
||||||
|
import { Spinner } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
@ -46,7 +47,9 @@ const ProfileAssignedIssuesPage: NextPageWithLayout = observer(() => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div>Loading...</div>
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full relative overflow-auto -z-1">
|
<div className="w-full h-full relative overflow-auto -z-1">
|
||||||
{activeLayout === "list" ? (
|
{activeLayout === "list" ? (
|
||||||
|
@ -11,6 +11,7 @@ import { ProfileAuthWrapper } from "layouts/profile-layout";
|
|||||||
import { UserProfileHeader } from "components/headers";
|
import { UserProfileHeader } from "components/headers";
|
||||||
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
|
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
|
||||||
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
||||||
|
import { Spinner } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
|
|
||||||
@ -42,7 +43,9 @@ const ProfileCreatedIssuesPage: NextPageWithLayout = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div>Loading...</div>
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full relative overflow-auto -z-1">
|
<div className="w-full h-full relative overflow-auto -z-1">
|
||||||
{activeLayout === "list" ? (
|
{activeLayout === "list" ? (
|
||||||
|
@ -11,6 +11,7 @@ import { ProfileAuthWrapper } from "layouts/profile-layout";
|
|||||||
import { UserProfileHeader } from "components/headers";
|
import { UserProfileHeader } from "components/headers";
|
||||||
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
|
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
|
||||||
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
||||||
|
import { Spinner } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
|
|
||||||
@ -40,21 +41,21 @@ const ProfileSubscribedIssuesPage: NextPageWithLayout = () => {
|
|||||||
const activeLayout = profileIssueFiltersStore.userDisplayFilters.layout;
|
const activeLayout = profileIssueFiltersStore.userDisplayFilters.layout;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout header={<UserProfileHeader title="Subscribed" />}>
|
<>
|
||||||
<ProfileAuthWrapper showProfileIssuesFilter>
|
{isLoading ? (
|
||||||
{isLoading ? (
|
<div className="flex justify-center items-center w-full h-full">
|
||||||
<div>Loading...</div>
|
<Spinner />
|
||||||
) : (
|
</div>
|
||||||
<div className="w-full h-full relative overflow-auto -z-1">
|
) : (
|
||||||
{activeLayout === "list" ? (
|
<div className="w-full h-full relative overflow-auto -z-1">
|
||||||
<ProfileIssuesListLayout />
|
{activeLayout === "list" ? (
|
||||||
) : activeLayout === "kanban" ? (
|
<ProfileIssuesListLayout />
|
||||||
<ProfileIssuesKanBanLayout />
|
) : activeLayout === "kanban" ? (
|
||||||
) : null}
|
<ProfileIssuesKanBanLayout />
|
||||||
</div>
|
) : null}
|
||||||
)}
|
</div>
|
||||||
</ProfileAuthWrapper>
|
)}
|
||||||
</AppLayout>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -186,6 +186,13 @@ export class IssueFilterStore implements IIssueFilterStore {
|
|||||||
// set sub_group_by to null if group_by is set to null
|
// set sub_group_by to null if group_by is set to null
|
||||||
if (newViewProps.display_filters.group_by === null) newViewProps.display_filters.sub_group_by = null;
|
if (newViewProps.display_filters.group_by === null) newViewProps.display_filters.sub_group_by = null;
|
||||||
|
|
||||||
|
// set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same
|
||||||
|
if (
|
||||||
|
newViewProps.display_filters.layout === "kanban" &&
|
||||||
|
newViewProps.display_filters.group_by === newViewProps.display_filters.sub_group_by
|
||||||
|
)
|
||||||
|
newViewProps.display_filters.sub_group_by = null;
|
||||||
|
|
||||||
// set group_by to state if layout is switched to kanban and group_by is null
|
// set group_by to state if layout is switched to kanban and group_by is null
|
||||||
if (newViewProps.display_filters.layout === "kanban" && newViewProps.display_filters.group_by === null)
|
if (newViewProps.display_filters.layout === "kanban" && newViewProps.display_filters.group_by === null)
|
||||||
newViewProps.display_filters.group_by = "state";
|
newViewProps.display_filters.group_by = "state";
|
||||||
|
Loading…
Reference in New Issue
Block a user