mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
* chore: migrations and backmigration to move attachments to file asset * chore: move attachments to file assets * chore: update migration file to include created by and updated by and size * chore: remove uninmport errors * chore: make size as float field * fix: file asset uploads * chore: asset uploads migration changes * chore: v2 assets endpoint * chore: remove unused imports * chore: issue attachments * chore: issue attachments * chore: workspace logo endpoints * chore: private bucket changes * chore: user asset endpoint * chore: add logo_url validation * chore: cover image urlk * chore: change asset max length * chore: pages endpoint * chore: store the storage_metadata only when none * chore: attachment asset apis * chore: update create private bucket * chore: make bucket private * chore: fix response of user uploads * fix: response of user uploads * fix: job to fix file asset uploads * fix: user asset endpoints * chore: avatar for user profile * chore: external apis user url endpoint * chore: upload workspace and user asset actions updated * chore: analytics endpoint * fix: analytics export * chore: avatar urls * chore: update user avatar instances * chore: avatar urls for assignees and creators * chore: bucket permission script * fix: all user avatr instances in the web app * chore: update project cover image logic * fix: issue attachment endpoint * chore: patch endpoint for issue attachment * chore: attachments * chore: change attachment storage class * chore: update issue attachment endpoints * fix: issue attachment * chore: update issue attachment implementation * chore: page asset endpoints * fix: web build errors * chore: attachments * chore: page asset urls * chore: comment and issue asset endpoints * chore: asset endpoints * chore: attachment endpoints * chore: bulk asset endpoint * chore: restore endpoint * chore: project assets endpoints * chore: asset url * chore: add delete asset endpoints * chore: fix asset upload endpoint * chore: update patch endpoints * chore: update patch endpoint * chore: update editor image handling * chore: asset restore endpoints * chore: avatar url for space assets * chore: space app assets migration * fix: space app urls * chore: space endpoints * fix: old editor images rendering logic * fix: issue archive and attachment activity * chore: asset deletes * chore: attachment delete * fix: issue attachment * fix: issue attachment get * chore: cover image url for projects * chore: remove duplicate py file * fix: url check function * chore: chore project cover asset delete * fix: migrations * chore: delete migration files * chore: update bucket * fix: build errors * chore: add asset url in intake attachment * chore: project cover fix * chore: update next.config * chore: delete old workspace logos * chore: workspace assets * chore: asset get for space * chore: update project modal * chore: remove unused imports * fix: space app editor helper * chore: update rich-text read-only editor * chore: create multiple column for entity identifiers * chore: update migrations * chore: remove entity identifier * fix: issue assets * chore: update maximum file size logic * chore: update editor max file size logic * fix: close modal after removing workspace logo * chore: update uploaded asstes' status post issue creation * chore: added file size limit to the space app * dev: add file size limit restriction on all endpoints * fix: remove old workspace logo and user avatar --------- Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
127 lines
4.4 KiB
TypeScript
127 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { observer } from "mobx-react";
|
|
import Link from "next/link";
|
|
import { Plus } from "lucide-react";
|
|
// plane types
|
|
import { TRecentProjectsWidgetResponse } from "@plane/types";
|
|
// plane ui
|
|
import { Avatar, AvatarGroup, Card } from "@plane/ui";
|
|
// components
|
|
import { Logo } from "@/components/common";
|
|
import { WidgetLoader, WidgetProps } from "@/components/dashboard/widgets";
|
|
// constants
|
|
import { PROJECT_BACKGROUND_COLORS } from "@/constants/dashboard";
|
|
// helpers
|
|
import { getFileURL } from "@/helpers/file.helper";
|
|
// hooks
|
|
import { useEventTracker, useDashboard, useProject, useCommandPalette, useUserPermissions } from "@/hooks/store";
|
|
// plane web constants
|
|
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
|
|
|
const WIDGET_KEY = "recent_projects";
|
|
|
|
type ProjectListItemProps = {
|
|
projectId: string;
|
|
workspaceSlug: string;
|
|
};
|
|
|
|
const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
|
|
const { projectId, workspaceSlug } = props;
|
|
// store hooks
|
|
const { getProjectById } = useProject();
|
|
const projectDetails = getProjectById(projectId);
|
|
|
|
const randomBgColor = PROJECT_BACKGROUND_COLORS[Math.floor(Math.random() * PROJECT_BACKGROUND_COLORS.length)];
|
|
|
|
if (!projectDetails) return null;
|
|
|
|
return (
|
|
<Link href={`/${workspaceSlug}/projects/${projectId}/issues`} className="group flex items-center gap-8">
|
|
<div
|
|
className={`grid h-[3.375rem] w-[3.375rem] flex-shrink-0 place-items-center rounded border border-transparent ${randomBgColor}`}
|
|
>
|
|
<div className="grid h-7 w-7 place-items-center">
|
|
<Logo logo={projectDetails.logo_props} size={20} />
|
|
</div>
|
|
</div>
|
|
<div className="flex-grow truncate">
|
|
<h6 className="truncate text-sm font-medium text-custom-text-300 group-hover:text-custom-text-100 group-hover:underline">
|
|
{projectDetails.name}
|
|
</h6>
|
|
<div className="mt-2">
|
|
<AvatarGroup>
|
|
{projectDetails.members?.map((member) => (
|
|
<Avatar
|
|
key={member.member_id}
|
|
src={getFileURL(member.member__avatar_url)}
|
|
name={member.member__display_name}
|
|
/>
|
|
))}
|
|
</AvatarGroup>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
);
|
|
});
|
|
|
|
export const RecentProjectsWidget: React.FC<WidgetProps> = observer((props) => {
|
|
const { dashboardId, workspaceSlug } = props;
|
|
// store hooks
|
|
const { toggleCreateProjectModal } = useCommandPalette();
|
|
const { setTrackElement } = useEventTracker();
|
|
const { allowPermissions } = useUserPermissions();
|
|
const { fetchWidgetStats, getWidgetStats } = useDashboard();
|
|
// derived values
|
|
const widgetStats = getWidgetStats<TRecentProjectsWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY);
|
|
const canCreateProject = allowPermissions(
|
|
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
|
EUserPermissionsLevel.WORKSPACE
|
|
);
|
|
|
|
useEffect(() => {
|
|
fetchWidgetStats(workspaceSlug, dashboardId, {
|
|
widget_key: WIDGET_KEY,
|
|
});
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
|
|
|
|
return (
|
|
<Card>
|
|
<Link
|
|
href={`/${workspaceSlug}/projects`}
|
|
className="text-lg font-semibold text-custom-text-300 hover:underline mb-4"
|
|
>
|
|
Recent projects
|
|
</Link>
|
|
<div className="mt-4 space-y-8">
|
|
{canCreateProject && (
|
|
<button
|
|
type="button"
|
|
className="group flex items-center gap-8"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
setTrackElement("Sidebar");
|
|
toggleCreateProjectModal(true);
|
|
}}
|
|
>
|
|
<div className="grid h-[3.375rem] w-[3.375rem] flex-shrink-0 place-items-center rounded border border-dashed border-custom-primary-60 bg-custom-primary-100/20 text-custom-primary-100">
|
|
<Plus className="h-6 w-6" />
|
|
</div>
|
|
<p className="text-sm font-medium text-custom-text-300 group-hover:text-custom-text-100 group-hover:underline">
|
|
Create new project
|
|
</p>
|
|
</button>
|
|
)}
|
|
{widgetStats.map((projectId) => (
|
|
<ProjectListItem key={projectId} projectId={projectId} workspaceSlug={workspaceSlug} />
|
|
))}
|
|
</div>
|
|
</Card>
|
|
);
|
|
});
|