Merge branch 'develop' of github.com:makeplane/plane into chore/breadcrumb_component_revamp

This commit is contained in:
Anmol Singh Bhatia 2023-11-03 15:40:57 +05:30
commit b0fd935141
16 changed files with 160 additions and 160 deletions

View File

@ -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',

View File

@ -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',),
},
)
]

View File

@ -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,

View File

@ -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}

View File

@ -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

View File

@ -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}

View File

@ -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}

View File

@ -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);

View File

@ -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..."

View File

@ -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({

View File

@ -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",
];

View File

@ -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;

View File

@ -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" ? (

View File

@ -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" ? (

View File

@ -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> </>
); );
}; };

View File

@ -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";