diff --git a/apiserver/plane/app/serializers/cycle.py b/apiserver/plane/app/serializers/cycle.py index 104a3dd06..63abf3a03 100644 --- a/apiserver/plane/app/serializers/cycle.py +++ b/apiserver/plane/app/serializers/cycle.py @@ -40,6 +40,7 @@ class CycleSerializer(BaseSerializer): started_estimates = serializers.IntegerField(read_only=True) workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") project_detail = ProjectLiteSerializer(read_only=True, source="project") + status = serializers.CharField(read_only=True) def validate(self, data): if ( diff --git a/apiserver/plane/app/views/cycle.py b/apiserver/plane/app/views/cycle.py index d2f82d75b..02f259de3 100644 --- a/apiserver/plane/app/views/cycle.py +++ b/apiserver/plane/app/views/cycle.py @@ -11,6 +11,10 @@ from django.db.models import ( Count, Prefetch, Sum, + Case, + When, + Value, + CharField ) from django.core import serializers from django.utils import timezone @@ -157,6 +161,28 @@ class CycleViewSet(WebhookMixin, BaseViewSet): ), ) ) + .annotate( + status=Case( + When( + Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()), + then=Value("CURRENT") + ), + When( + start_date__gt=timezone.now(), + then=Value("UPCOMING") + ), + When( + end_date__lt=timezone.now(), + then=Value("COMPLETED") + ), + When( + Q(start_date__isnull=True) & Q(end_date__isnull=True), + then=Value("DRAFT") + ), + default=Value("DRAFT"), + output_field=CharField(), + ) + ) .prefetch_related( Prefetch( "issue_cycle__issue__assignees", @@ -177,7 +203,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet): queryset = self.get_queryset() cycle_view = request.GET.get("cycle_view", "all") - queryset = queryset.order_by("-is_favorite","-created_at") + queryset = queryset.order_by("-is_favorite", "-created_at") # Current Cycle if cycle_view == "current": @@ -575,7 +601,9 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet): ) ) - issues = IssueStateSerializer(issues, many=True, fields=fields if fields else None).data + issues = IssueStateSerializer( + issues, many=True, fields=fields if fields else None + ).data issue_dict = {str(issue["id"]): issue for issue in issues} return Response(issue_dict, status=status.HTTP_200_OK) @@ -805,4 +833,4 @@ class TransferCycleIssueEndpoint(BaseAPIView): updated_cycles, ["cycle_id"], batch_size=100 ) - return Response({"message": "Success"}, status=status.HTTP_200_OK) \ No newline at end of file + return Response({"message": "Success"}, status=status.HTTP_200_OK) diff --git a/web/components/auth-screens/not-authorized-view.tsx b/web/components/auth-screens/not-authorized-view.tsx index 353bb8fce..f0a3e3d90 100644 --- a/web/components/auth-screens/not-authorized-view.tsx +++ b/web/components/auth-screens/not-authorized-view.tsx @@ -18,7 +18,8 @@ type Props = { export const NotAuthorizedView: React.FC = ({ actionButton, type }) => { const { user } = useUser(); - const { asPath: currentPath } = useRouter(); + const { query } = useRouter(); + const { next_path } = query; return ( @@ -37,7 +38,7 @@ export const NotAuthorizedView: React.FC = ({ actionButton, type }) => { {user ? (

