Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 15 additions & 9 deletions web/core/components/home/widgets/recents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,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);
// store hooks
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 @@ -72,7 +79,7 @@ export const RecentActivityWidget: React.FC<THomeWidgetProps> = observer((props)
<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 @@ -89,8 +96,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