Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/types/src/home.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TLogoProps } from "./common";
import { TIssuePriorities } from "./issues";

export type TRecentActivityFilterKeys = "all item" | "issue" | "page" | "project";
export type TRecentActivityFilterKeys = "all item" | "issue" | "page" | "project" | "workspace_page";
export type THomeWidgetKeys = "quick_links" | "recents" | "my_stickies" | "quick_tutorial" | "new_at_plane";

export type THomeWidgetProps = {
Expand Down Expand Up @@ -39,7 +39,7 @@ export type TIssueEntityData = {

export type TActivityEntityData = {
id: string;
entity_name: "page" | "project" | "issue";
entity_name: "page" | "project" | "issue" | "workspace_page";
entity_identifier: string;
visited_at: string;
entity_data: TPageEntityData | TProjectEntityData | TIssueEntityData;
Expand Down
26 changes: 16 additions & 10 deletions web/core/components/home/widgets/recents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ const WIDGET_KEY = EWidgetKeys.RECENT_ACTIVITY;
const workspaceService = new WorkspaceService();
const filters: { name: TRecentActivityFilterKeys; icon?: React.ReactNode }[] = [
{ name: "all item" },
{ name: "issue", icon: <LayersIcon className="w-4 h-4" /> },
{ name: "page", icon: <FileText size={16} /> },
{ name: "project", icon: <Briefcase size={16} /> },
{ name: "issue", icon: <LayersIcon className="flex-shrink-0 size-4" /> },
{ name: "page", icon: <FileText className="flex-shrink-0 size-4" /> },
{ name: "workspace_page", icon: <FileText className="flex-shrink-0 size-4" /> },
{ name: "project", icon: <Briefcase className="flex-shrink-0 size-4" /> },
];

export const RecentActivityWidget: React.FC<THomeWidgetProps> = observer((props) => {
const { workspaceSlug } = props;
type TRecentWidgetProps = THomeWidgetProps & {
presetFilter?: TRecentActivityFilterKeys;
showFilterSelect?: boolean;
};

export const RecentActivityWidget: React.FC<TRecentWidgetProps> = observer((props) => {
const { presetFilter, showFilterSelect = true, workspaceSlug } = props;
// state
const [filter, setFilter] = useState<TRecentActivityFilterKeys>(filters[0].name);
const [filter, setFilter] = useState<TRecentActivityFilterKeys>(presetFilter ?? filters[0].name);
// ref
const ref = useRef<HTMLDivElement>(null);
const { joinedProjectIds, loader } = useProject();
Expand All @@ -55,6 +61,7 @@ export const RecentActivityWidget: React.FC<THomeWidgetProps> = observer((props)
const resolveRecent = (activity: TActivityEntityData) => {
switch (activity.entity_name) {
case "page":
case "workspace_page":
return <RecentPage activity={activity} ref={ref} workspaceSlug={workspaceSlug} />;
case "project":
return <RecentProject activity={activity} ref={ref} workspaceSlug={workspaceSlug} />;
Expand All @@ -68,10 +75,10 @@ export const RecentActivityWidget: React.FC<THomeWidgetProps> = observer((props)
if (!loader && joinedProjectIds?.length === 0) return <EmptyWorkspace />;
if (!isLoading && recents?.length === 0)
return (
<div ref={ref} className=" max-h-[500px] overflow-y-scroll">
<div ref={ref} className="max-h-[500px] overflow-y-scroll">
<div className="flex items-center justify-between mb-4">
<div className="text-base font-semibold text-custom-text-350">Recents</div>
<FiltersDropdown filters={filters} activeFilter={filter} setActiveFilter={setFilter} />
{showFilterSelect && <FiltersDropdown filters={filters} activeFilter={filter} setActiveFilter={setFilter} />}
</div>
<div className="flex flex-col items-center justify-center">
<RecentsEmptyState type={filter} />
Expand All @@ -88,8 +95,7 @@ export const RecentActivityWidget: React.FC<THomeWidgetProps> = observer((props)
>
<div className="flex items-center justify-between mb-2">
<div className="text-base font-semibold text-custom-text-350">Recents</div>

<FiltersDropdown filters={filters} activeFilter={filter} setActiveFilter={setFilter} />
{showFilterSelect && <FiltersDropdown filters={filters} activeFilter={filter} setActiveFilter={setFilter} />}
</div>
<div className="min-h-[250px] flex flex-col">
{isLoading && <WidgetLoader widgetKey={WIDGET_KEY} />}
Expand Down
17 changes: 14 additions & 3 deletions web/core/components/home/widgets/recents/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import { useRouter } from "next/navigation";
import { FileText } from "lucide-react";
// plane types
import { TActivityEntityData, TPageEntityData } from "@plane/types";
// plane ui
import { Avatar, Logo } from "@plane/ui";
// plane utils
import { getFileURL } from "@plane/utils";
// components
import { ListItem } from "@/components/core/list";
// helpers
import { calculateTimeAgo } from "@/helpers/date-time.helper";
// hooks
import { useMember } from "@/hooks/store";

type BlockProps = {
activity: TActivityEntityData;
ref: React.RefObject<HTMLDivElement>;
workspaceSlug: string;
};

export const RecentPage = (props: BlockProps) => {
const { activity, ref, workspaceSlug } = props;
// router
const router = useRouter();
// hooks
// store hooks
const { getUserDetails } = useMember();
// derived values
const pageDetails: TPageEntityData = activity.entity_data as TPageEntityData;
const pageDetails = activity.entity_data as TPageEntityData;
const ownerDetails = getUserDetails(pageDetails?.owned_by);
const pageLink = pageDetails.project_id
? `/${workspaceSlug}/projects/${pageDetails.project_id}/pages/${pageDetails.id}`
: `/${workspaceSlug}/pages/${pageDetails.id}`;
Comment on lines +29 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Unsafe type casting confirmed for workspace pages
The TPageEntityData type incorrectly requires project_id and project_identifier fields, but workspace pages in the backend don't have project associations. This forced casting will cause runtime errors.

  • packages/types/src/home.d.ts: Update TPageEntityData to make project fields optional
  • web/core/components/home/widgets/recents/page.tsx: Add type guard before casting to TPageEntityData
🔗 Analysis chain

Confirm handling of workspace_page data.
When entity_name is "workspace_page", verify that the entity_data still conforms to TPageEntityData (e.g., having project_id, etc.). Otherwise, this forced casting may cause runtime issues.

Use this script to scan the codebase for "workspace_page" references, checking whether the entity data structure is consistent:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Look for workflow around "workspace_page" usage to ensure it always contains page fields
rg -A 5 '"workspace_page"'

Length of output: 2869


Script:

#!/bin/bash
# Look for TPageEntityData type definition and usage
ast-grep --pattern 'type TPageEntityData = {
  $$$
}'

# Also search for any references to this type
rg "TPageEntityData"

Length of output: 889


Script:

#!/bin/bash
# Look for workspace page API handling and data structure
rg -A 5 "workspace_page.*data" --type ts
rg -A 5 "workspace_page.*response" --type ts

# Also check Python API implementation
rg -A 10 "workspace_page" --type py

Length of output: 7380


return (
<ListItem
key={activity.id}
Expand Down Expand Up @@ -58,7 +69,7 @@ export const RecentPage = (props: BlockProps) => {
onItemClick={(e) => {
e.preventDefault();
e.stopPropagation();
router.push(`/${workspaceSlug}/projects/${pageDetails?.project_id}/pages/${pageDetails.id}`);
router.push(pageLink);
}}
/>
);
Expand Down
Loading