You have signed in as {user.email}.
- + Sign in {" "} with different account that has access to this page. @@ -45,7 +46,7 @@ export const NotAuthorizedView: React.FC = ({ actionButton, type }) => { ) : (

You need to{" "} - + Sign in {" "} with an account that has access to this page. diff --git a/web/components/core/activity.tsx b/web/components/core/activity.tsx index 37f8d2626..1ac34cf73 100644 --- a/web/components/core/activity.tsx +++ b/web/components/core/activity.tsx @@ -11,7 +11,6 @@ import { CopyPlus, Calendar, Link2Icon, - RocketIcon, Users2Icon, ArchiveIcon, PaperclipIcon, @@ -48,8 +47,8 @@ const IssueLink = ({ activity }: { activity: IIssueActivity }) => { rel={activity.issue === null ? "" : "noopener noreferrer"} className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > - {activity.issue_detail ? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}` : "Issue"} - + {activity.issue_detail ? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}` : "Issue"}{" "} + {activity.issue_detail?.name} ); @@ -163,7 +162,6 @@ const activityDetails: { className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > attachment - {showIssue && ( <> @@ -239,7 +237,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.new_value} - ); @@ -254,7 +251,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.new_value} - ); @@ -269,7 +265,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.old_value} - ); @@ -398,7 +393,6 @@ const activityDetails: { className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > link - {showIssue && ( <> @@ -420,7 +414,6 @@ const activityDetails: { className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > link - {showIssue && ( <> @@ -442,7 +435,6 @@ const activityDetails: { className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > link - {showIssue && ( <> @@ -469,7 +461,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.new_value} - ); @@ -484,7 +475,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.new_value} - ); @@ -499,7 +489,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.old_value} - ); diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index ea982099f..47beaa262 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -28,7 +28,7 @@ import { ViewIssueLabel } from "components/issues"; // icons import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } from "lucide-react"; // helpers -import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; +import { renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; import { truncateText } from "helpers/string.helper"; // types import { ICycle } from "types"; @@ -137,7 +137,7 @@ export const ActiveCycleDetails: React.FC = observer((props cancelled: cycle.cancelled_issues, }; - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status.toLocaleLowerCase(); const handleAddToFavorites = (e: MouseEvent) => { e.preventDefault(); diff --git a/web/components/cycles/cycles-board-card.tsx b/web/components/cycles/cycles-board-card.tsx index f020b0998..d43d56872 100644 --- a/web/components/cycles/cycles-board-card.tsx +++ b/web/components/cycles/cycles-board-card.tsx @@ -10,15 +10,10 @@ import { Avatar, AvatarGroup, CustomMenu, Tooltip, LayersIcon, CycleGroupIcon } // icons import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react"; // helpers -import { - getDateRangeStatus, - findHowManyDaysLeft, - renderShortDate, - renderShortMonthDate, -} from "helpers/date-time.helper"; +import { findHowManyDaysLeft, renderShortDate, renderShortMonthDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // types -import { ICycle } from "types"; +import { ICycle, TCycleGroups } from "types"; // store import { useMobxStore } from "lib/mobx/store-provider"; // constants @@ -45,7 +40,7 @@ export const CyclesBoardCard: FC = (props) => { const [updateModal, setUpdateModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false); // computed - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status.toLocaleLowerCase() as TCycleGroups; const isCompleted = cycleStatus === "completed"; const endDate = new Date(cycle.end_date ?? ""); const startDate = new Date(cycle.start_date ?? ""); diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index 86b3bffa9..9ea26ab39 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -13,15 +13,10 @@ import { CustomMenu, Tooltip, CircularProgressIndicator, CycleGroupIcon, AvatarG // icons import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react"; // helpers -import { - getDateRangeStatus, - findHowManyDaysLeft, - renderShortDate, - renderShortMonthDate, -} from "helpers/date-time.helper"; +import { findHowManyDaysLeft, renderShortDate, renderShortMonthDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // types -import { ICycle } from "types"; +import { ICycle, TCycleGroups } from "types"; // constants import { CYCLE_STATUS } from "constants/cycle"; import { EUserWorkspaceRoles } from "constants/workspace"; @@ -50,7 +45,7 @@ export const CyclesListItem: FC = (props) => { const [updateModal, setUpdateModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false); // computed - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status.toLocaleLowerCase() as TCycleGroups; const isCompleted = cycleStatus === "completed"; const endDate = new Date(cycle.end_date ?? ""); const startDate = new Date(cycle.start_date ?? ""); diff --git a/web/components/cycles/gantt-chart/blocks.tsx b/web/components/cycles/gantt-chart/blocks.tsx index 03614592c..76a4d9235 100644 --- a/web/components/cycles/gantt-chart/blocks.tsx +++ b/web/components/cycles/gantt-chart/blocks.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; // ui import { Tooltip, ContrastIcon } from "@plane/ui"; // helpers -import { getDateRangeStatus, renderShortDate } from "helpers/date-time.helper"; +import { renderShortDate } from "helpers/date-time.helper"; // types import { ICycle } from "types"; @@ -11,8 +11,7 @@ export const CycleGanttBlock = ({ data }: { data: ICycle }) => { const router = useRouter(); const { workspaceSlug } = router.query; - const cycleStatus = getDateRangeStatus(data?.start_date, data?.end_date); - + const cycleStatus = data.status.toLocaleLowerCase(); return (

{ cycleStatus === "current" ? "#09a953" : cycleStatus === "upcoming" - ? "#f7ae59" - : cycleStatus === "completed" - ? "#3f76ff" - : cycleStatus === "draft" - ? "rgb(var(--color-text-200))" - : "", + ? "#f7ae59" + : cycleStatus === "completed" + ? "#3f76ff" + : cycleStatus === "draft" + ? "rgb(var(--color-text-200))" + : "", }} onClick={() => router.push(`/${workspaceSlug}/projects/${data?.project}/cycles/${data?.id}`)} > @@ -52,7 +51,7 @@ export const CycleGanttSidebarBlock = ({ data }: { data: ICycle }) => { const router = useRouter(); const { workspaceSlug } = router.query; - const cycleStatus = getDateRangeStatus(data?.start_date, data?.end_date); + const cycleStatus = data.status.toLocaleLowerCase(); return (
{ cycleStatus === "current" ? "#09a953" : cycleStatus === "upcoming" - ? "#f7ae59" - : cycleStatus === "completed" - ? "#3f76ff" - : cycleStatus === "draft" - ? "rgb(var(--color-text-200))" - : "" + ? "#f7ae59" + : cycleStatus === "completed" + ? "#3f76ff" + : cycleStatus === "draft" + ? "rgb(var(--color-text-200))" + : "" }`} />
{data?.name}
diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index dcf86bb46..18c233d6c 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -31,7 +31,6 @@ import { import { copyUrlToClipboard } from "helpers/string.helper"; import { findHowManyDaysLeft, - getDateRangeStatus, isDateGreaterThanToday, renderDateFormat, renderShortDate, @@ -275,10 +274,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { [workspaceSlug, projectId, cycleId, issueFilters, updateFilters] ); - const cycleStatus = - cycleDetails?.start_date && cycleDetails?.end_date - ? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date) - : "draft"; + const cycleStatus = cycleDetails.status.toLocaleLowerCase(); const isCompleted = cycleStatus === "completed"; const isStartValid = new Date(`${cycleDetails?.start_date}`) <= new Date(); diff --git a/web/components/cycles/transfer-issues-modal.tsx b/web/components/cycles/transfer-issues-modal.tsx index 55555e221..dd462e360 100644 --- a/web/components/cycles/transfer-issues-modal.tsx +++ b/web/components/cycles/transfer-issues-modal.tsx @@ -15,8 +15,6 @@ import { AlertCircle, Search, X } from "lucide-react"; import { INCOMPLETE_CYCLES_LIST } from "constants/fetch-keys"; // types import { ICycle } from "types"; -//helper -import { getDateRangeStatus } from "helpers/date-time.helper"; type Props = { isOpen: boolean; @@ -138,7 +136,7 @@ export const TransferIssuesModal: React.FC = observer(({ isOpen, handleCl
{option?.name} - {getDateRangeStatus(option?.start_date, option?.end_date)} + {option.status}
diff --git a/web/components/headers/global-issues.tsx b/web/components/headers/global-issues.tsx index ef4c2b3f5..0b27bdd81 100644 --- a/web/components/headers/global-issues.tsx +++ b/web/components/headers/global-issues.tsx @@ -95,7 +95,7 @@ export const GlobalIssuesHeader: React.FC = observer((props) => { ) } - label={`Workspace ${activeLayout === "spreadsheet" ? "Issues" : "Views"}`} + label={`All ${activeLayout === "spreadsheet" ? "Issues" : "Views"}`} />
diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx index 07631a7de..133cce1f9 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx @@ -23,7 +23,7 @@ export const ArchivedIssueQuickActions: React.FC = (props) => const { setToastAlert } = useToast(); const handleCopyIssueLink = () => { - copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/archived-issues/${issue.id}`).then(() => + copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/archived-issues/${issue.id}`).then(() => setToastAlert({ type: "success", title: "Link copied", diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx index 5faace440..af018a652 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx @@ -27,7 +27,7 @@ export const CycleIssueQuickActions: React.FC = (props) => { const { setToastAlert } = useToast(); const handleCopyIssueLink = () => { - copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => + copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => setToastAlert({ type: "success", title: "Link copied", diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx index 7bd35e321..0ad1f610b 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx @@ -27,7 +27,7 @@ export const ModuleIssueQuickActions: React.FC = (props) => { const { setToastAlert } = useToast(); const handleCopyIssueLink = () => { - copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => + copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => setToastAlert({ type: "success", title: "Link copied", diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx index 0f4fe28ce..12438b2a3 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx @@ -37,7 +37,7 @@ export const ProjectIssueQuickActions: React.FC = (props) => const { setToastAlert } = useToast(); const handleCopyIssueLink = () => { - copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => + copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => setToastAlert({ type: "success", title: "Link copied", diff --git a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx index f967956f0..f77dfbed4 100644 --- a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx @@ -17,8 +17,6 @@ import { import { TransferIssues, TransferIssuesModal } from "components/cycles"; // ui import { Spinner } from "@plane/ui"; -// helpers -import { getDateRangeStatus } from "helpers/date-time.helper"; export const CycleLayoutRoot: React.FC = observer(() => { const [transferIssuesModal, setTransferIssuesModal] = useState(false); @@ -50,10 +48,7 @@ export const CycleLayoutRoot: React.FC = observer(() => { const activeLayout = issueFilters?.displayFilters?.layout; const cycleDetails = cycleId ? cycleStore.cycle_details[cycleId.toString()] : undefined; - const cycleStatus = - cycleDetails?.start_date && cycleDetails?.end_date - ? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date) - : "draft"; + const cycleStatus = cycleDetails?.status.toLocaleLowerCase() ?? "draft"; return ( <> diff --git a/web/components/issues/peek-overview/activity/card.tsx b/web/components/issues/peek-overview/activity/card.tsx index b8acb63d9..86d1a138c 100644 --- a/web/components/issues/peek-overview/activity/card.tsx +++ b/web/components/issues/peek-overview/activity/card.tsx @@ -88,7 +88,7 @@ export const IssueActivityCard: FC = (props) => {
-
+
{activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? ( Plane ) : activityItem.actor_detail.is_bot ? ( @@ -101,8 +101,8 @@ export const IssueActivityCard: FC = (props) => { : activityItem.actor_detail.display_name} - )}{" "} - {message}{" "} + )} + {message} = ({ onChange, issueDetails, p issueId={issueId as string} projectId={projectId as string} /> - - + + {issueDetails?.parent && ( + )} - {issueDetails?.parent && } - +
); }; diff --git a/web/components/profile/overview/activity.tsx b/web/components/profile/overview/activity.tsx index 1bba6a25f..9512a98f2 100644 --- a/web/components/profile/overview/activity.tsx +++ b/web/components/profile/overview/activity.tsx @@ -1,7 +1,5 @@ import { useRouter } from "next/router"; - import useSWR from "swr"; - // services import { UserService } from "services/user.service"; // components @@ -9,7 +7,6 @@ import { ActivityMessage } from "components/core"; // ui import { ProfileEmptyState } from "components/ui"; import { Loader } from "@plane/ui"; -import { Rocket } from "lucide-react"; // image import recentActivityEmptyState from "public/empty-state/recent_activity.svg"; // helpers @@ -67,10 +64,9 @@ export const ProfileActivity = () => { href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`} target="_blank" rel="noopener noreferrer" - className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" + className="inline-flex items-center gap-1 font-medium text-custom-text-200 hover:underline" > - Issue - + Issue. )} diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts index fa346d5aa..ab6116d77 100644 --- a/web/helpers/date-time.helper.ts +++ b/web/helpers/date-time.helper.ts @@ -170,18 +170,6 @@ export const formatLongDateDistance = (date: string | Date) => { } }; -export const getDateRangeStatus = (startDate: string | null | undefined, endDate: string | null | undefined) => { - if (!startDate || !endDate) return "draft"; - - const now = new Date(); - const start = new Date(startDate); - const end = new Date(endDate); - - if (start <= now && end >= now) return "current"; - else if (start > now) return "upcoming"; - else return "completed"; -}; - export const renderShortDateWithYearFormat = (date: string | Date, placeholder?: string) => { if (!date || date === "") return null; diff --git a/web/hooks/use-sign-in-redirection.ts b/web/hooks/use-sign-in-redirection.ts index 1863e510e..25d4e8bbd 100644 --- a/web/hooks/use-sign-in-redirection.ts +++ b/web/hooks/use-sign-in-redirection.ts @@ -17,36 +17,54 @@ const useSignInRedirection = (): UseSignInRedirectionProps => { const [error, setError] = useState(null); // router const router = useRouter(); - const { next_url } = router.query; + const { next_path } = router.query; // mobx store const { user: { fetchCurrentUser, fetchCurrentUserSettings }, } = useMobxStore(); + const isValidURL = (url: string): boolean => { + const disallowedSchemes = /^(https?|ftp):\/\//i; + return !disallowedSchemes.test(url); + }; + + console.log("next_path", next_path); + const handleSignInRedirection = useCallback( async (user: IUser) => { - // if the user is not onboarded, redirect them to the onboarding page - if (!user.is_onboarded) { - router.push("/onboarding"); - return; - } - // if next_url is provided, redirect the user to that url - if (next_url) { - router.push(next_url.toString()); - return; - } + try { + // if the user is not onboarded, redirect them to the onboarding page + if (!user.is_onboarded) { + router.push("/onboarding"); + return; + } + // if next_path is provided, redirect the user to that url + if (next_path) { + if (isValidURL(next_path.toString())) { + router.push(next_path.toString()); + return; + } else { + router.push("/"); + return; + } + } - // if the user is onboarded, fetch their last workspace details - await fetchCurrentUserSettings() - .then((userSettings: IUserSettings) => { - const workspaceSlug = - userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug; - if (workspaceSlug) router.push(`/${workspaceSlug}`); - else router.push("/profile"); - }) - .catch((err) => setError(err)); + // Fetch the current user settings + const userSettings: IUserSettings = await fetchCurrentUserSettings(); + + // Extract workspace details + const workspaceSlug = + userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug; + + // Redirect based on workspace details or to profile if not available + if (workspaceSlug) router.push(`/${workspaceSlug}`); + else router.push("/profile"); + } catch (error) { + console.error("Error in handleSignInRedirection:", error); + setError(error); + } }, - [fetchCurrentUserSettings, router, next_url] + [fetchCurrentUserSettings, router, next_path] ); const updateUserInfo = useCallback(async () => { diff --git a/web/hooks/use-user-auth.tsx b/web/hooks/use-user-auth.tsx index 882c0b713..8290a4545 100644 --- a/web/hooks/use-user-auth.tsx +++ b/web/hooks/use-user-auth.tsx @@ -12,7 +12,7 @@ const workspaceService = new WorkspaceService(); const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "admin") => { const router = useRouter(); - const { next_url } = router.query; + const { next_path } = router.query; const [isRouteAccess, setIsRouteAccess] = useState(true); const { @@ -29,6 +29,11 @@ const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "adm shouldRetryOnError: false, }); + const isValidURL = (url: string): boolean => { + const disallowedSchemes = /^(https?|ftp):\/\//i; + return !disallowedSchemes.test(url); + }; + useEffect(() => { const handleWorkSpaceRedirection = async () => { workspaceService.userWorkspaces().then(async (userWorkspaces) => { @@ -84,8 +89,15 @@ const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "adm if (!isLoading) { setIsRouteAccess(() => true); if (user) { - if (next_url) router.push(next_url.toString()); - else handleUserRouteAuthentication(); + if (next_path) { + if (isValidURL(next_path.toString())) { + router.push(next_path.toString()); + return; + } else { + router.push("/"); + return; + } + } else handleUserRouteAuthentication(); } else { if (routeAuth === "sign-in") { setIsRouteAccess(() => false); @@ -97,7 +109,7 @@ const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "adm } } } - }, [user, isLoading, routeAuth, router, next_url]); + }, [user, isLoading, routeAuth, router, next_path]); return { isLoading: isRouteAccess, diff --git a/web/hooks/use-user.tsx b/web/hooks/use-user.tsx index 97518fed6..2203faaa2 100644 --- a/web/hooks/use-user.tsx +++ b/web/hooks/use-user.tsx @@ -31,7 +31,7 @@ export default function useUser({ redirectTo = "", redirectIfFound = false, opti ) { router.push(redirectTo); return; - // const nextLocation = router.asPath.split("?next=")[1]; + // const nextLocation = router.asPath.split("?next_path=")[1]; // if (nextLocation) { // router.push(nextLocation as string); // return; diff --git a/web/layouts/auth-layout/user-wrapper.tsx b/web/layouts/auth-layout/user-wrapper.tsx index 50b17fdb7..ccc30a382 100644 --- a/web/layouts/auth-layout/user-wrapper.tsx +++ b/web/layouts/auth-layout/user-wrapper.tsx @@ -56,7 +56,7 @@ export const UserAuthWrapper: FC = observer((props) => { if (currentUserError) { const redirectTo = router.asPath; - router.push(`/?next=${redirectTo}`); + router.push(`/?next_path=${redirectTo}`); return null; } diff --git a/web/pages/onboarding/index.tsx b/web/pages/onboarding/index.tsx index f6df0c4fc..e695b1f4c 100644 --- a/web/pages/onboarding/index.tsx +++ b/web/pages/onboarding/index.tsx @@ -11,8 +11,6 @@ import { Controller, useForm } from "react-hook-form"; import { useMobxStore } from "lib/mobx/store-provider"; // services import { WorkspaceService } from "services/workspace.service"; -// hooks -import useUserAuth from "hooks/use-user-auth"; // layouts import DefaultLayout from "layouts/default-layout"; import { UserAuthWrapper } from "layouts/auth-layout"; @@ -45,8 +43,6 @@ const OnboardingPage: NextPageWithLayout = observer(() => { const { setTheme } = useTheme(); - const {} = useUserAuth("onboarding"); - const { control, setValue } = useForm<{ full_name: string }>({ defaultValues: { full_name: "", @@ -158,8 +154,8 @@ const OnboardingPage: NextPageWithLayout = observer(() => { currentUser?.first_name ? `${currentUser?.first_name} ${currentUser?.last_name ?? ""}` : value.length > 0 - ? value - : currentUser?.email + ? value + : currentUser?.email } src={currentUser?.avatar} size={35} @@ -174,8 +170,8 @@ const OnboardingPage: NextPageWithLayout = observer(() => { {currentUser?.first_name ? `${currentUser?.first_name} ${currentUser?.last_name ?? ""}` : value.length > 0 - ? value - : null} + ? value + : null}

)} diff --git a/web/store/cycle/cycles.store.ts b/web/store/cycle/cycles.store.ts index 96122ec14..b6602172d 100644 --- a/web/store/cycle/cycles.store.ts +++ b/web/store/cycle/cycles.store.ts @@ -7,7 +7,6 @@ import { RootStore } from "../root"; import { ProjectService } from "services/project"; import { IssueService } from "services/issue"; import { CycleService } from "services/cycle.service"; -import { getDateRangeStatus } from "helpers/date-time.helper"; export interface ICycleStore { loader: boolean; @@ -318,7 +317,7 @@ export class CycleStore implements ICycleStore { }; addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycle: ICycle) => { - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status; const statusCyclesList = this.cycles[projectId]?.[cycleStatus] ?? []; const allCyclesList = this.projectCycles ?? []; @@ -379,7 +378,7 @@ export class CycleStore implements ICycleStore { }; removeCycleFromFavorites = async (workspaceSlug: string, projectId: string, cycle: ICycle) => { - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status; const statusCyclesList = this.cycles[projectId]?.[cycleStatus] ?? []; const allCyclesList = this.projectCycles ?? []; diff --git a/web/types/cycles.d.ts b/web/types/cycles.d.ts index c3c5248aa..4f243deeb 100644 --- a/web/types/cycles.d.ts +++ b/web/types/cycles.d.ts @@ -2,6 +2,8 @@ import type { IUser, IIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft"; +export type TCycleGroups = "current" | "upcoming" | "completed" | "draft"; + export type TCycleLayout = "list" | "board" | "gantt"; export interface ICycle { @@ -24,6 +26,7 @@ export interface ICycle { owned_by: IUser; project: string; project_detail: IProjectLite; + status: TCycleGroups; sort_order: number; start_date: string | null; started_issues: number;