-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WEB-3096] feat: stickies page #6380
Conversation
… into feat-stickies-page
WalkthroughThis pull request introduces significant enhancements to the sticky notes functionality within the Plane application. Key changes include modifications to the Changes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
🧹 Nitpick comments (26)
web/core/components/stickies/sticky/sticky-item-drag-handle.tsx (1)
18-20
: Consider accessibility improvements for drag handle.The drag handle's visibility relies solely on hover state (
group-hover
). This might cause accessibility issues for keyboard and touch device users.Consider adding keyboard focus visibility:
-"hidden group-hover/sticky:flex absolute top-3 left-1/2 -translate-x-1/2 items-center justify-center rounded text-custom-sidebar-text-400 cursor-grab mr-2 rotate-90" +"hidden group-hover/sticky:flex focus-within:flex absolute top-3 left-1/2 -translate-x-1/2 items-center justify-center rounded text-custom-sidebar-text-400 cursor-grab mr-2 rotate-90"web/core/components/stickies/sticky/inputs.tsx (2)
76-76
: Consider using CSS custom properties for dimensions.Hardcoding dimensions in class names makes it harder to maintain consistent spacing across components.
Consider using CSS variables:
-containerClassName={"px-0 text-base min-h-[250px] w-full text-[#455068]"} +containerClassName={"px-0 text-base min-h-[var(--sticky-min-height)] w-full text-[var(--text-color-secondary)]"}
78-78
: Consider adding a default value for showToolbar prop.The
showToolbar
prop is passed through without a default value, which could lead to undefined behavior.Add a default value in the destructuring:
-showToolbar={showToolbar} +showToolbar={showToolbar ?? true}web/core/components/stickies/sticky/root.tsx (2)
24-25
: Consider memoizing pathname check.The pathname check could cause unnecessary re-renders when the path changes but doesn't affect the sticky functionality.
Consider using useMemo:
-const pathName = usePathname(); +const showDragHandle = useMemo(() => { + const pathName = usePathname(); + return pathName?.includes("stickies") ?? false; +}, [usePathname()]);
69-69
: Remove unnecessary string literal.The empty string literal after the conditional rendering is unnecessary.
-{pathName?.includes("stickies") && <StickyItemDragHandle isDragging={false} />}{" "} +{pathName?.includes("stickies") && <StickyItemDragHandle isDragging={false} />}web/core/components/editor/sticky-editor/editor.tsx (1)
85-106
: Consider extracting toolbar visibility logic.The toolbar visibility logic combines multiple concerns: conditional rendering, animation, and state management.
Consider extracting this into a separate component:
interface ToolbarWrapperProps { show: boolean; isFocused: boolean; editorRef: EditorRefApi | null; handleDelete: () => void; handleColorChange: (data: Partial<TSticky>) => Promise<void>; } const ToolbarWrapper: React.FC<ToolbarWrapperProps> = ({ show, isFocused, editorRef, handleDelete, handleColorChange, }) => { if (!show) return null; return ( <div className={cn( "transition-all duration-300 ease-out origin-top", isFocused ? "max-h-[200px] opacity-100 scale-y-100 mt-3" : "max-h-0 opacity-0 scale-y-0 invisible" )} > <Toolbar executeCommand={(item) => { editorRef?.executeMenuItemCommand({ itemKey: item.itemKey, ...item.extraProps, }); }} handleDelete={handleDelete} handleColorChange={handleColorChange} editorRef={editorRef} /> </div> ); };web/core/components/stickies/layout/stickies-truncated.tsx (1)
17-21
: Consider adding error handling for SWR data fetchingThe SWR configuration disables revalidation, but there's no error handling or loading state management visible.
Consider adding error and loading states:
useSWR( workspaceSlug ? `WORKSPACE_STICKIES_${workspaceSlug}` : null, workspaceSlug ? () => fetchWorkspaceStickies(workspaceSlug.toString()) : null, - { revalidateIfStale: false, revalidateOnFocus: false } + { + revalidateIfStale: false, + revalidateOnFocus: false, + onError: (error) => { + // Handle error appropriately + console.error("Failed to fetch stickies:", error); + } + }web/core/components/stickies/widget.tsx (1)
41-41
: Consider adding loading state for StickiesTruncatedThe StickiesTruncated component might benefit from a loading state while data is being fetched.
- <StickiesTruncated /> + {/* Add a loading state wrapper */} + <Suspense fallback={<LoadingSpinner />}> + <StickiesTruncated /> + </Suspense>web/core/components/stickies/modal/stickies.tsx (1)
63-63
: Add aria-label for better accessibilityThe scrollable content area should have an appropriate aria-label.
- <div className="mb-4 max-h-[625px] overflow-scroll"> + <div + className="mb-4 max-h-[625px] overflow-scroll" + aria-label="Stickies content" + >web/app/[workspaceSlug]/(projects)/stickies/layout.tsx (1)
6-13
: LGTM! Consider using type alias for propsThe layout implementation is clean and follows best practices. Consider extracting the props type to improve reusability:
type WorkspaceStickiesLayoutProps = { children: React.ReactNode; }; export default function WorkspaceStickiesLayout({ children }: WorkspaceStickiesLayoutProps) { // ... rest of the implementation }web/app/[workspaceSlug]/(projects)/stickies/page.tsx (1)
14-14
: Improve type safety for workspaceSlugInstead of type casting, consider using type guard for better type safety:
const workspaceSlug = typeof routeWorkspaceSlug === 'string' ? routeWorkspaceSlug : undefined;apiserver/plane/db/models/sticky.py (1)
39-44
: Consider handling potential HTML processing errors.While the HTML stripping logic is correct, it should handle potential exceptions from the
strip_tags
function to prevent save failures.def save(self, *args, **kwargs): # Strip the html tags using html parser - self.description_stripped = ( - None - if (self.description_html == "" or self.description_html is None) - else strip_tags(self.description_html) - ) + try: + self.description_stripped = ( + None + if (self.description_html == "" or self.description_html is None) + else strip_tags(self.description_html) + ) + except Exception as e: + # Log the error but don't block the save + self.description_stripped = Noneweb/core/components/stickies/layout/sticky.helpers.ts (1)
18-45
: Consider adding error boundaries for edge cases.The function handles multiple scenarios well, but could benefit from additional error handling for malformed data.
export const getInstructionFromPayload = ( dropTarget: TDropTarget, source: TDropTarget, location: IPragmaticPayloadLocation ): InstructionType | undefined => { + // Early validation of required parameters + if (!dropTarget || !source || !location) { + console.warn('Invalid parameters provided to getInstructionFromPayload'); + return undefined; + } const dropTargetData = dropTarget?.data as TargetData; const sourceData = source?.data as TargetData; const allDropTargets = location?.current?.dropTargets;apiserver/plane/app/views/workspace/sticky.py (1)
42-44
: Consider adding index for performance optimization.The ordering and filtering on
sort_order
anddescription_stripped
fields would benefit from database indexes for better query performance.Consider adding the following indexes to your model:
class Meta: indexes = [ models.Index(fields=['-sort_order']), models.Index(fields=['description_stripped']), ]web/core/components/stickies/layout/stickies-infinite.tsx (2)
29-32
: Add debouncing to prevent rapid load more calls.The
handleLoadMore
function should be debounced to prevent multiple rapid calls when scrolling.+import { debounce } from 'lodash'; -const handleLoadMore = () => { +const handleLoadMore = debounce(() => { if (loader === "pagination") return; fetchNextWorkspaceStickies(workspaceSlug?.toString()); -}; +}, 300);
46-57
: Consider adding aria-label for accessibility.The intersection element should include appropriate ARIA attributes for screen readers.
<div className={cn("flex min-h-[300px] box-border p-2 w-full")} ref={setElementRef} id="intersection-element" + role="status" + aria-label="Loading more stickies" >web/core/components/stickies/modal/search.tsx (1)
41-46
: Consider memoizing dependencies for debounced search.The
debouncedSearch
callback depends onfetchStickies
, butfetchStickies
itself depends onworkspaceSlug
andfetchWorkspaceStickies
. Consider including these dependencies in the dependency array to prevent stale closures.const debouncedSearch = useCallback( debounce(async () => { await fetchStickies(); }, 500), - [fetchWorkspaceStickies] + [fetchWorkspaceStickies, workspaceSlug] );web/core/components/stickies/sticky/use-operations.tsx (1)
23-26
: Cache random color generation function.The
getRandomStickyColor
function should be memoized to prevent unnecessary recalculations.+ const memoizedGetRandomStickyColor = useMemo(() => { const getRandomStickyColor = (): string => { const randomIndex = Math.floor(Math.random() * STICKY_COLORS.length); return STICKY_COLORS[randomIndex]; }; + return getRandomStickyColor; + }, []);web/core/components/core/content-overflow-HOC.tsx (1)
12-12
: Add type constraints for customButton prop.The
customButton
prop should have stricter type constraints to ensure proper rendering.- customButton?: ReactNode; + customButton?: JSX.Element;web/core/components/stickies/layout/stickies-list.tsx (1)
114-122
: Extract breakpoint values as named constantsThe magic numbers in the column count logic could be more maintainable as named constants.
Consider this refactoring:
+ const BREAKPOINTS = { + SM: 640, + MD: 768, + LG: 1024, + XL: 1280, + } as const; + + const COLUMN_COUNTS = { + DEFAULT: 4, + SM: 2, + MD: 3, + LG: 4, + XL: 5, + XXL: 6, + } as const; + const getColumnCount = (width: number | null): number => { - if (width === null) return 4; + if (width === null) return COLUMN_COUNTS.DEFAULT; - if (width < 640) return 2; // sm - if (width < 768) return 3; // md - if (width < 1024) return 4; // lg - if (width < 1280) return 5; // xl - return 6; // 2xl and above + if (width < BREAKPOINTS.SM) return COLUMN_COUNTS.SM; + if (width < BREAKPOINTS.MD) return COLUMN_COUNTS.MD; + if (width < BREAKPOINTS.LG) return COLUMN_COUNTS.LG; + if (width < BREAKPOINTS.XL) return COLUMN_COUNTS.XL; + return COLUMN_COUNTS.XXL; };web/core/components/stickies/empty.tsx (2)
12-46
: Enhance accessibility for empty stateThe empty state could benefit from improved accessibility.
Consider these improvements:
- <div className="flex justify-center min-h-[500px] rounded border-[1.5px] border-custom-border-100 mx-2 h-full"> + <div + className="flex justify-center min-h-[500px] rounded border-[1.5px] border-custom-border-100 mx-2 h-full" + role="region" + aria-label={query ? "No search results" : "Empty stickies state"} + > <div className="m-auto"> <div className={`mb-2 rounded-full mx-auto last:rounded-full w-[50px] h-[50px] flex items-center justify-center bg-custom-background-80/40 transition-transform duration-300`} + aria-hidden="true" > <StickyIcon className="size-[30px] rotate-90 text-custom-text-350/20" /> </div>
36-44
: Improve loading state accessibilityThe loading spinner needs proper ARIA attributes.
Consider this enhancement:
{creatingSticky && ( - <div className="flex items-center justify-center ml-2"> + <div + className="flex items-center justify-center ml-2" + role="status" + aria-live="polite" + > <div className={`w-4 h-4 border-2 border-t-transparent rounded-full animate-spin border-custom-primary-100`} - role="status" - aria-label="loading" + aria-hidden="true" /> + <span className="sr-only">Creating sticky note...</span> </div> )}web/core/components/stickies/layout/sticky-dnd-wrapper.tsx (3)
113-113
: RemoveisDragging
fromuseEffect
dependency arrayIncluding
isDragging
in the dependency array causes theuseEffect
hook to re-run wheneverisDragging
changes, leading to unnecessary re-executions and potential issues with event registrations. Consider removingisDragging
from the dependency array.Apply this diff to adjust the dependency array:
- }, [stickyId, isDragging]); + }, [stickyId]);
69-69
: Eliminate unnecessary.toString()
onworkspaceSlug
The
workspaceSlug
is already a string as per its type definition, so calling.toString()
is unnecessary.Apply this diff to simplify the code:
- workspaceSlug={workspaceSlug.toString()} + workspaceSlug={workspaceSlug}
124-124
: Simplify thekey
prop usageSince
stickyId
is always a string (as it's a required prop), the fallback"new"
is unnecessary. You can directly usestickyId
as the key.Apply this diff:
- <StickyNote key={stickyId || "new"} workspaceSlug={workspaceSlug} stickyId={stickyId} /> + <StickyNote key={stickyId} workspaceSlug={workspaceSlug} stickyId={stickyId} />web/core/store/sticky/sticky.store.ts (1)
139-143
: Improve loader state managementSetting
this.loader
inside conditional blocks may lead to inconsistent loader states if multiple requests are initiated simultaneously. This can cause UI flickering or incorrect loading indicators.Consider refactoring the loader state management to handle multiple concurrent requests more gracefully, possibly by implementing a request counter or using separate loader states for different requests.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (30)
apiserver/plane/app/views/workspace/sticky.py
(1 hunks)apiserver/plane/db/models/sticky.py
(2 hunks)packages/constants/src/index.ts
(1 hunks)packages/constants/src/stickies.ts
(1 hunks)packages/types/src/stickies.d.ts
(1 hunks)web/app/[workspaceSlug]/(projects)/stickies/header.tsx
(1 hunks)web/app/[workspaceSlug]/(projects)/stickies/layout.tsx
(1 hunks)web/app/[workspaceSlug]/(projects)/stickies/page.tsx
(1 hunks)web/core/components/core/content-overflow-HOC.tsx
(3 hunks)web/core/components/editor/sticky-editor/editor.tsx
(1 hunks)web/core/components/home/widgets/links/link-detail.tsx
(3 hunks)web/core/components/home/widgets/links/use-links.tsx
(0 hunks)web/core/components/stickies/empty.tsx
(1 hunks)web/core/components/stickies/index.ts
(1 hunks)web/core/components/stickies/layout/index.ts
(1 hunks)web/core/components/stickies/layout/stickies-infinite.tsx
(1 hunks)web/core/components/stickies/layout/stickies-list.tsx
(1 hunks)web/core/components/stickies/layout/stickies-truncated.tsx
(1 hunks)web/core/components/stickies/layout/sticky-dnd-wrapper.tsx
(1 hunks)web/core/components/stickies/layout/sticky.helpers.ts
(1 hunks)web/core/components/stickies/modal/search.tsx
(3 hunks)web/core/components/stickies/modal/stickies.tsx
(4 hunks)web/core/components/stickies/stickies-layout.tsx
(0 hunks)web/core/components/stickies/sticky/inputs.tsx
(2 hunks)web/core/components/stickies/sticky/root.tsx
(3 hunks)web/core/components/stickies/sticky/sticky-item-drag-handle.tsx
(1 hunks)web/core/components/stickies/sticky/use-operations.tsx
(3 hunks)web/core/components/stickies/widget.tsx
(3 hunks)web/core/services/sticky.service.ts
(2 hunks)web/core/store/sticky/sticky.store.ts
(4 hunks)
💤 Files with no reviewable changes (2)
- web/core/components/home/widgets/links/use-links.tsx
- web/core/components/stickies/stickies-layout.tsx
✅ Files skipped from review due to trivial changes (3)
- packages/constants/src/stickies.ts
- web/core/components/stickies/index.ts
- web/core/components/stickies/layout/index.ts
🧰 Additional context used
🪛 Biome (1.9.4)
web/core/components/stickies/layout/stickies-truncated.tsx
[error] 27-27: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
(lint/complexity/noUselessFragments)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: lint-apiserver
- GitHub Check: Analyze (javascript)
🔇 Additional comments (9)
web/core/components/editor/sticky-editor/editor.tsx (1)
94-96
: Address TODO comment and type assertion.The TODO comment and type assertion indicate potential technical debt that should be addressed.
Could you provide more details about the toolbar homogenization plan? This would help in properly typing the
executeCommand
function.packages/constants/src/index.ts (1)
13-13
: LGTM! Export follows consistent patternThe addition of the stickies export follows the established pattern in the file.
web/app/[workspaceSlug]/(projects)/stickies/header.tsx (1)
17-20
: Add error handling for workspaceSlugThe workspaceSlug from useParams should be validated before use in stickyOperations.
const workspaceSlug = params.workspaceSlug?.toString(); if (!workspaceSlug) { console.error("Workspace slug is required"); return null; }apiserver/plane/db/models/sticky.py (1)
8-10
: LGTM! Clean import organization.The addition of
strip_tags
from the html processor module is well-placed in the third-party imports section.web/core/components/stickies/layout/sticky.helpers.ts (1)
4-9
: LGTM! Well-structured type definition.The
TargetData
type clearly defines the shape of drag-and-drop target data with appropriate types for each property.web/core/components/stickies/layout/stickies-list.tsx (2)
77-78
: 🛠️ Refactor suggestionFix type mismatch with Masonry component
The
@ts-expect-error
comment suggests an unresolved type issue.Let's check the type definitions:
Consider:
- Updating to a newer version of react-masonry-component with better TypeScript support
- Creating proper type definitions if they don't exist
- Using an alternative library with better TypeScript support
✅ Verification successful
Type mismatch with Masonry is expected and properly handled
The
@ts-expect-error
comment is an appropriate solution here sincereact-masonry-component
doesn't provide TypeScript definitions. While better typing would be ideal, the current approach is a valid workaround for this third-party dependency.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for Masonry type definitions rg -t ts "export.*Masonry" node_modules/react-masonry-componentLength of output: 142
Script:
#!/bin/bash # Find Masonry imports rg -g '*.{ts,tsx}' "import.*Masonry" # Check package.json for masonry-related dependencies cat package.json | grep -i masonry # Look for type definitions fd -e d.ts -e ts -e tsx | xargs rg "Masonry.*interface|interface.*Masonry|type.*Masonry"Length of output: 385
3-5
: Consider using public types instead of internal types from @atlaskitThe use of internal types (
DropTargetRecord
,DragLocationHistory
) from@atlaskit/pragmatic-drag-and-drop/dist/types/internal-types
could be risky as they might change without notice in future updates.Let's check if public alternatives exist:
web/core/store/sticky/sticky.store.ts (1)
225-245
: EnsureresultSequence
remains within valid rangeThe calculation of
resultSequence
may lead to negative values when moving a sticky below another with a lowersort_order
. Negativesort_order
values might cause unexpected behavior in sorting or database constraints.Please verify that
resultSequence
is always a positive number and adjust the calculation to prevent negative values. Consider using a minimum sequence value or adjusting the logic to handle edge cases appropriately.packages/types/src/stickies.d.ts (1)
8-8
: Confirm thatsort_order
is always definedThe
sort_order
property is added as a required field in theTSticky
type. If existing sticky notes or API responses might lack this field (e.g., due to older data), it could lead to runtime errors.Ensure that all sticky notes will have a defined
sort_order
. If not, consider making it optional:- sort_order: number; + sort_order?: number;This adjustment will prevent potential undefined errors when accessing
sort_order
.✅ Verification successful
sort_order
should remain a required fieldThe field is used for core sorting functionality and appears to be managed by the backend. The API accepts partial updates and likely handles default values, making it safe to keep as required in the type definition.
🏁 Scripts executed
The following scripts were executed for the analysis:
Error: No shell scripts found
Script:
#!/bin/bash # Search for files containing TSticky type usage echo "=== Files containing TSticky type ===" rg "TSticky" -l echo -e "\n=== TSticky type definition and context ===" rg "TSticky" -B 3 -A 10 echo -e "\n=== Files containing sort_order in relation to sticky notes ===" rg "sort_order.*sticky" -i echo -e "\n=== Database related files ===" fd "schema|migration" --type fLength of output: 30802
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
web/core/components/stickies/layout/stickies-list.tsx (1)
44-61
:⚠️ Potential issueSimplify handleDrop logic and improve error handling
The nested conditionals make the code harder to follow, and the error handling could be more robust.
The previous review comment's suggestion to refactor this function is still valid and should be implemented.
web/core/components/stickies/layout/sticky-dnd-wrapper.tsx (1)
45-117
:⚠️ Potential issueEnsure proper cleanup in useEffect
The previous review comment's suggestion about adding proper cleanup is still valid and should be implemented.
Add error handling in drag preview rendering
The drag preview rendering lacks error handling which could cause issues if the rendering fails.
render: ({ container }) => { + try { const root = createRoot(container); root.render( // ... existing code ); return () => root.unmount(); + } catch (error) { + console.error("Failed to render drag preview:", error); + return () => {}; + } },
🧹 Nitpick comments (3)
web/core/components/stickies/layout/stickies-list.tsx (1)
73-84
: Add loading state during sticky creationThe empty state handles the creation action but doesn't show a loading indicator while the sticky is being created. This could lead to a poor user experience if the creation takes time.
Consider adding a loading state:
return ( <EmptyState query={searchQuery} creatingSticky={creatingSticky} handleCreate={() => { + if (creatingSticky) return; toggleShowNewSticky(true); stickyOperations.create(); }} + isLoading={creatingSticky} /> );web/core/components/stickies/layout/sticky-dnd-wrapper.tsx (2)
21-44
: Add prop validation for required fieldsWhile the props are well-typed, runtime validation for required props would improve reliability.
Consider adding validation:
+ if (!stickyId || !workspaceSlug) { + console.error("stickyId and workspaceSlug are required"); + return null; + }
86-116
: Improve type safety for instruction systemThe instruction system could benefit from stronger type safety to prevent potential runtime errors.
Consider adding type guards:
+ const isValidInstruction = (instruction: unknown): instruction is InstructionType => { + return instruction === "make-child" || instruction === "reorder-above" || instruction === "reorder-below"; + }; onDrag: ({ self, source, location }) => { const instruction = getInstructionFromPayload(self, source, location); + if (!isValidInstruction(instruction)) return; setInstruction(instruction); },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
web/core/components/stickies/layout/stickies-list.tsx
(1 hunks)web/core/components/stickies/layout/sticky-dnd-wrapper.tsx
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: lint-apiserver
- GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
web/core/components/stickies/layout/stickies-list.tsx (2)
1-24
: LGTM! Well-structured type definitions and imports.The type definitions are clear and the dependencies are appropriate for implementing a masonry layout with drag-and-drop functionality.
111-146
: LGTM! Well-implemented responsive layout.The StickiesLayout component effectively handles:
- Responsive column count based on container width
- Proper cleanup of ResizeObserver
- Clear breakpoint definitions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
web/core/components/home/widgets/empty-states/links.tsx (1)
1-13
: Consider implementing a compound component patternGiven that this component is part of a larger feature change, consider implementing a compound component pattern to maintain flexibility. This would allow different parts of the application to either use this static version or wrap it with interactive features as needed.
Example structure:
const Links = { EmptyState: LinksEmptyState, // Other related components can be added here // Interactive: LinksInteractive, // Container: LinksContainer, }; export default Links;web/core/components/home/widgets/empty-states/recents.tsx (2)
4-4
: Consider using literal type for better type safetyInstead of using a generic string type, consider using a union type of specific values to ensure type safety.
-export const RecentsEmptyState = ({ type }: { type: string }) => { +type RecentsType = 'project' | 'page' | 'issue' | 'all item'; +export const RecentsEmptyState = ({ type }: { type: RecentsType }) => {
5-28
: Extract constants for better maintainabilityConsider extracting the display content configurations into constants to improve maintainability and reusability.
+const DISPLAY_CONTENT_CONFIG = { + project: { + icon: <Briefcase size={30} className="text-custom-text-400/40" />, + text: "Your recent projects will appear here once you visit one.", + }, + page: { + icon: <FileText size={30} className="text-custom-text-400/40" />, + text: "Your recent pages will appear here once you visit one.", + }, + issue: { + icon: <LayersIcon className="text-custom-text-400/40 w-[30px] h-[30px]" />, + text: "Your recent issues will appear here once you visit one.", + }, + default: { + icon: <History size={30} className="text-custom-text-400/40" />, + text: "You don't have any recent items yet.", + }, +}; const getDisplayContent = () => { - switch (type) { - case "project": - return { - icon: <Briefcase size={30} className="text-custom-text-400/40" />, - text: "Your recent projects will appear here once you visit one.", - }; - // ... other cases - default: - return { - icon: <History size={30} className="text-custom-text-400/40" />, - text: "You don't have any recent items yet.", - }; - } + return DISPLAY_CONTENT_CONFIG[type as keyof typeof DISPLAY_CONTENT_CONFIG] + ?? DISPLAY_CONTENT_CONFIG.default; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
web/core/components/home/widgets/empty-states/links.tsx
(1 hunks)web/core/components/home/widgets/empty-states/recents.tsx
(1 hunks)web/core/components/home/widgets/recents/index.tsx
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: lint-apiserver
- GitHub Check: Analyze (javascript)
- GitHub Check: Analyze (python)
🔇 Additional comments (6)
web/core/components/home/widgets/empty-states/links.tsx (3)
7-9
: Well-structured empty state message!The combination of the rotated icon and descriptive text provides clear guidance to users while maintaining visual appeal through consistent styling.
1-1
: Verify the relocation of link creation functionalityThe component has been simplified to a static state by removing props and the link creation handler. Please ensure that this functionality has been properly relocated elsewhere in the codebase.
Also applies to: 3-3
✅ Verification successful
Link creation functionality is properly relocated
The link creation functionality has been correctly moved to a more maintainable architecture:
- UI components handle presentation
- Business logic is managed in the
use-links
hook- Data operations are handled by the link store
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for components that might have inherited the link creation functionality rg -t tsx -t ts "handleCreate.*link" --glob '!*.test.*' --glob '!*.spec.*'Length of output: 104
Script:
#!/bin/bash # Search for link-related handler functions and components rg "handleCreate.*[Ll]ink|create[Ll]ink|onLink" --type-add 'ts:*.{ts,tsx}' -t ts # Find files that might be related to link management fd -e ts -e tsx . | grep -i "link"Length of output: 8906
5-6
: Verify visual consistency with reduced heightThe container height has been reduced from 200px to 110px. Please ensure this change maintains visual consistency with other empty states in the application.
✅ Verification successful
Height change aligns with widget empty states
The reduced height of 110px is consistent with other widget empty states in the application, such as the recents widget (120px). This appears to be a deliberate design pattern for widget-level empty states.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for other empty state components to compare heights rg -t tsx "min-h-\[.*px\].*empty" --glob 'web/**/*empty*.tsx'Length of output: 91
Script:
#!/bin/bash # Search for empty states and height patterns without file type restriction echo "=== Searching for empty states with min-height ===" rg "min-h-\[.*px\].*empty" web/ echo -e "\n=== Searching for empty state components ===" fd "empty" web/ --type f echo -e "\n=== Searching for similar height patterns in components ===" rg "min-h-\[" web/core/components/Length of output: 7603
web/core/components/home/widgets/recents/index.tsx (1)
77-77
: Verify type compatibility between filter and RecentsEmptyState propThe
filter
value being passed astype
prop should be type-checked against the expected type inRecentsEmptyState
. Consider using a shared enum or type definition for both components to ensure type safety.✅ Verification successful
Type compatibility is properly maintained ✅
The implementation is type-safe as RecentsEmptyState accepts a string type and handles all possible filter values, including a default case. The filter state is properly managed through FiltersDropdown component.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for type definitions related to recent activity filters ast-grep --pattern 'type $name = $$$' | rg -A 2 'TRecentActivityFilterKeys|RecentsEmptyState'Length of output: 95
Script:
#!/bin/bash # Find RecentsEmptyState component definition and its props ast-grep --pattern 'interface $props { $$$ }' | rg -B2 -A5 'RecentsEmptyState' # Find the component definition ast-grep --pattern 'const RecentsEmptyState = $$$' # Look for filter-related types/enums ast-grep --pattern 'type $name = $$$|enum $name { $$$ }' | rg -B1 -A5 'filter|Filter|Recent' # Get context of the current file to see filter definition rg -B10 -A2 "RecentsEmptyState.*type={filter}" web/core/components/home/widgets/recents/index.tsxLength of output: 3840
Script:
#!/bin/bash # Fix the regex and get context around filter usage rg -B10 -A2 'RecentsEmptyState.*type=\{filter\}' web/core/components/home/widgets/recents/index.tsx # Look for filter declaration ast-grep --pattern 'const filter = $$$' web/core/components/home/widgets/recents/index.tsxLength of output: 801
Script:
#!/bin/bash # Look for FiltersDropdown component and its props ast-grep --pattern 'interface $props { $$$ }' | rg -B2 -A5 'FiltersDropdown' # Find filters array definition rg -B5 -A2 'const filters =' web/core/components/home/widgets/recents/index.tsxLength of output: 160
web/core/components/home/widgets/empty-states/recents.tsx (2)
32-37
: LGTM! Clean and consistent layout implementationThe flex layout implementation with consistent spacing and styling looks good. The min-height ensures proper empty state visibility.
1-38
: Verify PR scope and documentationThese changes to the recent activity components seem unrelated to the PR's main objective of implementing sticky notes functionality. Consider:
- Moving these changes to a separate PR if they're unrelated
- Updating the PR description to clarify the relationship between these changes and the sticky notes feature
- Adding test scenarios to verify the empty state behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
♻️ Duplicate comments (1)
web/core/components/stickies/modal/search.tsx (1)
76-79
:⚠️ Potential issueAdd error handling for debouncedSearch call.
The search operation could fail, but errors are not handled.
onChange={(e) => { updateSearchQuery(e.target.value); - debouncedSearch(); + debouncedSearch().catch((error) => { + console.error("Search failed:", error); + // Consider showing a user-friendly error message + }); }}
🧹 Nitpick comments (13)
web/core/components/home/widgets/empty-states/recents.tsx (3)
4-4
: Consider using a union type for better type safety.Instead of using a generic string type, consider defining specific valid values to prevent potential runtime errors.
-export const RecentsEmptyState = ({ type }: { type: string }) => { +type RecentsType = 'project' | 'page' | 'issue' | 'default'; +export const RecentsEmptyState = ({ type }: { type: RecentsType }) => {
5-28
: LGTM! Well-structured content generation logic.The switch statement is well-organized with consistent object structure and appropriate default case handling.
Consider standardizing icon styling approach.
While the implementation works, there's a slight inconsistency in how the LayersIcon styling is applied compared to other icons.
- icon: <LayersIcon className="text-custom-text-400/40 w-[30px] h-[30px]" />, + icon: <LayersIcon size={30} className="text-custom-text-400/40" />,
31-37
: Consider adding ARIA labels for better accessibility.The layout and styling look good, but consider adding appropriate ARIA labels to improve accessibility.
- <div className="min-h-[120px] flex w-full justify-center py-6 bg-custom-border-100 rounded"> + <div + className="min-h-[120px] flex w-full justify-center py-6 bg-custom-border-100 rounded" + role="status" + aria-label={`No recent ${type === 'default' ? 'items' : type + 's'}`} + >web/core/components/stickies/sticky/use-operations.tsx (2)
23-38
: Consider a more SSR-friendly approach for HTML content validation.The current implementation uses DOM manipulation which could cause issues in SSR environments. Consider using a regex-based approach or a lightweight HTML parser library.
-const isHtmlContentEmpty = (html: string): boolean => { - // Create a temporary DOM element - const tempElement = document.createElement("div"); - tempElement.innerHTML = html; - - // Get text content and trim whitespace - const textContent = tempElement.textContent || ""; - const trimmedContent = textContent.trim(); - - return trimmedContent.length === 0; -}; +const isHtmlContentEmpty = (html: string): boolean => { + // Remove HTML tags and decode entities + const textContent = html + .replace(/<[^>]*>/g, '') + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); + return textContent.trim().length === 0; +};
40-43
: Consider memoizing color selection for performance.If this function is called frequently, consider memoizing the random color selection or moving it to a constant if the color doesn't need to change during the session.
+const memoizedRandomColor = useMemo(() => { + const randomIndex = Math.floor(Math.random() * STICKY_COLORS.length); + return STICKY_COLORS[randomIndex]; +}, []); // Color stays constant during session + export const getRandomStickyColor = (): string => { - const randomIndex = Math.floor(Math.random() * STICKY_COLORS.length); - return STICKY_COLORS[randomIndex]; + return memoizedRandomColor; };web/core/components/stickies/modal/search.tsx (2)
Line range hint
3-18
: Consider memoizing the fetchStickies function.The
fetchStickies
function is recreated on every render but used in theuseCallback
hook's dependency array. This could lead to unnecessary re-renders.- const fetchStickies = async () => { - await fetchWorkspaceStickies(workspaceSlug.toString()); - }; + const fetchStickies = useCallback(async () => { + await fetchWorkspaceStickies(workspaceSlug.toString()); + }, [fetchWorkspaceStickies, workspaceSlug]);
41-46
: Consider adding a loading state during search.The debounced search operation could take time, but there's no visual feedback to users.
+ const [isSearching, setIsSearching] = useState(false); const debouncedSearch = useCallback( debounce(async () => { + setIsSearching(true); try { await fetchStickies(); + } finally { + setIsSearching(false); + } }, 500), [fetchWorkspaceStickies] );web/core/store/sticky/sticky.store.ts (1)
102-135
: Consider adding retry logic for fetchNextWorkspaceStickies.The method handles errors but doesn't provide any retry mechanism, which could be useful for handling temporary network issues.
+ const MAX_RETRIES = 3; + const RETRY_DELAY = 1000; fetchNextWorkspaceStickies = async (workspaceSlug: string) => { + let retries = 0; try { if (!this.paginationInfo?.next_cursor || !this.paginationInfo.next_page_results || this.loader === "pagination") { return; } this.loader = "pagination"; + while (retries < MAX_RETRIES) { try { const response = await this.stickyService.getStickies( workspaceSlug, this.paginationInfo.next_cursor, this.searchQuery ); // ... rest of the success logic ... + break; + } catch (e) { + retries++; + if (retries === MAX_RETRIES) throw e; + await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); + } + } } catch (e) { console.error(e); runInAction(() => { this.loader = "loaded"; }); } };web/core/components/stickies/layout/stickies-truncated.tsx (1)
10-13
: Add type safety for workspaceSlug parameter.Consider adding type safety by explicitly typing the params object:
- const { workspaceSlug } = useParams(); + const { workspaceSlug } = useParams() as { workspaceSlug: string };web/core/components/home/widgets/empty-states/links.tsx (1)
4-4
: Consider accessibility improvementsThe component could benefit from semantic HTML and accessibility attributes.
Consider this enhancement:
- <div className="min-h-[110px] flex w-full justify-center py-6 bg-custom-border-100 rounded"> + <section aria-label="Quick Links Empty State" className="min-h-[110px] flex w-full justify-center py-6 bg-custom-border-100 rounded">web/core/components/stickies/layout/sticky-dnd-wrapper.tsx (1)
24-42
: Add prop validation for required propsThe component accepts several required props but lacks validation. Consider adding runtime checks to provide helpful error messages during development.
export const StickyDNDWrapper = observer( ({ stickyId, workspaceSlug, itemWidth, isLastChild, isInFirstRow, isInLastRow, handleDrop, }: { stickyId: string; workspaceSlug: string; itemWidth: string; isLastChild: boolean; isInFirstRow: boolean; isInLastRow: boolean; handleDrop: (self: DropTargetRecord, source: ElementDragPayload, location: DragLocationHistory) => void; }) => { + if (!stickyId || !workspaceSlug) { + console.error('StickyDNDWrapper: stickyId and workspaceSlug are required'); + return null; + }web/core/components/stickies/layout/stickies-list.tsx (2)
36-42
: Memoize row position calculations for better performanceThe
getRowPositions
function is recreated on every render and performs calculations that could be memoized.+ const getRowPositions = useMemo(() => (index: number) => { const currentRow = Math.floor(index / columnCount); return { isInFirstRow: currentRow === 0, isInLastRow: currentRow === totalRows - 1 || index >= workspaceStickies.length - columnCount, }; - }; + }, [columnCount, totalRows, workspaceStickies.length]);
130-138
: Extract breakpoints and column counts to configurationThe breakpoint values and corresponding column counts should be extracted to configuration constants for better maintainability.
+const BREAKPOINTS = { + SM: 640, + MD: 768, + LG: 1024, + XL: 1280, +} as const; + +const COLUMN_COUNTS = { + DEFAULT: 4, + SM: 2, + MD: 3, + LG: 4, + XL: 5, + XXL: 6, +} as const; + const getColumnCount = (width: number | null): number => { - if (width === null) return 4; + if (width === null) return COLUMN_COUNTS.DEFAULT; - if (width < 640) return 2; // sm - if (width < 768) return 3; // md - if (width < 1024) return 4; // lg - if (width < 1280) return 5; // xl - return 6; // 2xl and above + if (width < BREAKPOINTS.SM) return COLUMN_COUNTS.SM; + if (width < BREAKPOINTS.MD) return COLUMN_COUNTS.MD; + if (width < BREAKPOINTS.LG) return COLUMN_COUNTS.LG; + if (width < BREAKPOINTS.XL) return COLUMN_COUNTS.XL; + return COLUMN_COUNTS.XXL; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
web/app/[workspaceSlug]/(projects)/stickies/header.tsx
(1 hunks)web/core/components/home/widgets/empty-states/links.tsx
(1 hunks)web/core/components/home/widgets/empty-states/recents.tsx
(1 hunks)web/core/components/home/widgets/links/links.tsx
(1 hunks)web/core/components/stickies/layout/stickies-list.tsx
(1 hunks)web/core/components/stickies/layout/stickies-truncated.tsx
(1 hunks)web/core/components/stickies/layout/sticky-dnd-wrapper.tsx
(1 hunks)web/core/components/stickies/modal/search.tsx
(4 hunks)web/core/components/stickies/sticky/root.tsx
(3 hunks)web/core/components/stickies/sticky/use-operations.tsx
(3 hunks)web/core/components/stickies/widget.tsx
(3 hunks)web/core/store/sticky/sticky.store.ts
(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- web/core/components/stickies/sticky/root.tsx
- web/core/components/stickies/widget.tsx
- web/app/[workspaceSlug]/(projects)/stickies/header.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (14)
web/core/components/home/widgets/empty-states/recents.tsx (2)
1-2
: LGTM! Clean import organization.The imports are well-organized and correctly separated by source.
29-29
: LGTM! Clean destructuring.The destructuring is clear and effective.
web/core/components/stickies/sticky/use-operations.tsx (2)
2-2
: LGTM! Well-structured type definitions and imports.The type definitions are clear and type-safe, particularly the new
updatePosition
method signature in theTOperations
type.Also applies to: 4-4, 8-16
131-148
: Enhance error handling in updatePosition.This is a duplicate of a previous review comment. The error handling needs improvement to be more specific and provide better error messages.
updatePosition: async ( workspaceSlug: string, sourceId: string, droppedId: string, instruction: InstructionType ) => { try { if (!workspaceSlug) throw new Error("Missing required fields"); + if (!sourceId || !droppedId) throw new Error("Missing sticky IDs"); await updateStickyPosition(workspaceSlug, sourceId, droppedId, instruction); } catch (error) { setToast({ - message: "The sticky could not be updated", + message: error instanceof Error ? error.message : "Failed to update sticky position", type: TOAST_TYPE.ERROR, title: "Sticky not updated", }); throw error; } },web/core/store/sticky/sticky.store.ts (1)
218-265
: LGTM! Good error handling with state rollback.The
updateStickyPosition
method properly handles errors by rolling back to the previous sort order state when the update fails.web/core/components/stickies/layout/stickies-truncated.tsx (4)
1-9
: LGTM! Imports are well-organized.All necessary dependencies are properly imported and organized.
37-37
: Add null check for workspaceSlug prop.The workspaceSlug prop passed to StickiesLayout could be undefined.
- <StickiesLayout workspaceSlug={workspaceSlug?.toString()} /> + <StickiesLayout workspaceSlug={workspaceSlug?.toString() ?? ""} />
15-19
:⚠️ Potential issueAdd null check before toString() call.
The toString() call could throw if workspaceSlug is undefined.
- workspaceSlug ? () => fetchWorkspaceStickies(workspaceSlug.toString()) : null, + workspaceSlug ? () => fetchWorkspaceStickies(workspaceSlug?.toString() ?? "") : null,Likely invalid or redundant comment.
18-18
: Verify the data revalidation strategy.The current setup disables both stale data revalidation and focus revalidation. Please verify if this aligns with the application's data freshness requirements.
✅ Verification successful
SWR configuration is appropriate for this component
The current configuration aligns with the established patterns across the codebase, particularly for similar components handling relatively static data. This approach is consistently used in related components like
stickies-infinite.tsx
and various issue layouts.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for other SWR configurations in the codebase to ensure consistency rg -A 2 'revalidateIfStale|revalidateOnFocus' --type ts --type tsxLength of output: 96
Script:
#!/bin/bash # Search for SWR configurations with correct file type handling rg -t typescript -t tsx 'revalidateIfStale|revalidateOnFocus' -A 2 # Search for SWR configuration files or setup fd -e ts -e tsx -E '*.d.ts' -E '*.test.*' | rg -l 'swr|SWR' # Search for any comments or documentation about data freshness rg -t typescript -t tsx -B 2 'revalidateIfStale|revalidateOnFocus' --context 3Length of output: 289
Script:
#!/bin/bash # Search for SWR configurations using file extensions rg 'revalidateIfStale|revalidateOnFocus' --glob '*.{ts,tsx}' # Look for SWR setup files and configurations rg 'SWRConfig|SWRConfiguration' --glob '*.{ts,tsx}' # Search for useSWR hook usage patterns rg 'useSWR.*{' --glob '*.{ts,tsx}' -A 2Length of output: 6549
web/core/components/home/widgets/links/links.tsx (2)
30-30
: Consider UX implications of removing link creation from empty stateThe empty state no longer provides a direct way to create links. This might confuse users who see the message "Add any links you need for quick access" but have no obvious way to do so.
Consider:
- Adding a clear path for users to create links
- Updating the empty state message if link creation is intentionally disabled
- Documenting the new intended workflow for link management
23-24
: Verify the removal of link creation functionalityThe removal of
toggleLinkModal
suggests that link creation capability has been intentionally removed. This architectural change might impact user experience if there's no alternative way to create links.Let's verify if there's an alternative implementation for link creation:
web/core/components/stickies/layout/sticky-dnd-wrapper.tsx (2)
48-120
: Ensure proper cleanup in useEffect to prevent memory leaks
48-120
:⚠️ Potential issueAdd missing dependencies to useEffect
The useEffect hook is missing several dependencies that could lead to stale closures:
- pathName
- handleDrop
- isLastChild
- workspaceSlug
- }, [stickyId, isDragging]); + }, [stickyId, isDragging, pathName, handleDrop, isLastChild, workspaceSlug]);Likely invalid or redundant comment.
web/core/components/stickies/layout/stickies-list.tsx (1)
1-24
: LGTM! Clean and well-organized imports and type definitions.
… into feat-stickies-page
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
web/core/components/stickies/sticky/use-operations.tsx (2)
64-85
:⚠️ Potential issueFix the empty sticky check logic.
The empty sticky check has several issues that need to be addressed.
The condition
getWorkspaceStickies(workspaceSlug).length >= 0
is always true, and there's a redundant call togetWorkspaceStickies
. This should be fixed as suggested in the previous review.
151-151
:⚠️ Potential issueAdd missing dependencies to useMemo.
The dependency array is missing several dependencies that are used within the memoized operations.
This issue was previously identified and should be fixed as suggested in the earlier review.
🧹 Nitpick comments (2)
web/core/components/stickies/sticky/use-operations.tsx (2)
23-38
: Consider optimizing HTML content check for SSR compatibility.The current implementation uses DOM manipulation which could cause issues in SSR environments. Consider using a regex-based approach or a lightweight HTML parser library.
-const isHtmlContentEmpty = (html: string): boolean => { - // Create a temporary DOM element - const tempElement = document.createElement("div"); - tempElement.innerHTML = html; - - // Get text content and trim whitespace - const textContent = tempElement.textContent || ""; - const trimmedContent = textContent.trim(); - - return trimmedContent.length === 0; -}; +const isHtmlContentEmpty = (html: string): boolean => { + // Remove HTML tags and decode entities + const textContent = html + .replace(/<[^>]*>/g, '') + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); + + return textContent.trim().length === 0; +};
40-43
: Consider memoizing the random color selection.The
getRandomStickyColor
function is simple and effective. However, if it's called frequently (e.g., during rapid sticky creation), consider memoizing the result for a short duration.+const colorCache = { + color: '', + timestamp: 0, + TTL: 1000, // 1 second +}; + export const getRandomStickyColor = (): string => { + const now = Date.now(); + if (now - colorCache.timestamp < colorCache.TTL) { + return colorCache.color; + } + const randomIndex = Math.floor(Math.random() * STICKY_COLORS.length); - return STICKY_COLORS[randomIndex]; + colorCache.color = STICKY_COLORS[randomIndex]; + colorCache.timestamp = now; + return colorCache.color; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
web/core/components/stickies/sticky/use-operations.tsx
(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
web/core/components/stickies/sticky/use-operations.tsx (2)
2-2
: LGTM! Type definitions are well-structured.The type modifications and new imports are appropriate for the added functionality. The
updatePosition
method signature is well-defined with clear parameter types.Also applies to: 4-4, 8-16
132-149
: Enhance error handling in updatePosition.The error handling in the
updatePosition
method could be more specific.Consider implementing the previously suggested improvements to provide better error messages and validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🔭 Outside diff range comments (1)
web/core/components/home/widgets/empty-states/root.tsx (1)
Line range hint
33-40
: Add aria-label to the create project button for better accessibility.The button's onClick handler should also have proper error handling and feedback for users without permissions.
cta: { text: "Create Project", + ariaLabel: "Create a new project", onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { if (!canCreateProject) { + e.preventDefault(); + e.stopPropagation(); + // Show a tooltip or notification explaining why the action is not allowed + return; } e.preventDefault(); e.stopPropagation(); setTrackElement("Sidebar"); toggleCreateProjectModal(true); }, },
🧹 Nitpick comments (13)
web/core/components/stickies/action-bar.tsx (2)
33-36
: Add null checks and improve color fallback logic.The derived value helps centralize color management, but consider these improvements:
- Add null check for
stickies[recentStickyId]
to prevent runtime errors- Consider a more explicit fallback color instead of relying on the first color in the list
const recentStickyBackgroundColor = recentStickyId - ? STICKY_COLORS_LIST.find((c) => c.key === stickies[recentStickyId].background_color)?.backgroundColor - : STICKY_COLORS_LIST[0].backgroundColor; + ? STICKY_COLORS_LIST.find((c) => c.key === stickies[recentStickyId]?.background_color)?.backgroundColor + ?? '#E2E8F0' // explicit fallback color + : '#E2E8F0';
Line range hint
78-92
: Consider accessibility improvements for tooltips.While the tooltip implementation is good, consider these accessibility enhancements:
- Add
aria-label
to buttons- Consider adding keyboard navigation support for the tooltip content
<button className="btn btn--icon rounded-full w-10 h-10 flex items-center justify-center shadow-sm bg-custom-background-100" onClick={() => setShowRecentSticky(true)} + aria-label="Show recent sticky note" style={{ color: recentStickyBackgroundColor }} >
web/core/components/stickies/sticky/inputs.tsx (2)
38-45
: Add error handling to form submission.The form submission callback should include error handling to gracefully handle potential failures.
Consider updating the implementation:
const handleFormSubmit = useCallback( async (formdata: Partial<TSticky>) => { + try { await handleUpdate({ description_html: formdata.description_html ?? "<p></p>", }); + } catch (error) { + console.error("Failed to update sticky:", error); + // Consider adding user notification here + } }, [handleUpdate] );
71-74
: Consider improvements to editor configuration.A few suggestions to enhance the editor configuration:
- The placeholder text could be more descriptive for better user guidance
- Consider making the min-height configurable through props
Consider these improvements:
-placeholder="Click to type here" +placeholder="Enter your note content here..." -containerClassName="px-0 text-base min-h-[250px] w-full" +containerClassName={`px-0 text-base min-h-[${minHeight ?? '250px'}] w-full`}Add the minHeight prop to TProps:
type TProps = { // ... existing props minHeight?: string; };web/core/components/home/widgets/manage/widget-item.tsx (2)
49-49
: Add null check for widget title lookup.Consider adding a fallback for cases where the widget key might not exist in HOME_WIDGETS_LIST.
- const widgetTitle = HOME_WIDGETS_LIST[widget.key]?.title; + const widgetTitle = HOME_WIDGETS_LIST[widget.key]?.title ?? widget.key.replaceAll("_", " ");
Line range hint
52-119
: Consider adding error boundaries for drag-and-drop operations.The drag-and-drop implementation is robust, but adding error boundaries would help gracefully handle any potential failures during drag operations and prevent the entire widget management interface from breaking.
Would you like me to provide an example implementation of an error boundary component for the drag-and-drop operations?
web/core/components/home/widgets/empty-states/stickies.tsx (1)
4-13
: LGTM! Consider adding aria-label for better accessibility.The empty state implementation is clean and follows React best practices. The styling is consistent with Tailwind conventions.
Consider adding an
aria-label
to improve accessibility:- <div className="min-h-[110px] flex w-full justify-center py-6 bg-custom-border-100 rounded"> + <div className="min-h-[110px] flex w-full justify-center py-6 bg-custom-border-100 rounded" aria-label="No stickies available">web/core/components/editor/sticky-editor/color-palette.tsx (2)
50-52
: Consider adding validation for color keys.The type definition looks good, but consider adding validation to ensure
background_color
matches the available color keys.type TColorKey = typeof STICKY_COLORS_LIST[number]['key']; type TProps = { handleUpdate: (data: Partial<Omit<TSticky, 'background_color'> & { background_color: TColorKey }>) => Promise<void>; };
54-78
: Consider keyboard navigation improvements.The color palette implementation is good, but could benefit from keyboard navigation enhancements.
Consider these improvements:
- Add
aria-label
to buttons with color names- Implement arrow key navigation between colors
- Add
role="toolbar"
to the color palette container- <div className="flex flex-wrap gap-2"> + <div className="flex flex-wrap gap-2" role="toolbar"> {STICKY_COLORS_LIST.map((color) => ( <button key={color.key} type="button" + aria-label={`${color.label} background color`} onClick={() => {web/core/components/home/home-dashboard-widgets.tsx (1)
68-93
: Consider extracting widget rendering logic to a separate component.The conditional rendering logic for widgets could be extracted to improve readability and maintainability.
+const WidgetRenderer: React.FC<{ + widgetKey: THomeWidgetKeys; + widgetComponent: React.FC<THomeWidgetProps> | null; + isEnabled: boolean; + workspaceSlug: string; +}> = ({ widgetKey, widgetComponent: WidgetComponent, isEnabled, workspaceSlug }) => { + if (!WidgetComponent || !isEnabled) return null; + return ( + <div key={widgetKey} className="py-4"> + <WidgetComponent workspaceSlug={workspaceSlug.toString()} /> + </div> + ); +}; {isAnyWidgetEnabled ? ( <div className="flex flex-col divide-y-[1px] divide-custom-border-100"> {orderedWidgets.map((key) => { - const WidgetComponent = HOME_WIDGETS_LIST[key]?.component; - const isEnabled = widgetsMap[key]?.is_enabled; - if (!WidgetComponent || !isEnabled) return null; - return ( - <div key={key} className="py-4"> - <WidgetComponent workspaceSlug={workspaceSlug.toString()} /> - </div> - ); + return ( + <WidgetRenderer + key={key} + widgetKey={key} + widgetComponent={HOME_WIDGETS_LIST[key]?.component} + isEnabled={widgetsMap[key]?.is_enabled} + workspaceSlug={workspaceSlug.toString()} + /> + ); })} </div> ) : ( // ... empty state remains the same )}web/core/components/home/widgets/empty-states/root.tsx (1)
Line range hint
20-24
: Consider memoizing the canCreateProject value.Since this value depends on permissions that may not change frequently, memoizing it could prevent unnecessary recalculations.
-const canCreateProject = allowPermissions( +const canCreateProject = React.useMemo(() => allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.WORKSPACE -); +), [allowPermissions]);web/core/components/empty-state/empty-state.tsx (1)
78-79
: Consider destructuring primaryButtonConfig for better readability.The repeated access to primaryButtonConfig properties could be simplified.
+const { size, variant } = primaryButtonConfig; const commonProps = { - size: primaryButtonConfig.size, - variant: primaryButtonConfig.variant, + size, + variant, prependIcon: primaryButton.icon, onClick: primaryButtonOnClick ? primaryButtonOnClick : undefined, disabled: !isEditingAllowed, };web/core/constants/empty-state.tsx (1)
946-957
: Consider adding a secondary action for widget management.The HOME_WIDGETS empty state could benefit from a secondary action to help users understand what widgets are available.
[EmptyStateType.HOME_WIDGETS]: { key: EmptyStateType.HOME_WIDGETS, title: "It's Quiet Without Widgets, Turn Them On", description: "It looks like all your widgets are turned off. Enable them\nnow to enhance your experience!", path: "/empty-state/dashboard/widgets", primaryButton: { icon: <Shapes className="size-4" />, text: "Manage widgets", }, + secondaryButton: { + text: "Learn about widgets", + comicBox: { + title: "Widgets enhance your dashboard", + description: "Widgets show you quick links, recent activity, and your stickies at a glance.", + }, + }, access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], accessType: "workspace", },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (29)
packages/constants/src/index.ts
(1 hunks)packages/editor/src/styles/editor.css
(1 hunks)packages/editor/src/styles/variables.css
(0 hunks)packages/types/src/stickies.d.ts
(1 hunks)web/app/[workspaceSlug]/(projects)/stickies/page.tsx
(1 hunks)web/core/components/editor/sticky-editor/color-palette.tsx
(1 hunks)web/core/components/editor/sticky-editor/color-pallete.tsx
(0 hunks)web/core/components/editor/sticky-editor/toolbar.tsx
(1 hunks)web/core/components/empty-state/empty-state.tsx
(4 hunks)web/core/components/home/home-dashboard-widgets.tsx
(1 hunks)web/core/components/home/root.tsx
(1 hunks)web/core/components/home/user-greetings.tsx
(2 hunks)web/core/components/home/widgets/empty-states/root.tsx
(2 hunks)web/core/components/home/widgets/empty-states/stickies.tsx
(1 hunks)web/core/components/home/widgets/manage/widget-item.tsx
(3 hunks)web/core/components/stickies/action-bar.tsx
(3 hunks)web/core/components/stickies/empty.tsx
(0 hunks)web/core/components/stickies/layout/stickies-infinite.tsx
(1 hunks)web/core/components/stickies/layout/stickies-list.tsx
(1 hunks)web/core/components/stickies/layout/stickies-truncated.tsx
(1 hunks)web/core/components/stickies/modal/stickies.tsx
(3 hunks)web/core/components/stickies/sticky/inputs.tsx
(2 hunks)web/core/components/stickies/sticky/root.tsx
(3 hunks)web/core/components/stickies/sticky/use-operations.tsx
(4 hunks)web/core/components/stickies/widget.tsx
(1 hunks)web/core/constants/empty-state.tsx
(3 hunks)web/core/store/sticky/sticky.store.ts
(5 hunks)web/core/store/workspace/home.ts
(3 hunks)web/styles/globals.css
(1 hunks)
💤 Files with no reviewable changes (3)
- web/core/components/editor/sticky-editor/color-pallete.tsx
- web/core/components/stickies/empty.tsx
- packages/editor/src/styles/variables.css
✅ Files skipped from review due to trivial changes (1)
- web/core/components/editor/sticky-editor/toolbar.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/constants/src/index.ts
- web/core/components/stickies/widget.tsx
- web/core/components/stickies/modal/stickies.tsx
- web/core/components/stickies/sticky/root.tsx
- web/core/components/stickies/layout/stickies-truncated.tsx
- web/app/[workspaceSlug]/(projects)/stickies/page.tsx
- web/core/components/stickies/layout/stickies-infinite.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
web/core/components/empty-state/empty-state.tsx
[error] 187-192: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
🔇 Additional comments (23)
web/core/store/workspace/home.ts (2)
14-14
: LGTM! Well-typed interface addition.The new computed property is properly typed and well-placed in the interface definition.
57-59
: LGTM! Clean and efficient implementation.The computed property implementation:
- Uses efficient methods (Object.values + Array.some)
- Follows MobX best practices with no side effects
- Will properly cache results and only recompute when widgetsMap changes
web/core/components/stickies/action-bar.tsx (2)
6-11
: Well-organized imports with clear sectioning!The imports are properly organized with descriptive comments, making the code more maintainable.
21-32
: Clean state management organization!Good separation of state variables, navigation, and store hooks with clear comments improves code readability.
web/core/components/stickies/sticky/inputs.tsx (1)
4-21
: LGTM! Clean type definitions and well-organized imports.The addition of the optional
showToolbar
prop and the organization of imports with clear grouping comments enhance code readability and maintainability.Also applies to: 24-24
web/core/components/home/widgets/manage/widget-item.tsx (2)
17-25
: Well-organized import categorization!The clear categorization of imports improves code organization and maintainability.
131-131
: Clean widget title rendering!The use of the
widgetTitle
variable improves code maintainability and centralizes title management.packages/editor/src/styles/editor.css (1)
148-148
: Verify checkbox visibility across themes.The change to transparent background might affect checkbox visibility, especially in dark mode where contrast could be an issue.
Run the following script to check for any related accessibility or contrast issues in the codebase:
✅ Verification successful
Checkbox visibility is properly maintained
The transparent background is safe as the checkbox remains visible through:
- Themed border colors using CSS custom properties
- Hover and checked states with appropriate background colors
- Consistent styling across themes through CSS variables
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for any related accessibility or contrast issues with checkboxes # Search for any existing accessibility-related comments or issues rg -i "checkbox.*visibility|checkbox.*contrast|checkbox.*accessibility" --type css --type ts --type tsx # Search for any theme-specific checkbox styles rg "checkbox.*dark|checkbox.*light" --type cssLength of output: 182
Script:
#!/bin/bash # Search for checkbox-related styles rg -A 3 "input\[type=.?checkbox" --type css # Search for theme-related styles and CSS custom properties rg "(:root|\.theme--(dark|light))" -A 5 --type css # Search for accessibility-related code in all files rg -i "(aria-|role=)" . | rg -i "checkbox"Length of output: 3962
web/styles/globals.css (3)
384-406
: LGTM! Well-organized color variables.The color variables are well-structured with clear separation between text and background colors. The naming convention is consistent and semantic.
382-428
: Excellent color system implementation!The color system is well-implemented with:
- Clear separation of concerns between text and background colors
- Consistent naming conventions
- Proper theme support including contrast modes
- Semantic organization of color variables
408-427
: Verify color contrast ratios for accessibility.The dark theme colors look good, but it's important to verify that they meet WCAG accessibility standards for contrast ratios.
Run the following script to extract and analyze the color combinations:
✅ Verification successful
Color contrast ratios meet accessibility standards
The editor color combinations in both light and dark themes meet WCAG AA standards for large text (3:1 minimum contrast ratio). The dark theme's contrast ratios range from 3.5:1 to 4.8:1, while the light theme shows even better contrast due to lighter backgrounds.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Extract and analyze color combinations for contrast verification # Search for any existing color contrast tests rg -i "contrast.*test|wcag|a11y" --type test # Search for any color utility functions that might handle contrast rg "contrast|color.*util" --type ts --type tsxLength of output: 152
Script:
#!/bin/bash # Search for color-related CSS variables and their usage rg "color|--editor" web/styles/globals.css # Search for any test files or utilities related to colors/contrast rg -l "contrast|wcag|a11y|color.*test" # Find usage of the background color variables rg "var\(--editor-colors-.*-background\)"Length of output: 20753
web/core/components/home/user-greetings.tsx (1)
56-59
: LGTM! Good migration to the UI component library.The replacement of the HTML button with the
Button
component from@plane/ui
improves consistency across the application while maintaining the same functionality.web/core/components/editor/sticky-editor/color-palette.tsx (1)
3-48
: LGTM! Well-structured color constants.The color constants are well-organized with clear keys, labels, and use of CSS variables for theme support.
web/core/components/home/root.tsx (1)
62-64
: LGTM! Minor styling adjustment.The gap reduction from 7 to 6 units maintains visual hierarchy while likely improving alignment with other components.
web/core/components/home/home-dashboard-widgets.tsx (1)
18-49
: LGTM! Well-structured widget configuration.The HOME_WIDGETS_LIST is well-typed and provides a clear structure for widget configuration with title, component, and fullWidth properties.
web/core/constants/empty-state.tsx (1)
921-933
: LGTM! Well-defined empty state for stickies.The empty state configuration for stickies is comprehensive and includes all necessary properties.
packages/types/src/stickies.d.ts (1)
4-15
: LGTM! Type definitions align with API response structure.The changes to the
TSticky
type definition look good:
- Added metadata fields (created_at, updated_at, etc.)
- Added sort_order for position management
- Added logo_props for UI customization
- Proper typing of optional fields
web/core/components/stickies/sticky/use-operations.tsx (2)
141-141
:⚠️ Potential issueAdd missing dependencies to useMemo.
The dependency array is missing several dependencies that are used within the memoized operations.
Add the missing dependencies:
-[createSticky, deleteSticky, getWorkspaceStickyIds, stickies, updateSticky, updateStickyPosition, workspaceSlug] +[ + createSticky, + deleteSticky, + getWorkspaceStickyIds, + stickies, + updateSticky, + updateStickyPosition, + workspaceSlug, + isValid +]Likely invalid or redundant comment.
60-72
:⚠️ Potential issueFix empty sticky check logic.
The condition
workspaceStickIds && workspaceStickIds.length >= 0
is always true. This could lead to unexpected behavior.Apply this diff to fix the logic:
-if (workspaceStickIds && workspaceStickIds.length >= 0) { +if (workspaceStickIds && workspaceStickIds.length > 0) {Likely invalid or redundant comment.
web/core/components/stickies/layout/stickies-list.tsx (2)
91-94
: 🛠️ Refactor suggestionAdd error handling for sticky creation.
The sticky creation operation should be wrapped in try-catch to handle potential failures gracefully.
-primaryButtonOnClick={() => { - toggleShowNewSticky(true); - stickyOperations.create(); -}} +primaryButtonOnClick={async () => { + try { + toggleShowNewSticky(true); + await stickyOperations.create(); + } catch (error) { + console.error('Failed to create sticky:', error); + toggleShowNewSticky(false); + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error", + message: "Failed to create sticky" + }); + } +}}Likely invalid or redundant comment.
55-72
: 🛠️ Refactor suggestionSimplify handleDrop logic and improve error handling.
The nested conditionals make the code harder to follow. Consider this refactoring:
-const handleDrop = (self: DropTargetRecord, source: ElementDragPayload, location: DragLocationHistory) => { - const dropTargets = location?.current?.dropTargets ?? []; - if (!dropTargets || dropTargets.length <= 0) return; - - const dropTarget = dropTargets[0]; - if (!dropTarget?.data?.id || !source.data?.id) return; - - const instruction = getInstructionFromPayload(dropTarget, source, location); - const droppedId = dropTarget.data.id; - const sourceId = source.data.id; - - try { - if (!instruction || !droppedId || !sourceId) return; - stickyOperations.updatePosition(workspaceSlug, sourceId as string, droppedId as string, instruction); - } catch (error) { - console.error("Error reordering sticky:", error); - } +const handleDrop = async (self: DropTargetRecord, source: ElementDragPayload, location: DragLocationHistory) => { + try { + const dropTarget = location?.current?.dropTargets?.[0]; + const sourceId = source.data?.id; + const droppedId = dropTarget?.data?.id; + + if (!dropTarget || !sourceId || !droppedId) return; + + const instruction = getInstructionFromPayload(dropTarget, source, location); + if (!instruction) return; + + await stickyOperations.updatePosition( + workspaceSlug, + sourceId, + droppedId, + instruction + ); + } catch (error) { + console.error("Error reordering sticky:", error); + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error", + message: "Failed to reorder sticky" + }); + } };Likely invalid or redundant comment.
web/core/store/sticky/sticky.store.ts (2)
223-270
: LGTM! Good error handling with state rollback.The
updateStickyPosition
method has proper error handling:
- Saves previous state before update
- Rolls back to previous state on error
- Uses runInAction for state updates
78-84
: 🛠️ Refactor suggestionAdd search filtering to getWorkspaceStickyIds.
The computed function only sorts by
sort_order
but doesn't filter based onsearchQuery
.getWorkspaceStickyIds = computedFn((workspaceSlug: string) => orderBy( - (this.workspaceStickies[workspaceSlug] || []).map((stickyId) => this.stickies[stickyId]), + (this.workspaceStickies[workspaceSlug] || []) + .map((stickyId) => this.stickies[stickyId]) + .filter((sticky) => + !this.searchQuery || + sticky.name?.toLowerCase().includes(this.searchQuery.toLowerCase()) || + sticky.description_html?.toLowerCase().includes(this.searchQuery.toLowerCase()) + ), ["sort_order"], ["desc"] ).map((sticky) => sticky.id) );Likely invalid or redundant comment.
Pull Request Linked with Plane Issues Comment Automatically Generated by Plane |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
web/app/[workspaceSlug]/(projects)/stickies/page.tsx (2)
7-16
: Add loading state and error handling.While the implementation is clean, consider these improvements for better user experience and reliability:
- Add a loading state while StickiesInfinite is initializing
- Wrap StickiesInfinite with an error boundary
export default function WorkspaceStickiesPage() { return ( <> <PageHead title="Your stickies" /> - <div className="relative h-full w-full overflow-hidden overflow-y-auto"> - <StickiesInfinite /> + <div className="relative h-full w-full overflow-hidden overflow-y-auto" role="main"> + <ErrorBoundary fallback={<StickyErrorState />}> + <Suspense fallback={<StickiesLoader />}> + <StickiesInfinite /> + </Suspense> + </ErrorBoundary> </div> </> ); }
11-11
: Add ARIA landmark for better accessibility.The main content div should have an ARIA landmark role for better screen reader navigation.
-<div className="relative h-full w-full overflow-hidden overflow-y-auto"> +<div className="relative h-full w-full overflow-hidden overflow-y-auto" role="main">
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
web/app/[workspaceSlug]/(projects)/stickies/header.tsx
(1 hunks)web/app/[workspaceSlug]/(projects)/stickies/page.tsx
(1 hunks)web/core/components/empty-state/empty-state.tsx
(5 hunks)web/core/components/stickies/sticky/use-operations.tsx
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- web/app/[workspaceSlug]/(projects)/stickies/header.tsx
- web/core/components/empty-state/empty-state.tsx
- web/core/components/stickies/sticky/use-operations.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
web/app/[workspaceSlug]/(projects)/stickies/page.tsx (2)
1-6
: LGTM! Clean imports and proper client directive.The imports are well-organized and the "use client" directive is correctly placed.
7-16
: Validate workspaceSlug parameter.The previous review comment about validating workspaceSlug is still applicable.
Description
This PR includes the following changes-
Features
Improvements
Refactor
Type of Change
Summary by CodeRabbit
Release Notes
New Features
Improvements
Bug Fixes
Chores