-
Notifications
You must be signed in to change notification settings - Fork 2
Content Feed New Post #209
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
Conversation
|
WalkthroughThis PR introduces several new features and modifications. A new GraphQL mutation for content post creation is implemented along with its custom hook. New React components and styled components are added for displaying the content feed, creating new posts, and handling image uploads. The GraphQL schema and TypeScript types are extended to support these features including pagination and deletion operations. Additionally, the package exports are updated, ESLint configuration is adjusted, and the design system’s Dropzone component is enhanced with improved file handling and styling. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant N as NewContentPost Component
participant MH as Mutation Hook (useContentPostCreateMutation)
participant G as GraphQL Server
U->>N: Fill and submit new post form
N->>MH: Trigger mutation with form data
MH->>G: Send contentPostCreate mutation
G-->>MH: Return mutation response (data/errors)
MH-->>N: Pass response to component
alt Mutation Error
N->>U: Display error notification
else Mutation Success
N->>U: Redirect to created post page & show success notification
end
sequenceDiagram
participant U as User
participant DZ as Dropzone Component
participant DP as DropzonePreview Component
participant FH as File Handler
U->>DZ: Select/drag files
DZ->>FH: Validate and process files
FH-->>DZ: Return processed file data
DZ->>DP: Render preview with file data
U->>DP: Click preview to remove file
DP->>DZ: Trigger file removal update
Suggested labels
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
packages/components/modules/content-feed/web/ContentFeed/index.tsxOops! Something went wrong! :( ESLint: 8.57.1 Error: Cannot read config file: /packages/components/.eslintrc.js
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.tsOops! Something went wrong! :( ESLint: 8.57.1 Error: Cannot read config file: /packages/components/.eslintrc.js
packages/components/modules/content-feed/web/ContentFeed/styled.tsxOops! Something went wrong! :( ESLint: 8.57.1 Error: Cannot read config file: /packages/components/.eslintrc.js
✨ Finishing Touches
🪧 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: 7
🧹 Nitpick comments (8)
packages/components/modules/content-feed/NewContentPost/types.ts (1)
1-4: Consider separating image types for better type safety.The
imagesfield allows mixed types (string[], File[], Blob[]) which could lead to type-checking issues. Consider using a discriminated union or separate fields for different image types.export interface ContentPostCreateForm { content: string - images?: string[] | File[] | Blob[] + images?: { + urls?: string[] + files?: (File | Blob)[] + } }packages/components/modules/content-feed/NewContentPost/constants.ts (1)
1-1: Consider using path alias for imports.The import path is verbose. Consider using a path alias to simplify imports.
-import { ContentPostCreateForm } from '@baseapp-frontend/components/modules/content-feed/NewContentPost/types' +import { ContentPostCreateForm } from '@/types'packages/components/modules/content-feed/ContentFeed/index.tsx (2)
10-35: Add TypeScript interface and improve accessibility.The component would benefit from TypeScript interface definition and accessibility improvements.
+ interface ContentFeedProps { + className?: string; + } - const ContentFeed = () => { + const ContentFeed: React.FC<ContentFeedProps> = ({ className }) => { const router = useRouter() const onNewPost = useCallback(() => { router.push('/new-post') }, [router]) return ( - <RootContainer> + <RootContainer className={className}> <HeaderContainer> <Typography component="h4" variant="h4"> Content Feed </Typography> <Button variant="outlined" color="inherit" onClick={onNewPost} disableRipple sx={{ maxWidth: 'fit-content' }} + aria-label="Create new post" > New Post </Button> </HeaderContainer> </RootContainer> ) }
23-31: Consider adding loading state to the button.Since the button triggers a navigation action, it would be good to add a loading state to prevent multiple clicks.
+ const [isNavigating, setIsNavigating] = useState(false); const onNewPost = useCallback(() => { + setIsNavigating(true); router.push('/new-post') }, [router]) <Button variant="outlined" color="inherit" onClick={onNewPost} disableRipple + disabled={isNavigating} sx={{ maxWidth: 'fit-content' }} aria-label="Create new post" > New Post </Button>packages/components/modules/content-feed/ContentFeedImage/styled.tsx (2)
3-22: Consider enhancing scrollbar accessibilityThe scrollbar styling looks good, but consider adding
@media (prefers-reduced-motion: reduce)to disable smooth scrolling for users who prefer reduced motion.export const ContentFeedImageContainer = styled(Box)(({ theme }) => ({ display: 'flex', marginBottom: '16px', overflow: 'auto', paddingBottom: '6px', + '@media (prefers-reduced-motion: reduce)': { + '::-webkit-scrollbar': { + scrollBehavior: 'auto', + }, + }, '::-webkit-scrollbar': { height: '6px', },
56-70: Add hover state color contrast for accessibilityThe RemoveFileButton maintains the same background color on hover, which might not provide enough visual feedback for users.
export const RemoveFileButton = styled('button')(({ theme }) => ({ position: 'absolute', top: '4px', right: '4px', width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '0 6px 0 6px', backgroundColor: theme.palette.grey[800], '&:hover': { - backgroundColor: theme.palette.grey[800], + backgroundColor: theme.palette.grey[900], + opacity: 0.9, }, }))packages/components/modules/content-feed/NewContentPost/index.tsx (1)
89-98: Add loading state feedbackThe LoadingButton should provide visual feedback about the loading state to improve user experience.
<LoadingButton color="inherit" type="submit" loading={isMutationInFlight} disabled={!isDirty || !isValid || isMutationInFlight} + loadingPosition="start" + startIcon={<></>} sx={{ maxWidth: 'fit-content', justifySelf: 'end' }} > Publish </LoadingButton>packages/components/modules/content-feed/ContentFeedImage/index.tsx (1)
38-39: Define constants outside componentMove constants outside the component to prevent recreation on each render.
+const DEFAULT_IMAGE_FORMATS = 'image/png, image/gif, image/jpeg' +const DEFAULT_IMAGE_MAX_SIZE = 10 * 1024 * 1024 // 10MB + const ContentFeedImage = ({ form }: IContentFeedImageProps) => { const [selectedUploadedFile, setSelectedUploadedFiles] = useState<File>() const [isDragging, setIsDragging] = useState(false) - - const DEFAULT_IMAGE_FORMATS = 'image/png, image/gif, image/jpeg' - const DEFAULT_IMAGE_MAX_SIZE = 10 * 1024 * 1024
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
packages/components/index.ts(1 hunks)packages/components/modules/content-feed/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/ContentFeedImage/styled.tsx(1 hunks)packages/components/modules/content-feed/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/index.ts(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Analyze (javascript)
- GitHub Check: Lint
- GitHub Check: Component Test Applications and Packages
🔇 Additional comments (4)
packages/components/modules/content-feed/NewContentPost/types.ts (1)
6-8: LGTM! Well-structured type for file uploads.The template literal type for dynamic keys is a good approach for handling multiple file uploads.
packages/components/modules/content-feed/index.ts (1)
1-7: LGTM! Clean export structure.The export structure is well-organized and follows common patterns.
packages/components/index.ts (1)
5-5: LGTM! Consistent module export pattern.The content-feed module export follows the established pattern and maintains alphabetical order.
packages/components/modules/content-feed/graphql/mutations/ContentPostCreate.ts (1)
4-22: LGTM! Well-structured GraphQL mutation.The mutation is well-defined with proper error handling and type safety. The structure follows GraphQL best practices by including both success and error cases in the response.
packages/components/modules/content-feed/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
957515f to
ae03fd9
Compare
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: 8
🧹 Nitpick comments (7)
packages/components/modules/content-feed/web/NewContentPost/types.ts (1)
1-4: Consider runtime type validation for images array.While the type definition is correct, consider adding runtime validation for the
imagesarray since it accepts multiple types (string[] | File[] | Blob[]). This can help prevent type-related issues during runtime.Consider using zod for runtime validation:
const imageValidator = z.array( z.union([z.string(), z.instanceof(File), z.instanceof(Blob)]) )packages/components/index.ts (1)
1-7: Consider explicit exports for better dependency tracking.While wildcard exports are convenient, they can make it harder to track dependencies and could lead to unintended exports. Consider using explicit exports to make dependencies more visible.
Example:
export { ContentFeed, ContentFeedImage, NewContentPost } from './modules/content-feed/web'packages/components/modules/content-feed/web/ContentFeed/styled.tsx (2)
3-8: Consider responsive width for better mobile support.Consider using a responsive width instead of a fixed 600px to better support different screen sizes.
Apply this diff to make the width responsive:
export const RootContainer = styled(Box)(() => ({ display: 'flex', - width: '600px', + width: '100%', + maxWidth: '600px', alignSelf: 'center', flexDirection: 'column', }))
18-24: Remove redundant justifyContent with gap.The
ButtonContaineruses bothgapandjustifyContent: 'space-between'which might conflict. Since you're usinggapfor spacing,space-betweenis unnecessary.Apply this diff to remove the redundant property:
export const ButtonContainer = styled(Box)(() => ({ display: 'flex', width: 'fit-content', flexDirection: 'row', - justifyContent: 'space-between', gap: '10px', }))packages/components/modules/content-feed/web/ContentFeed/index.tsx (1)
10-35: Consider TypeScript interface and accessibility improvements.The component looks good but could benefit from:
- Adding a TypeScript interface for future props
- Improving accessibility with ARIA labels
Apply these improvements:
+interface ContentFeedProps { + // Add future props here +} -const ContentFeed = () => { +const ContentFeed: React.FC<ContentFeedProps> = () => { const router = useRouter() const onNewPost = useCallback(() => { router.push('/new-post') }, [router]) return ( <RootContainer> <HeaderContainer> - <Typography component="h4" variant="h4"> + <Typography component="h1" variant="h4"> Content Feed </Typography> <Button variant="outlined" color="inherit" onClick={onNewPost} disableRipple sx={{ maxWidth: 'fit-content' }} + aria-label="Create new post" > New Post </Button> </HeaderContainer> </RootContainer> ) }packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (1)
8-21: Add cross-browser support for custom scrollbar styling.The scrollbar styling only targets WebKit browsers. Consider adding Firefox (scrollbar-*) and standard scrollbar styling for better cross-browser compatibility.
'::-webkit-scrollbar': { height: '6px', }, '::-webkit-scrollbar-track': { boxShadow: `inset 0 0 1px ${theme.palette.grey[400]}`, borderRadius: '10px', }, '::-webkit-scrollbar-thumb': { background: theme.palette.grey[400], borderRadius: '10px', }, '::-webkit-scrollbar-thumb:hover': { background: theme.palette.grey[600], }, +scrollbarWidth: 'thin', +scrollbarColor: `${theme.palette.grey[400]} ${theme.palette.grey[200]}`,packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
89-97: Prevent layout shift during loading state.The LoadingButton might cause layout shift when transitioning to loading state. Consider setting a minimum width.
<LoadingButton color="inherit" type="submit" loading={isMutationInFlight} disabled={!isDirty || !isValid || isMutationInFlight} - sx={{ maxWidth: 'fit-content', justifySelf: 'end' }} + sx={{ maxWidth: 'fit-content', justifySelf: 'end', minWidth: '100px' }} > Publish </LoadingButton>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
packages/components/index.ts(1 hunks)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/components/modules/content-feed/common/index.ts
🔇 Additional comments (5)
packages/components/modules/content-feed/web/NewContentPost/types.ts (1)
6-8: LGTM! Good use of template literal type.The template literal type for dynamic image keys is a good use of TypeScript's type system features.
packages/components/modules/content-feed/web/index.ts (1)
1-6: LGTM! Well-organized exports.The exports are well-organized, with components exported individually and types re-exported separately. This provides good flexibility for importing specific components or all types as needed.
packages/components/modules/content-feed/web/NewContentPost/constants.ts (1)
5-8: LGTM! Good use of satisfies operator.The use of the
satisfiesoperator ensures type safety while maintaining type inference. This is a good TypeScript practice.packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts (2)
5-23: Well-structured GraphQL mutation with proper error handling.The mutation is well-designed with:
- Proper error handling structure
- Clear field selection
- Good type safety
25-26: Clean hook implementation using react-relay.The hook implementation is clean and properly typed.
packages/components/modules/content-feed/web/NewContentPost/constants.ts
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
ae03fd9 to
6c551c8
Compare
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: 3
♻️ Duplicate comments (1)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
52-72:⚠️ Potential issueAdd error handling for network failures.
The mutation error handling only covers API response errors but doesn't handle network failures or unexpected errors.
commitMutation({ variables: { input: { content: data.content, images: imageKeys, }, }, uploadables, + onError: (error) => { + sendToast('Failed to create post. Please try again.', { type: 'error' }); + }, onCompleted(response) { const errors = response.contentPostCreate?.errors if (errors) { sendToast('Something went wrong', { type: 'error' }) setFormRelayErrors(formReturn, errors) } else { reset({ content: '' }) sendToast('Post Created Successfully', { type: 'success' }) router.push(`/content-post/${response.contentPostCreate?.contentPost?.node?.id}`) } }, })
🧹 Nitpick comments (3)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (2)
93-93: Consider adding visual feedback when images are attached.The current disabled state is based on content validity, but users might not understand why the button is disabled if they've only attached images without text.
disabled={!isDirty || !isValid || isMutationInFlight}Consider enhancing the UI to provide clearer feedback about form validation state.
102-110: Improve accessibility by adding aria attributes.The text input could benefit from ARIA attributes to improve accessibility for screen reader users.
<TextField name="content" type="text" placeholder="What is on your mind?" multiline rows={4} control={control} + aria-label="Post content" + aria-required="true" />packages/components/schema.graphql (1)
482-490: Consider implementing additional interfaces for ContentPost.The ContentPost type currently doesn't implement interfaces like CommentsInterface or ReactionsInterface, which would enable commenting and reactions on posts. Consider adding these interface implementations to provide consistent functionality across content types.
-type ContentPost implements Node { +type ContentPost implements Node & CommentsInterface & ReactionsInterface { author: User content: String! """The ID of the object""" id: ID! pk: Int! contentImages: [ContentPostImage] + commentsCount: CommentsCount! + comments(offset: Int, before: String, after: String, first: Int, last: Int, q: String, orderBy: String): CommentConnection + isCommentsEnabled: Boolean! + reactionsCount: ReactionsCount + reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection + isReactionsEnabled: Boolean! + myReaction(profileId: ID): Reaction }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
packages/components/index.ts(1 hunks)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/schema.graphql(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
- packages/components/modules/content-feed/common/index.ts
- packages/components/modules/content-feed/web/index.ts
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/components/index.ts
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/components/modules/content-feed/web/NewContentPost/types.ts
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx
- packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Analyze (javascript)
- GitHub Check: Component Test Packages
- GitHub Check: Build and Lint Packages
🔇 Additional comments (1)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
74-76: Add confirmation dialog for unsaved changes.Users might accidentally lose their work when clicking cancel. Consider adding a confirmation dialog when there are unsaved changes.
const onCancel = useCallback(() => { + if (isDirty) { + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?'); + if (!confirmed) return; + } router.push('/feed') }, [router])
6c551c8 to
f1707b4
Compare
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 (1)
packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (1)
42-54: Use theme colors instead of hardcoded color values for consistency.The MiniatureFileWrapper component uses a hardcoded "black" color for its hover state border, which could lead to inconsistent styling when the theme changes or in different color modes (like dark mode).
export const MiniatureFileWrapper = styled(Box)(({ theme }) => ({ position: 'relative', flexShrink: 0, width: '80px', height: '80px', border: `2px solid ${theme.palette.grey[200]}`, borderRadius: '12px', padding: '4px', display: 'inline-block', '&:hover': { - border: `2px solid black`, + border: `2px solid ${theme.palette.grey[900]}`, }, }))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
packages/components/index.ts(1 hunks)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/schema.graphql(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
- packages/components/modules/content-feed/common/index.ts
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/components/modules/content-feed/web/index.ts
- packages/components/modules/content-feed/web/NewContentPost/types.ts
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
- packages/components/index.ts
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: Build and Lint Packages
- GitHub Check: Component Test Packages
🔇 Additional comments (9)
packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (4)
56-70: Enhance button accessibility with proper focus states.The RemoveFileButton lacks proper focus state styling for keyboard navigation. Also, the hover state uses the same color as the default state, providing no visual feedback on interaction.
export const RemoveFileButton = styled('button')(({ theme }) => ({ position: 'absolute', top: '4px', right: '4px', width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '0 6px 0 6px', backgroundColor: theme.palette.grey[800], + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: '2px', + }, '&:hover': { - backgroundColor: theme.palette.grey[800], + backgroundColor: theme.palette.grey[900], }, }))
72-83: LGTM! Good implementation of the drop zone container.The DropFilesContainer component is well-structured with proper spacing, theming, and layout properties. The dashed border and centered content create a clear visual indication of the drop zone area.
3-22: Good implementation of scrollable container with custom scrollbar styling.The ContentFeedImageContainer has well-defined scrollbar customization that improves user experience while maintaining theme consistency.
24-40: Good implementation of file upload button components.Both the AddFileButton and AddFileWrapper components are well-designed with proper theming, dimensions, and hover states.
packages/components/modules/content-feed/web/NewContentPost/index.tsx (2)
52-71: Add error handling for network failures.This mutation call doesn't include an
onErrorcallback to handle scenarios such as network failures, timeouts, or unexpected exceptions. This is a repeat of a previous review comment; adding anonErrorcallback would help display a user-friendly message or perform alternate error-handling logic.
74-76: Prompt user for confirmation before discarding unsaved changes.If the form is dirty and the user clicks "Cancel," they could lose their input without warning. A confirmation prompt (e.g., with
window.confirm) would help avoid accidental data loss. This is a repeat of a previous review comment.packages/components/schema.graphql (3)
502-506: Enforce schema-level validation on the “content” field.The
contentfield has no minimum length or pattern constraints. Consider adding a custom directive or adopting a library likegraphql-constraint-directiveto validate essential constraints (e.g. minimum length, restricted characters) at the schema level. This is a repeat of a previous review comment.
780-781: Separate query from mutation.
contentPostsin theMutationtype appears more like a query operation and might already exist in theQuerytype at line 1453. Consider removing or relocating it to theQuerytype. This is a repeat of a previous review comment.
1891-1932: Remove duplicate type definitions forContentPostand related entities.These lines repeat the same definitions for
ContentPost,ContentPostConnection, andContentPostImagethat were previously defined at lines 482-532. Keeping duplicate schema definitions can cause confusion and potential conflicts. Remove or consolidate them into a single definition. This is a repeat of a previous review comment.
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeed/index.tsx
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
038efd5 to
6756f3c
Compare
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: 3
♻️ Duplicate comments (4)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (2)
28-29:⚠️ Potential issueFix inconsistency in max file size.
The displayed max file size (15MB) doesn't match the actual validation limit (10MB).
- const DEFAULT_IMAGE_MAX_SIZE = 10 * 1024 * 1024 + const DEFAULT_IMAGE_MAX_SIZE = 15 * 1024 * 1024Also applies to: 101-102
62-71:⚠️ Potential issueAdd file validation on drop.
Files dropped via drag-and-drop bypass the size and type validation that's present in the file input handler.
const handleDrop = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setIsDragging(false) const { files } = e.dataTransfer if (files.length) { + const validFiles = Array.from(files).filter((file) => { + if (!DEFAULT_IMAGE_FORMATS.includes(file.type)) { + sendToast('Invalid file type', { type: 'error' }) + return false + } + if (file.size > DEFAULT_IMAGE_MAX_SIZE) { + sendToast(`File too large (max ${DEFAULT_IMAGE_MAX_SIZE / 1024 / 1024}MB)`, { type: 'error' }) + return false + } + return true + }) - form.setValue('images', [...(formFiles as never[]), ...(files as unknown as never[])]) + form.setValue('images', [...(formFiles as never[]), ...(validFiles as unknown as never[])]) } }packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
52-72: 🛠️ Refactor suggestionAdd error handling for network failures.
The mutation error handling only covers API response errors but doesn't handle network failures or unexpected errors.
commitMutation({ variables: { input: { content: data.content, images: imageKeys, }, }, uploadables, + onError: (error) => { + sendToast('Failed to create post. Please try again.', { type: 'error' }); + }, onCompleted(response) { const errors = response.contentPostCreate?.errors if (errors) { sendToast('Something went wrong', { type: 'error' }) setFormRelayErrors(formReturn, errors) } else { reset({ content: '' }) sendToast('Post Created Successfully', { type: 'success' }) router.push(`/content-post/${response.contentPostCreate?.contentPost?.node?.id}`) } }, })packages/components/schema.graphql (1)
1892-1933:⚠️ Potential issueDuplicate GraphQL Type Definitions Detected (Second Occurrence)
This block (lines 1892–1933) redefines the ContentPost types and related types identically to the earlier block. To avoid duplicate type definitions—which can lead to ambiguous schema behavior—please remove one of these duplicate sets.
🧹 Nitpick comments (7)
packages/components/modules/content-feed/web/ContentFeed/types.ts (1)
1-3: Consider adding properties or using a type alias instead of an empty interfaceThe
IContentFeedPropsinterface is currently empty, which is equivalent to{}. According to static analysis, this is a suspicious pattern.You could:
- Add properties that will be needed by the ContentFeed component, or
- Use a type alias instead:
-export interface IContentFeedProps { - -} +export type IContentFeedProps = {}
- If no props are planned for this component, you could remove the interface entirely and use
FCwithout type parameters.🧰 Tools
🪛 Biome (1.9.4)
[error] 1-3: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/ContentFeed/index.tsx (1)
21-23: Consider making the title configurableFor maximum flexibility, the title "Content Feed" should also be configurable through props.
export interface IContentFeedProps { + title?: string } -const ContentFeed: FC<IContentFeedProps> = () => { +const ContentFeed: FC<IContentFeedProps> = ({ title = 'Content Feed' }) => { // ... return ( <RootContainer> <HeaderContainer> <Typography component="h4" variant="h4"> - Content Feed + {title} </Typography> // ...packages/components/modules/content-feed/web/NewContentPost/types.ts (1)
10-10: Use a type alias instead of empty interface.An empty interface is equivalent to
{}and doesn't provide any additional type information. Consider using a type alias for clarity.-export interface INewContentPostProps {} +export type NewContentPostProps = {}🧰 Tools
🪛 Biome (1.9.4)
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (2)
43-43: Import DragEvent type explicitly.The DragEvent usage is missing a generic type parameter, which could lead to type errors.
-import { ChangeEventHandler, FC, useRef, useState } from 'react' +import { ChangeEventHandler, DragEvent, FC, useRef, useState } from 'react' - const handleDragEnter = (e: React.DragEvent) => { + const handleDragEnter = (e: DragEvent<HTMLDivElement>) => { - const handleDragOver = (e: React.DragEvent) => { + const handleDragOver = (e: DragEvent<HTMLDivElement>) => { - const handleDragLeave = (e: React.DragEvent) => { + const handleDragLeave = (e: DragEvent<HTMLDivElement>) => { - const handleDrop = (e: React.DragEvent) => { + const handleDrop = (e: DragEvent<HTMLDivElement>) => {Also applies to: 49-49, 54-54, 62-62
25-25: Fix typo in state variable name.The variable name doesn't match its purpose - it's setting a single file but named as if handling multiple files.
- const [selectedUploadedFile, setSelectedUploadedFiles] = useState<File>() + const [selectedUploadedFile, setSelectedUploadedFile] = useState<File>()Also applies to line 165:
- onClick={() => setSelectedUploadedFiles(file)} + onClick={() => setSelectedUploadedFile(file)}packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
21-21: Remove 'I' prefix from interface name.According to coding standards in this project, interface names should not have the 'I' prefix.
-import { ContentPostCreateForm, INewContentPostProps, UploadableContentPostFiles } from './types' +import { ContentPostCreateForm, NewContentPostProps, UploadableContentPostFiles } from './types'Also update the type usage in line 23:
-const NewContentPost: FC<INewContentPostProps> = () => { +const NewContentPost: FC<NewContentPostProps> = () => {packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (1)
24-32: Add focus state to AddFileButton.AddFileButton is missing focus states for keyboard navigation.
export const AddFileButton = styled(Button)(({ theme }) => ({ width: '72px', height: '72px', backgroundColor: theme.palette.grey[200], borderRadius: '8px', + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: '2px', + }, '&:hover': { backgroundColor: theme.palette.grey[300], }, }))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
packages/components/index.ts(1 hunks)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/types.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/package.json(1 hunks)packages/components/schema.graphql(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- packages/components/modules/content-feed/common/index.ts
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/components/package.json
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/components/modules/content-feed/web/index.ts
🧰 Additional context used
🧬 Code Definitions (2)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (3)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2) (2)
ContentPostCreateForm(1-4)UploadableContentPostFiles(6-8)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts (1) (1)
useContentPostCreateMutation(25-26)packages/components/modules/content-feed/web/NewContentPost/constants.ts (2) (2)
DEFAULT_CONTENT_POST_CREATE_FORM_VALUES(5-8)CONTENT_POST_CREATE_FORM_VALIDATION(10-13)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (2)
packages/components/modules/content-feed/web/ContentFeedImage/types.ts (1) (1)
IContentFeedImageProps(3-12)packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (6) (6)
DropFilesContainer(72-83)ContentFeedImageContainer(3-22)AddFileWrapper(34-40)AddFileButton(24-32)MiniatureFileWrapper(42-54)RemoveFileButton(56-70)
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/ContentFeed/types.ts
[error] 1-3: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
🪛 GitHub Check: Build and Lint Packages
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
[failure] 9-9:
'/home/runner/work/baseapp-frontend/baseapp-frontend/packages/components/node_modules/@mui/icons-material/esm/index.js' imported multiple times
[failure] 8-8:
'/home/runner/work/baseapp-frontend/baseapp-frontend/packages/components/node_modules/@mui/icons-material/esm/index.js' imported multiple times
[failure] 7-7:
'/home/runner/work/baseapp-frontend/baseapp-frontend/packages/components/node_modules/@mui/icons-material/esm/index.js' imported multiple times
packages/components/index.ts
[failure] 7-7:
Missing file extension for "./modules/notifications"
[failure] 6-6:
Missing file extension for "./modules/profiles"
[failure] 5-5:
Missing file extension for "./modules/content-feed"
[failure] 4-4:
Missing file extension for "./modules/navigations"
[failure] 3-3:
Missing file extension for "./modules/messages"
[failure] 2-2:
Missing file extension for "./modules/comments"
[failure] 1-1:
Missing file extension for "./modules/shared"
🪛 GitHub Actions: Main Workflow
packages/components/index.ts
[error] 1-1: Missing file extension for "./modules/shared" import/extensions
🔇 Additional comments (11)
packages/components/modules/content-feed/web/ContentFeed/index.tsx (1)
14-16: Make the new post route configurable for flexibilityHardcoding the route path makes the component less reusable across different projects that might have different routing structures.
Per the previous review comment, consider making the route configurable:
const ContentFeed: FC<IContentFeedProps> = () => { const router = useRouter() + const newPostRoute = '/new-post' const onNewPost = useCallback(() => { - router.push('/new-post') + router.push(newPostRoute) }, [router])Even better, add it as a prop with a default value:
export interface IContentFeedProps { + newPostRoute?: string } -const ContentFeed: FC<IContentFeedProps> = () => { +const ContentFeed: FC<IContentFeedProps> = ({ newPostRoute = '/new-post' }) => { const router = useRouter() const onNewPost = useCallback(() => { - router.push('/new-post') + router.push(newPostRoute) - }, [router]) + }, [router, newPostRoute])packages/components/index.ts (1)
1-7:❓ Verification inconclusive
Fix missing file extensions in import statements
The build pipeline is failing due to missing file extensions in the import statements. This is a common ESM requirement.
Add file extensions to resolve the pipeline failures:
-export * from './modules/__shared__' -export * from './modules/comments' -export * from './modules/messages' -export * from './modules/navigations' -export * from './modules/content-feed' -export * from './modules/profiles' -export * from './modules/notifications' +export * from './modules/__shared__/index.js' +export * from './modules/comments/index.js' +export * from './modules/messages/index.js' +export * from './modules/navigations/index.js' +export * from './modules/content-feed/index.js' +export * from './modules/profiles/index.js' +export * from './modules/notifications/index.js'Alternatively, consider removing this file as suggested in previous review comments.
🏁 Script executed:
#!/bin/bash # Check if this file is necessary or if there's a better approach to module exports # Look for other files importing from this index.ts echo "Files importing from packages/components/index.ts:" rg -l "from ['|\"]@components['|\"]" --type ts --type tsx # Check if direct imports of the modules are used elsewhere echo "Files importing directly from individual modules:" rg -l "from ['|\"]@components/modules/" --type ts --type tsxLength of output: 404
Below is a revised review comment. Note that the re-exported modules must include explicit file extensions (i.e.
/index.js) to satisfy ESM requirements and resolve the build pipeline failures. The original in-repository search for importers was inconclusive due to file type flags causing errors; please manually verify (or re-run adjusted commands) whether other parts of the codebase directly import from the module directories or the index file.Action Required: Fix missing file extensions and double-check module usage
Update exports in
packages/components/index.tsto:-export * from './modules/__shared__' -export * from './modules/comments' -export * from './modules/messages' -export * from './modules/navigations' -export * from './modules/content-feed' -export * from './modules/profiles' -export * from './modules/notifications' +export * from './modules/__shared__/index.js' +export * from './modules/comments/index.js' +export * from './modules/messages/index.js' +export * from './modules/navigations/index.js' +export * from './modules/content-feed/index.js' +export * from './modules/profiles/index.js' +export * from './modules/notifications/index.js'Verification note:
The previous automated search usingrg --type ts --type tsxfailed with errors (unrecognized file type: tsx). Please run a revised search (removing file type restrictions) to ensure that either:
- Other files are importing from
packages/components/index.ts, or- Direct imports from the individual module directories are used.
Alternatively, if this re-export file isn’t necessary for your project, consider removing it as mentioned in previous review comments.
🧰 Tools
🪛 GitHub Check: Build and Lint Packages
[failure] 7-7:
Missing file extension for "./modules/notifications"
[failure] 6-6:
Missing file extension for "./modules/profiles"
[failure] 5-5:
Missing file extension for "./modules/content-feed"
[failure] 4-4:
Missing file extension for "./modules/navigations"
[failure] 3-3:
Missing file extension for "./modules/messages"
[failure] 2-2:
Missing file extension for "./modules/comments"
[failure] 1-1:
Missing file extension for "./modules/shared"🪛 GitHub Actions: Main Workflow
[error] 1-1: Missing file extension for "./modules/shared" import/extensions
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
1-4: LGTM! Good type definition for the form.The ContentPostCreateForm interface clearly defines the structure needed for content posts with appropriate optionality for the images field.
6-8: LGTM! Well-typed uploadable files mapping.The UploadableContentPostFiles type elegantly handles the image file naming pattern required for the GraphQL mutation.
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (1)
82-88: Optimize image preview memory management.The URL.createObjectURL call creates a memory leak if the component unmounts before the image loads.
+ const [previewUrl, setPreviewUrl] = useState<string>('') + + useEffect(() => { + if (selectedUploadedFile) { + const objectUrl = URL.createObjectURL(selectedUploadedFile) + setPreviewUrl(objectUrl) + return () => URL.revokeObjectURL(objectUrl) + } + }, [selectedUploadedFile]) return ( <Box width="100%" position="relative" height="500px" mb="24px"> <Image - src={URL.createObjectURL(selectedUploadedFile)} + src={previewUrl} alt={selectedUploadedFile.name} fill style={{ objectFit: 'cover', borderRadius: '8px', height: '100%', width: '100%' }} - onLoad={() => URL.revokeObjectURL(URL.createObjectURL(selectedUploadedFile))} /> </Box> )Similar issue exists at lines 168-174.
packages/components/modules/content-feed/web/NewContentPost/index.tsx (3)
74-76: Add confirmation dialog for unsaved changes.Users might accidentally lose their work when clicking cancel. Consider adding a confirmation dialog when there are unsaved changes.
const onCancel = useCallback(() => { + if (isDirty) { + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?'); + if (!confirmed) return; + } router.push('/feed') }, [router])
38-39: LGTM! Good form state management.The component properly tracks form state including
isDirtyandisValidfor controlling the submit button state.
85-98: LGTM! Intuitive button handling in the UI.The UI provides clear actions with Cancel and Publish buttons, with appropriate loading and disabled states.
packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (2)
56-70: Enhance button accessibility with proper focus states.The RemoveFileButton lacks proper focus state styling for keyboard navigation. Also, the hover state uses the same color as the default state.
export const RemoveFileButton = styled('button')(({ theme }) => ({ position: 'absolute', top: '4px', right: '4px', width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '0 6px 0 6px', backgroundColor: theme.palette.grey[800], + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: '2px', + }, '&:hover': { - backgroundColor: theme.palette.grey[800], + backgroundColor: theme.palette.grey[900], }, }))
3-22: LGTM! Good scrollbar customization.The scrollbar styling provides a consistent and polished look for the overflow content.
packages/components/schema.graphql (1)
483-533: Duplicate GraphQL Type Definitions Detected (First Occurrence)
The ContentPost-related types defined in this block (lines 483–533) appear to be duplicated later in the file (lines 1892–1933). Only one set should be maintained to prevent schema conflicts and confusion. Additionally, consider adding a custom validation directive (e.g.@constraint(minLength: 10)) on thecontentfield in theContentPostCreateInputtype to ensure minimal content quality if your GraphQL implementation and tooling support it.Suggested diff to remove this duplicate block (if you choose this instance to keep only the later occurrence):
-type ContentPost implements Node { - author: User - content: String! - """The ID of the object""" - id: ID! - pk: Int! - contentImages: [ContentPostImage] -} - -type ContentPostConnection { - """Pagination data for this connection.""" - pageInfo: PageInfo! - - """Contains the nodes in this connection.""" - edges: [ContentPostEdge]! - totalCount: Int - edgeCount: Int -} - -input ContentPostCreateInput { - content: String! - images: [String] - clientMutationId: String -} - -type ContentPostCreatePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - contentPost: ContentPostEdge - clientMutationId: String -} - -"""A Relay edge containing a `ContentPost` and its cursor.""" -type ContentPostEdge { - """The item at the end of the edge""" - node: ContentPost - - """A cursor for use in pagination""" - cursor: String! -} - -type ContentPostImage implements Node { - image: String - post: ContentPost! - - """The ID of the object""" - id: ID! - pk: Int! -}
packages/components/modules/content-feed/web/ContentFeedImage/types.ts
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
Outdated
Show resolved
Hide resolved
6cdd7bc to
e386cca
Compare
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.
Caution
Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 4
🧹 Nitpick comments (6)
packages/components/modules/content-feed/web/NewContentPost/types.ts (1)
10-10: Use a type alias instead of an empty interface.Empty interfaces are equivalent to
{}in TypeScript and should be avoided.-export interface INewContentPostProps {} +export type NewContentPostProps = Record<string, never>🧰 Tools
🪛 Biome (1.9.4)
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (1)
43-43: Import DragEvent type directly.The current code uses React.DragEvent but DragEvent should be imported directly.
-import { ChangeEventHandler, FC, useRef, useState } from 'react' +import { ChangeEventHandler, DragEvent, FC, useRef, useState } from 'react' // Then update all occurrences: -const handleDragEnter = (e: React.DragEvent) => { +const handleDragEnter = (e: DragEvent) => {packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (1)
42-54: Consider using theme color instead of hardcoded color value.The MiniatureFileWrapper uses a hardcoded "black" color for hover state instead of a theme color.
'&:hover': { - border: `2px solid black`, + border: `2px solid ${theme.palette.common.black}`, },packages/components/schema.graphql (3)
511-517: ContentPostCreatePayload payload type:
The payload type follows standard GraphQL mutation design by returning potential errors, debugging information, the created content post edge, and theclientMutationId. As a minor suggestion, double-check that naming (i.e.contentPost) is consistent with similar mutation payloads elsewhere in your schema.
528-535: ContentPostImage type:
The newContentPostImagetype is set up to represent images associated with a content post. It would be beneficial to validate theimagefield (for example, by checking for a valid URL format) and to ensure that the relationship to theContentPostis correctly handled in your resolvers.
573-576: DeleteNodeInput input type:
This generic input type for deletion operations is straightforward, accepting anidand aclientMutationId. Ensure that this type is consistent with other delete-related inputs in your schema and that it meets the expected contract for identifying nodes to delete.
🛑 Comments failed to post (4)
packages/components/modules/content-feed/web/ContentFeed/types.ts (1)
1-3: 🛠️ Refactor suggestion
Empty interface should be replaced with a type alias or appropriate properties.
The current empty interface doesn't provide any type-checking value. According to TypeScript best practices and the static analysis tool:
If the component truly doesn't need props, consider:
export type IContentFeedProps = Record<string, never>;If this is a placeholder for future properties, include relevant properties or at least add a comment explaining the intention:
export interface IContentFeedProps { // TODO: Add properties as requirements are finalized }These changes will improve type safety and make the code more maintainable.
🧰 Tools
🪛 Biome (1.9.4)
[error] 1-3: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (1)
167-174:
⚠️ Potential issueFix memory leak in image preview thumbnails.
Similar to the main image preview, the thumbnails also have a memory leak with URL.createObjectURL.
+// Add at component level +const [objectUrls, setObjectUrls] = useState<Record<string, string>>({}) + +// Add effect to manage object URLs +useEffect(() => { + const newObjectUrls: Record<string, string> = {} + + formFiles?.forEach((file, index) => { + if (!objectUrls[index]) { + newObjectUrls[index] = URL.createObjectURL(file) + } + }) + + // Update state with new URLs + if (Object.keys(newObjectUrls).length > 0) { + setObjectUrls(prev => ({...prev, ...newObjectUrls})) + } + + // Cleanup function + return () => { + Object.values(objectUrls).forEach(url => URL.revokeObjectURL(url)) + } +}, [formFiles]) // Then update the Image component <Image - src={URL.createObjectURL(file)} + src={objectUrls[index] || ''} alt={file.name} width={72} height={72} style={{ objectFit: 'cover', borderRadius: '8px', height: '100%' }} - onLoad={() => URL.revokeObjectURL(URL.createObjectURL(file))} />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.import React, { useState, useEffect } from 'react'; import Image from 'some-image-component-path'; // adjust the import as needed const ContentFeedImage = ({ formFiles }) => { // Add at component level const [objectUrls, setObjectUrls] = useState<Record<number, string>>({}); // Add effect to manage object URLs useEffect(() => { const newObjectUrls: Record<number, string> = {}; formFiles?.forEach((file, index) => { if (!objectUrls[index]) { newObjectUrls[index] = URL.createObjectURL(file); } }); // Update state with new URLs if any were created if (Object.keys(newObjectUrls).length > 0) { setObjectUrls(prev => ({ ...prev, ...newObjectUrls })); } // Cleanup function to revoke URLs when formFiles change or component unmounts return () => { Object.values(objectUrls).forEach(url => URL.revokeObjectURL(url)); }; }, [formFiles, objectUrls]); return ( <div> {/* Other component content */} {formFiles?.map((file, index) => ( <Image key={index} // Then update the Image component to use the stored URL src={objectUrls[index] || ''} alt={file.name} width={72} height={72} style={{ objectFit: 'cover', borderRadius: '8px', height: '100%' }} // Removed onLoad handler as cleanup is handled in useEffect /> ))} </div> ); }; export default ContentFeedImage;packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
41-50:
⚠️ Potential issueProperly initialize imageKeys array.
The imageKeys array is defined but never populated before being sent to the mutation.
const onSubmit = handleSubmit((data: ContentPostCreateForm) => { const uploadables: UploadableContentPostFiles = {} const imageKeys: string[] = [] if (data.images) { data.images.forEach((image, index) => { uploadables[`images.${index}`] = image as File + imageKeys.push(`images.${index}`) }) }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const onSubmit = handleSubmit((data: ContentPostCreateForm) => { const uploadables: UploadableContentPostFiles = {} const imageKeys: string[] = [] if (data.images) { data.images.forEach((image, index) => { uploadables[`images.${index}`] = image as File imageKeys.push(`images.${index}`) }) }packages/components/schema.graphql (1)
505-509: 🛠️ Refactor suggestion
ContentPostCreateInput input type:
This input type is clear and concise, accepting a non-nullcontent, an optional list ofimages, and aclientMutationId. Consider whether you might need to enforce extra validations on thecontentfield (for example, a minimum length) using custom directives. Additionally, if image metadata becomes relevant, you may want to evolve theimagesfield into a more structured type later on.
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: 3
♻️ Duplicate comments (1)
packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (1)
56-70: 🛠️ Refactor suggestionImprove RemoveFileButton accessibility by fixing focus and hover states.
The current implementation lacks proper focus state styling for keyboard navigation, and the hover state uses the same color as the default state, which doesn't provide visual feedback to users.
Apply this diff to enhance accessibility:
export const RemoveFileButton = styled('button')(({ theme }) => ({ position: 'absolute', top: '4px', right: '4px', width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '0 6px 0 6px', backgroundColor: theme.palette.grey[800], + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: '2px', + }, '&:hover': { - backgroundColor: theme.palette.grey[800], + backgroundColor: theme.palette.grey[900], }, }))
🧹 Nitpick comments (7)
packages/components/modules/content-feed/web/ContentFeed/types.ts (1)
1-3: Replace empty interface with a type alias or add relevant fields.
This interface is currently empty, which triggers a linter warning. If no props are required, you can replace it with a type alias or add fields that accurately represent the component’s needs.-export interface IContentFeedProps { -} +// If you're not expecting any props: +export type IContentFeedProps = Record<string, never>🧰 Tools
🪛 Biome (1.9.4)
[error] 1-3: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts (1)
10-10: Replace empty interface with a type alias or remove it if unused.
An empty interface can be replaced with a type alias to avoid linter warnings.-export interface INewContentPostProps {} +export type INewContentPostProps = Record<string, never>🧰 Tools
🪛 Biome (1.9.4)
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (2)
24-32: Add ARIA label for better accessibility.The AddFileButton component would benefit from an aria-label to provide context for screen reader users.
Consider adding aria-label through props:
export const AddFileButton = styled(Button)(({ theme }) => ({ width: '72px', height: '72px', backgroundColor: theme.palette.grey[200], borderRadius: '8px', '&:hover': { backgroundColor: theme.palette.grey[300], }, + // Make sure to pass aria-label="Add file" when using this component }))
8-21: Consider cross-browser compatibility for scrollbar styling.Custom scrollbar styling works well for WebKit browsers, but you should consider Firefox and other browsers for consistent user experience.
Add Firefox scrollbar support:
export const ContentFeedImageContainer = styled(Box)(({ theme }) => ({ display: 'flex', marginBottom: '16px', overflow: 'auto', paddingBottom: '6px', '::-webkit-scrollbar': { height: '6px', }, '::-webkit-scrollbar-track': { boxShadow: `inset 0 0 1px ${theme.palette.grey[400]}`, borderRadius: '10px', }, '::-webkit-scrollbar-thumb': { background: theme.palette.grey[400], borderRadius: '10px', }, '::-webkit-scrollbar-thumb:hover': { background: theme.palette.grey[600], }, + scrollbarWidth: 'thin', + scrollbarColor: `${theme.palette.grey[400]} ${theme.palette.grey[200]}`, }))packages/components/schema.graphql (3)
484-493: Consider adding reactionsInterface to ContentPost.The ContentPost type might benefit from implementing the ReactionsInterface to allow users to react to posts, similar to other content types in the application.
Consider updating the type definition:
-type ContentPost implements Node { +type ContentPost implements Node & ReactionsInterface { user: User! profile: Profile content: String! images(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ContentPostImageConnection """The ID of the object""" id: ID! pk: Int! + reactionsCount: ReactionsCount + reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection + isReactionsEnabled: Boolean! + myReaction(profileId: ID): Reaction }
484-493: Consider adding CommentsInterface to ContentPost.Posts typically include commenting functionality. Adding the CommentsInterface would enable users to comment on posts.
Consider updating the type definition:
-type ContentPost implements Node { +type ContentPost implements Node & CommentsInterface { user: User! profile: Profile content: String! images(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ContentPostImageConnection """The ID of the object""" id: ID! pk: Int! + commentsCount: CommentsCount! + comments(offset: Int, before: String, after: String, first: Int, last: Int, q: String, orderBy: String): CommentConnection + isCommentsEnabled: Boolean! }
528-535: Add documentation comments to ContentPostImage.The ContentPostImage type lacks descriptive documentation comments that explain the purpose of each field, especially the image field which might need more clarity on what exactly it contains (URL, base64, etc).
Consider adding documentation comments:
type ContentPostImage implements Node { + """The image URL or identifier""" image: String + """The post this image belongs to""" post: ContentPost! """The ID of the object""" id: ID! pk: Int! }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
packages/components/__generated__/ActivityLogsFragment.graphql.tsis excluded by!**/__generated__/**packages/components/__generated__/RemoveMemberMutation.graphql.tsis excluded by!**/__generated__/**
📒 Files selected for processing (15)
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/types.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/package.json(1 hunks)packages/components/schema.graphql(9 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
- packages/components/modules/content-feed/common/index.ts
- packages/components/modules/content-feed/web/ContentFeedImage/types.ts
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/components/package.json
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/components/modules/content-feed/web/index.ts
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
🧰 Additional context used
🧬 Code Definitions (2)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (4)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
ContentPostCreateForm(1-4)UploadableContentPostFiles(6-8)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts (1)
useContentPostCreateMutation(25-26)packages/components/modules/content-feed/web/NewContentPost/constants.ts (2)
DEFAULT_CONTENT_POST_CREATE_FORM_VALUES(5-8)CONTENT_POST_CREATE_FORM_VALIDATION(10-13)packages/components/modules/content-feed/web/NewContentPost/styled.tsx (3)
RootContainer(3-8)HeaderContainer(10-17)ButtonContainer(19-24)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (2)
packages/components/modules/content-feed/web/ContentFeedImage/types.ts (1)
IContentFeedImageProps(3-12)packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx (6)
DropFilesContainer(72-83)ContentFeedImageContainer(3-22)AddFileWrapper(34-40)AddFileButton(24-32)MiniatureFileWrapper(42-54)RemoveFileButton(56-70)
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/ContentFeed/types.ts
[error] 1-3: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
🔇 Additional comments (13)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
1-4: Looks good!
This interface properly defines the data structure needed for post creation.
6-8: Clean design for mapping uploadable files.
Defining a pattern-based key aids in handling multiple images.packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (5)
7-9: Imports from the same package can be consolidated.
This matches a previous comment about importing multiple icons separately.
22-22: Avoid 'I' prefix on type interfaces.
This naming convention feedback was given previously; consider removing the prefix for a cleaner style.
28-29: Fix file size mismatch.
Currently set to 10MB in code but 15MB is displayed in the UI, which can confuse users.- const DEFAULT_IMAGE_MAX_SIZE = 10 * 1024 * 1024 + const DEFAULT_IMAGE_MAX_SIZE = 15 * 1024 * 1024Or update the displayed limit at line 101 to match 10MB, ensuring consistency between validation and messaging.
Also applies to: 101-101
62-71: Add validation logic for drag-and-drop.
Files dropped here bypass the size check inhandleOnChange. This was mentioned before; replicate the filtering logic for dropped files.
80-91: Prevent repeated calls to URL.createObjectURL and memory leaks.
A past review suggested using a stable object URL stored in state or via useEffect. The current pattern triggers new URLs for each re-render and can leak memory.packages/components/modules/content-feed/web/NewContentPost/index.tsx (4)
21-23: Remove or enhance the empty interface usage.
INewContentPostPropsis imported but remains empty and unused in this component.
51-71: Add network error handling.
A previous review suggested handling network failures usingonError. Currently only server-side errors are handled viaonCompleted.
73-75: Prompt user about unsaved changes.
Reiterating a past suggestion: if the user has unsaved data, confirm before navigating away.
54-56:❓ Verification inconclusive
Verify passing images to the mutation.
The input object omitsimages. If your backend requires them as fields (not just uploadables), ensure they are included here.
🏁 Script executed:
#!/bin/bash # Searching the mutation schema usage to confirm if images are needed in the input ast-grep --pattern $'mutation $_($_) {\n contentPostCreate($_) {\n $$$\n }\n}'Length of output: 80
Action Required: Manually Verify the Mutation Input Fields
It appears that our automated search for usage of
contentPostCreatedid not return any conclusive evidence regarding whether the backend mutation requires theimagesfield in its input. Consequently, please manually verify the backend schema or API documentation to confirm if theimagesfield is expected as part of the mutation input. If the backend does requireimages(beyond handling uploadables), update the mutation payload inpackages/components/modules/content-feed/web/NewContentPost/index.tsxaccordingly.packages/components/schema.graphql (2)
1489-1489: LGTM: Proper location for contentPosts query.The contentPosts query is correctly placed in the Query type rather than in the Mutation type, which aligns with GraphQL best practices.
805-816:❓ Verification inconclusive
Verify that contentPostCreate mutation is included in unit tests.
It's important to ensure the new contentPostCreate mutation is properly tested.
Run the following script to check if tests exist for this mutation:
🏁 Script executed:
#!/bin/bash # Search for test files related to contentPostCreate fd -t f "test|spec" | grep -i "contentpost" | xargs cat | grep -i "contentpostcreate"Length of output: 83
Below is a revised review comment. We couldn’t confirm via our automated search that any test file contains an explicit reference to the new
contentPostCreatemutation. Since the original search script did not yield output, please manually verify (or run an expanded search) that this mutation is covered by unit tests—directly or indirectly—so that the new functionality is appropriately validated.Action Required: Verify unit tests for
contentPostCreate
- Ensure that a test is present that exercises
contentPostCreate(either explicitly or through integration tests).- If no such test exists, add one to fully cover the new functionality.
packages/components/modules/content-feed/web/NewContentPost/index.tsx
Outdated
Show resolved
Hide resolved
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: 3
♻️ Duplicate comments (5)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (2)
45-45:⚠️ Potential issueFix URL object memory leak.
The current implementation creates a new object URL and immediately revokes it on image load, which is incorrect and creates a memory leak. You should create the URL once and store it, then revoke that same URL after the image loads.
Implement this with a useEffect hook to properly manage URL lifecycle:
const ContentFeedImage: FC<IContentFeedImageProps> = ({ form }) => { const [selectedPreview, setSelectedPreview] = useState<File>() const [selectedPreviewIndex, setSelectedPreviewIndex] = useState<number>() + const [previewUrl, setPreviewUrl] = useState<string>() const { watch } = form const formFiles = watch('images') + useEffect(() => { + if (selectedPreview) { + const url = URL.createObjectURL(selectedPreview) + setPreviewUrl(url) + return () => URL.revokeObjectURL(url) + } + return undefined + }, [selectedPreview]) // Rest of the component... return ( <Box> {selectedPreview && ( <Box width="100%" position="relative" height="500px" mb="24px"> <Image - src={URL.createObjectURL(selectedPreview)} + src={previewUrl || ''} alt={selectedPreview.name} fill style={{ objectFit: 'cover', borderRadius: '8px', height: '100%', width: '100%' }} - onLoad={() => URL.revokeObjectURL(URL.createObjectURL(selectedPreview))} /> </Box> )} // Rest of the component...
60-60:⚠️ Potential issueClarify support for images vs. videos.
The placeholder text mentions "images and videos," but the
acceptprop only allows PNG, GIF, and JPEG image formats. Either update the text to mention only images or update theacceptprop to include video formats.- Click to browse or drag and drop images and videos. + Click to browse or drag and drop images.Or if you want to support videos:
accept={{ 'image/png': ['.png'], 'image/gif': ['.gif'], 'image/jpeg': ['.jpg', '.jpeg'], + 'video/mp4': ['.mp4'], + 'video/quicktime': ['.mov'] }}packages/components/modules/content-feed/web/NewContentPost/index.tsx (3)
41-48:⚠️ Potential issueAdd missing image keys to mutation input.
The form collects image files but the imageKeys array created is empty and not used in the mutation input. This will prevent images from being properly associated with the post.
const onSubmit = handleSubmit((data: ContentPostCreateForm) => { const uploadables: UploadableContentPostFiles = {} + const imageKeys: string[] = [] if (data.images) { data.images.forEach((image, index) => { uploadables[`images.${index}`] = image as File + imageKeys.push(`images.${index}`) }) }Also update the mutation input to include the image keys:
commitMutation({ variables: { input: { content: data.content, + images: imageKeys, }, },
50-68:⚠️ Potential issueAdd error handling for network failures.
The mutation error handling only covers API response errors but doesn't handle network failures or unexpected errors.
commitMutation({ variables: { input: { content: data.content, }, }, uploadables, + onError: (error) => { + sendToast('Failed to create post. Please try again.', { type: 'error' }); + }, onCompleted(response) { const errors = response.contentPostCreate?.errors if (errors) { sendToast('Something went wrong', { type: 'error' }) setFormRelayErrors(formReturn, errors) } else { reset({ content: '' }) sendToast('Post Created Successfully', { type: 'success' }) router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`) } }, })
71-73: 🛠️ Refactor suggestionAdd confirmation dialog for unsaved changes.
Users might accidentally lose their work when clicking cancel. Consider adding a confirmation dialog when there are unsaved changes.
const onCancel = useCallback(() => { + if (isDirty) { + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?'); + if (!confirmed) return; + } router.push('/posts') }, [router])
🧹 Nitpick comments (5)
packages/components/modules/content-feed/web/NewContentPost/types.ts (1)
10-10: Replace empty interface with type alias and removeIprefix.Empty interfaces provide no additional type information and should be replaced with type aliases. Additionally, the
Iprefix for interface names is not recommended in modern TypeScript style guides.-export interface INewContentPostProps {} +export type NewContentPostProps = {}🧰 Tools
🪛 Biome (1.9.4)
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (1)
52-52: Document the maxFileSize unit.The
maxFileSizeprop is set to15, but it's not clear what unit this represents (MB, KB, etc.). Add a comment or use a more descriptive variable name for clarity.- maxFileSize={15} + maxFileSize={15} {/* Size in MB */}For even better clarity, consider using a constant:
const MAX_FILE_SIZE_MB = 15; // ... <Dropzone maxFileSize={MAX_FILE_SIZE_MB} // ... />packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
65-65: Use more specific route path for post redirect.The line redirects to "/posts/" but the component itself appears to be using "/posts/" in line 72. Ensure consistent route path usage throughout the component.
- router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`) + router.push(`/content-post/${response.contentPostCreate?.contentPost?.node?.id}`)Or adjust both to use the same route pattern if they should be consistent.
packages/design-system/components/web/dropzones/Dropzone/index.tsx (2)
48-68: Refine type assertions in file handling logic.The code contains several
as anytype assertions that could be improved with more specific typing for better type safety.- const newFiles = [...((files || []) as File[]), ...resultingFiles] - setFiles(newFiles as any) - onSelect(newFiles as any) + const newFiles = [...((files || []) as (File | string)[]), ...resultingFiles] + setFiles(newFiles) + onSelect(newFiles)And similarly for the single file case:
- setFiles(file) - onSelect(file as any) + setFiles(file) + onSelect(file)Consider reviewing the type definitions to ensure they properly accommodate the types being used here, which would eliminate the need for these type assertions.
84-84: Improve type safety in file existence check.The condition
(files as [])?.lengthuses a type assertion that could be improved to be more type-safe.- const hasFiles = Boolean((!multiple && files) || (multiple && (files as [])?.length)) + const hasFiles = Boolean( + (!multiple && files) || + (multiple && Array.isArray(files) && files.length > 0) + )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/config/.eslintrc.js(1 hunks)packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/index.tsx(2 hunks)packages/design-system/components/web/dropzones/Dropzone/styled.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/types.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/components/modules/content-feed/web/ContentFeedImage/types.ts
🧰 Additional context used
🧬 Code Definitions (4)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (5)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
ContentPostCreateForm(1-4)UploadableContentPostFiles(6-8)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts (1)
useContentPostCreateMutation(25-26)packages/components/modules/content-feed/web/NewContentPost/constants.ts (2)
DEFAULT_CONTENT_POST_CREATE_FORM_VALUES(5-8)CONTENT_POST_CREATE_FORM_VALIDATION(10-13)packages/utils/functions/form/setFormRelayErrors/index.ts (1)
setFormRelayErrors(17-32)packages/components/modules/content-feed/web/NewContentPost/styled.tsx (3)
RootContainer(3-8)HeaderContainer(10-17)ButtonContainer(19-24)
packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx (2)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzonePreviewProps(60-65)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (2)
FileWrapper(51-74)RemoveFileButton(76-90)
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx (1)
packages/components/modules/content-feed/web/ContentFeedImage/types.ts (1)
IContentFeedImageProps(3-12)
packages/design-system/components/web/dropzones/Dropzone/index.tsx (3)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzoneProps(67-71)packages/utils/functions/file/getImageString/index.ts (1)
getImageString(3-6)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (5)
InputContainer(134-156)DropzoneContainer(9-31)AddFileWrapper(43-49)AddFileButton(33-41)ButtonContainer(158-164)
🪛 GitHub Actions: Main Workflow
packages/components/modules/content-feed/web/NewContentPost/index.tsx
[error] 97-97: Type 'UseFormReturn<{ content: string; images: never[]; }, any, undefined>' is not assignable to type 'UseFormReturn<{ content: string; images: File[]; }, any, undefined>'.
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/NewContentPost/types.ts
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
🔇 Additional comments (11)
packages/config/.eslintrc.js (1)
65-65:❓ Verification inconclusive
Consider the implications of disabling array index keys.
Disabling the
react/no-array-index-keyrule could lead to bugs when array items are reordered, removed, or added. Array indices don't provide stable identity for React elements, potentially causing unexpected behavior and performance issues.Consider using unique identifiers from your data instead of array indices, or document why this rule needs to be disabled in this specific case.
Run this script to find where array indices are being used as keys in React components:
🏁 Script executed:
#!/bin/bash # Search for array indices used as keys in React components rg -A 2 -B 2 'key=\{(index|i|idx)\}' --type tsx --type jsxLength of output: 88
Action Required: Verify Array Index Keys Usage
The ESLint config currently disables the
react/no-array-index-keyrule, which may lead to potential bugs if array indices are used as keys in your React components. Our initial verification command encountered a file type error with TSX files, so we couldn’t conclusively determine if array indices are being used in keys.Please re-run the following updated shell script to verify that array indices are not employed as keys in your
.tsxand.jsxfiles:#!/bin/bash # Search for array indices used as keys in React components in .tsx and .jsx files using glob filters rg -A 2 -B 2 'key=\{(index|i|idx)\}' -g "*.tsx" -g "*.jsx"If the search finds any instances where array indices are used as keys, consider refactoring the code to use stable, unique identifiers or provide clear documentation justifying why the rule is safely disabled in this context.
packages/design-system/components/web/dropzones/Dropzone/styled.tsx (3)
9-31: Well-structured scrollable container with custom scrollbar.The DropzoneContainer implementation provides a good user experience with custom scrollbar styling and proper overflow handling.
51-74: Great implementation of conditional styling based on theisMiniprop.The FileWrapper component correctly applies different styles based on the
isMiniprop, allowing for flexible layout options. The shouldForwardProp implementation properly filters custom props.
4-4: Import Button component for AddFileButton.Good addition of the Button import from @mui/material to support the new AddFileButton component.
packages/design-system/components/web/dropzones/Dropzone/index.tsx (3)
142-149: Good implementation of the file preview component.The use of the new DropzonePreview component for rendering file previews is a great improvement, providing consistent UI and handling for file previews.
132-142: Nice UX enhancement with the add file button in the file list.Including an add file button within the file list container creates a smooth user experience for adding more files.
153-164: Well-structured single file preview logic.The conditional rendering for single file mode is clear and maintains good separation of concerns.
packages/design-system/components/web/dropzones/Dropzone/types.ts (4)
6-11: Good extension of HTML attributes for InputContainerProps.Extending HTMLAttributes ensures that all standard HTML div attributes can be passed through to the component, improving flexibility.
32-58: Well-structured type definitions for different Dropzone use cases.The creation of separate interfaces for different Dropzone configurations (single/multiple, base64/file) with specific props is excellent. This provides strong type safety and clear API documentation for developers.
67-71: Good use of union types for flexibility.Using a union type to encompass all possible Dropzone configurations allows for a flexible API while maintaining type safety.
60-65: Well-defined DropzonePreviewProps interface.The DropzonePreviewProps interface clearly defines the requirements for the DropzonePreview component, making it easy to understand and use.
packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx
Outdated
Show resolved
Hide resolved
7df5bd9 to
fc78bbb
Compare
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: 1
♻️ Duplicate comments (4)
packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx (1)
26-26:⚠️ Potential issueFix URL object memory leak
The current implementation creates a new object URL and immediately revokes it on image load, which is incorrect. You should create the URL once and store it, then revoke that same URL after the image loads.
Implement a better approach using useEffect:
- onLoad={() => URL.revokeObjectURL(URL.createObjectURL(file as File))}const DropzonePreview: FC<DropzonePreviewProps> = ({ isMini = true, file, handleRemoveFile, onFileClick, }) => { const [objectUrl, setObjectUrl] = useState<string | undefined>(undefined) useEffect(() => { if (typeof file !== 'string') { const url = URL.createObjectURL(file) setObjectUrl(url) return () => URL.revokeObjectURL(url) } return undefined }, [file]) const imageUrl = typeof file === 'string' ? file : objectUrl return ( <FileWrapper isMini={isMini}> <button style={{ height: '100%' }} type="button" onClick={() => onFileClick?.(file)}> <img src={imageUrl} alt={typeof file !== 'string' ? (file as File).name : undefined} width={isMini ? 72 : undefined} height={isMini ? 72 : undefined} style={ isMini ? { objectFit: 'cover', borderRadius: '8px', height: '100%' } : { maxHeight: '200px', maxWidth: '100%' } } /> </button> <RemoveFileButton onClick={handleRemoveFile}> <CloseRoundedIcon sx={{ color: 'white', width: '20px', height: '20px' }} /> </RemoveFileButton> </FileWrapper> ) }packages/components/modules/content-feed/web/NewContentPost/index.tsx (2)
71-73: 🛠️ Refactor suggestionAdd confirmation dialog for unsaved changes
Users might accidentally lose their work when clicking cancel. Consider adding a confirmation dialog when there are unsaved changes.
const onCancel = useCallback(() => { + if (isDirty) { + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?'); + if (!confirmed) return; + } router.push('/posts') }, [router, isDirty])Don't forget to add
isDirtyto the dependency array of the useCallback.
50-69: 🛠️ Refactor suggestionAdd error handling for network failures
The mutation error handling only covers API response errors but doesn't handle network failures or unexpected errors.
commitMutation({ variables: { input: { content: data.content, }, }, uploadables, + onError: (error) => { + sendToast('Failed to create post. Please try again.', { type: 'error' }); + }, onCompleted(response) { const errors = response.contentPostCreate?.errors if (errors) { sendToast('Something went wrong', { type: 'error' }) setFormRelayErrors(formReturn, errors) } else { reset({ content: '' }) sendToast('Post Created Successfully', { type: 'success' }) router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`) } }, })packages/components/schema.graphql (1)
505-509: Consider adding validation constraints to the content field.The ContentPostCreateInput defines the content field as a required String, but it doesn't specify any validation constraints such as minimum length. Consider adding directives to ensure quality of content.
Without validation constraints, users could potentially submit empty content (just whitespace) or extremely short content that doesn't meet quality standards.
🧹 Nitpick comments (8)
packages/components/modules/content-feed/web/ContentFeed/types.ts (1)
1-1: Consider using a type alias instead of an empty interfaceThe current empty interface
IContentFeedPropsdoesn't define any properties. According to static analysis tools, an empty interface is equivalent to{}.-export interface IContentFeedProps {} +export type IContentFeedProps = Record<string, never>If you're planning to add properties to this interface in the future, consider adding a comment to explain that intention. Otherwise, a type alias provides the same functionality with clearer semantics.
🧰 Tools
🪛 Biome (1.9.4)
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts (1)
10-10: Consider using a type alias instead of an empty interfaceSimilar to
IContentFeedProps, this empty interface doesn't define any properties.-export interface INewContentPostProps {} +export type INewContentPostProps = Record<string, never>Either add properties if they're planned for the future or use a type alias for clearer semantics.
🧰 Tools
🪛 Biome (1.9.4)
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx (3)
16-18: Consider adding alt text for accessibilityWhen rendering an image, it's important to provide meaningful alt text for screen readers.
<img src={typeof file === 'string' ? file : URL.createObjectURL(file)} - alt={typeof file !== 'string' ? (file as File).name : undefined} + alt={typeof file !== 'string' ? `File preview: ${(file as File).name}` : 'File preview'} width={isMini ? 72 : undefined}
15-15: Add aria-label to improve button accessibilityThe button that wraps the image lacks descriptive text for screen readers. Adding an aria-label would improve accessibility.
- <button style={{ height: '100%' }} type="button" onClick={() => onFileClick?.(file)}> + <button + style={{ height: '100%' }} + type="button" + onClick={() => onFileClick?.(file)} + aria-label="View uploaded file" + >
8-34: Use TypeScript non-null assertion for file castingWhen you know the file isn't a string but TypeScript can't infer it, it's better to use a non-null assertion than a type assertion.
- alt={typeof file !== 'string' ? (file as File).name : undefined} + alt={typeof file !== 'string' ? file!.name : undefined}This applies to other instances of
file as Filein the component as well.packages/design-system/components/web/dropzones/Dropzone/index.tsx (2)
55-68: Implement more type-safe operations for multiple file handling.While the multiple file handling logic is well-structured, there are several type casts to
anythat could be more strongly typed for better type safety.- const newFiles = [...((files || []) as File[]), ...resultingFiles] - setFiles(newFiles as any) - onSelect(newFiles as any) + const newFiles = [...((files || []) as Array<typeof resultingFiles[0]>), ...resultingFiles] + setFiles(newFiles) + onSelect(newFiles)And similarly for the single file case, consider:
- setFiles(file) - onSelect(file as any) + setFiles(file) + onSelect(file)
153-164: Clean implementation of single file preview.The single file preview is well implemented within a Card component with proper removal functionality. Consider adding a loading state for larger files to improve user feedback.
packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)
76-90: Well-positioned remove button with appropriate styling.The RemoveFileButton is positioned absolutely in the top-right corner with appropriate styling and hover states. Consider adding a focus state for keyboard accessibility.
'&:hover': { backgroundColor: theme.palette.grey[800], }, + '&:focus': { + backgroundColor: theme.palette.grey[900], + outline: `2px solid ${theme.palette.primary.main}`, + },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/types.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/package.json(1 hunks)packages/components/schema.graphql(9 hunks)packages/config/.eslintrc.js(1 hunks)packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/index.tsx(2 hunks)packages/design-system/components/web/dropzones/Dropzone/styled.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/types.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
- packages/components/modules/content-feed/common/index.ts
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/components/modules/content-feed/web/ContentFeedImage/types.ts
- packages/components/modules/content-feed/web/index.ts
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
- packages/config/.eslintrc.js
- packages/components/package.json
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
🧰 Additional context used
🧬 Code Definitions (3)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (5)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
ContentPostCreateForm(1-4)UploadableContentPostFiles(6-8)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts (1)
useContentPostCreateMutation(25-26)packages/components/modules/content-feed/web/NewContentPost/constants.ts (2)
DEFAULT_CONTENT_POST_CREATE_FORM_VALUES(5-8)CONTENT_POST_CREATE_FORM_VALIDATION(10-13)packages/utils/functions/form/setFormRelayErrors/index.ts (1)
setFormRelayErrors(17-32)packages/components/modules/content-feed/web/NewContentPost/styled.tsx (3)
RootContainer(3-8)HeaderContainer(10-17)ButtonContainer(19-24)
packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx (2)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzonePreviewProps(60-65)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (2)
FileWrapper(51-74)RemoveFileButton(76-90)
packages/design-system/components/web/dropzones/Dropzone/index.tsx (3)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzoneProps(67-71)packages/utils/functions/file/getImageString/index.ts (1)
getImageString(3-6)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (4)
InputContainer(134-156)DropzoneContainer(9-31)AddFileWrapper(43-49)AddFileButton(33-41)
🪛 GitHub Actions: Main Workflow
packages/components/modules/content-feed/web/NewContentPost/index.tsx
[error] 97-97: Type 'UseFormReturn<{ content: string; images: never[]; }, any, undefined>' is not assignable to type 'UseFormReturn<{ content: string; images: File[]; }, any, undefined>'.
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/ContentFeed/types.ts
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
🔇 Additional comments (31)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
1-4: LGTM: Well-defined type for content post formThe interface properly defines the structure for creating a content post with appropriate types for each field.
6-8: LGTM: Well-defined type for uploadable filesThe type accurately defines the structure for mapping image indices to File or Blob objects, which will be useful for handling file uploads in the GraphQL mutation.
packages/components/modules/content-feed/web/NewContentPost/index.tsx (2)
23-111: LGTM: Well-structured component with proper form handlingThe component is well organized, uses appropriate hooks for form management, and handles mutations correctly. The UI is cleanly laid out with appropriate feedback for loading states and validation.
🧰 Tools
🪛 GitHub Actions: Main Workflow
[error] 97-97: Type 'UseFormReturn<{ content: string; images: never[]; }, any, undefined>' is not assignable to type 'UseFormReturn<{ content: string; images: File[]; }, any, undefined>'.
65-65:❓ Verification inconclusive
Ensure the route path matches your application's routing structure
The component navigates to
/posts/${id}after successful post creation. Verify that this path matches your application's routing structure.
🏁 Script executed:
#!/bin/bash # Check for route definitions or other components navigating to post pages rg -A 1 -B 1 "router\.push\(\s*[\'\"]\/posts\/" --type ts --type tsxLength of output: 188
Action: Verify the Route Configuration for Post Navigation
TheNewContentPost/index.tsxcomponent navigates withrouter.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`)after post creation. Please ensure that your application's routing configuration includes a corresponding
/posts/:idroute (or equivalent) that correctly handles the dynamic post ID. Given the earlier regex error when searching for similar route usages, a manual review of your router setup is advised to confirm consistency.packages/design-system/components/web/dropzones/Dropzone/index.tsx (7)
43-43: Improved state management with more specific type.The state is now properly typed as
DropzoneProps['storedImg']which will enforce type safety based on the component's configuration (single/multiple, base64/file).
48-53: Enhanced input validation with proper error handling.The validation now checks for both empty input and file size constraints with optional chaining, preventing potential null reference errors.
73-82: Clear separation of file removal logic for single vs multiple files.The handleRemove function now properly handles both single and multiple file scenarios, with index-based removal for multiple files and complete reset for single files.
84-84: Efficient helper for file presence detection.This boolean helper elegantly handles both single and multiple file scenarios to determine if files are present.
91-130: Improved empty state UI with better visual cues and typography.The empty state now provides clearer visual feedback with appropriate icons and typography hierarchy. The conditional rendering based on drag state also improves user experience.
131-151: Enhanced file preview for multiple files with add functionality.The multiple file preview now includes a convenient "add more" button and properly renders all files with their preview components. The horizontal scrolling container is a nice touch for managing many files.
172-183: Optional action buttons with conditional rendering.The action buttons are now conditionally rendered based on the includeActionButton prop, which provides flexibility in how the component is used. The remove button is also conditionally rendered based on file presence.
packages/design-system/components/web/dropzones/Dropzone/styled.tsx (5)
9-31: Well-implemented scrollable container with proper styling.The DropzoneContainer component provides an excellent horizontal scrolling experience with custom scrollbar styling. This enables a good UX when dealing with multiple file uploads.
33-41: Clean button styling with appropriate hover states.The AddFileButton styling is clean and consistent with appropriate dimensions and hover state changes that provide good visual feedback.
43-49: Visually distinct wrapper for the add file button.The dashed border styling provides a clear visual cue that this element is for adding new files, which improves the intuitiveness of the UI.
51-74: Flexible file wrapper with conditional styling based on size.The FileWrapper component uses conditional styling based on the isMini prop, which allows for different layouts in different contexts. This promotes reusability.
92-103: Clear container for file dropping with appropriate visual cues.The DropFilesContainer provides clear visual cues with its dashed border and centered content, making it evident that files can be dropped there.
packages/design-system/components/web/dropzones/Dropzone/types.ts (5)
1-1: Updated imports with proper TypeScript types.The imports now include CSSProperties, which is used for the new InputContainerStyle prop, as well as HTMLAttributes for proper typing of component props.
6-11: Enhanced type safety with proper HTML attributes inheritance.The InputContainerProps now correctly extends HTMLAttributes, ensuring that all standard HTML div attributes are available to the component.
17-30: Well-structured base props interface with clear optional properties.The BaseDropzoneProps interface is well structured with clear property definitions. The onRemove method now includes an optional index parameter to support multiple file removal.
32-66: Comprehensive type definitions for different dropzone configurations.The specialized interfaces for different dropzone configurations (single/multiple, base64/file) ensure type safety throughout the component. Each interface clearly defines the expected types for storedImg and onSelect.
67-71: Clean union type for all dropzone configurations.The DropzoneProps union type elegantly combines all possible configurations, making the component API flexible while maintaining type safety.
packages/components/schema.graphql (10)
204-204: Added support for tracking added participants in chat room updates.The
addedParticipantsfield is now consistently included in bothChatRoomOnRoomUpdateandChatRoomUpdatePayloadtypes, allowing for better tracking of participant changes.Also applies to: 328-328
484-493: New ContentPost type with appropriate fields.The ContentPost type properly includes essential fields like user, profile, content, and images. It implements the Node interface for compatibility with the Relay specification.
495-503: Well-structured pagination support for ContentPost.The ContentPostConnection type includes standard Relay pagination fields (pageInfo, edges, totalCount, edgeCount) ensuring proper pagination support for content posts.
511-517: Appropriate response payload for content post creation.The ContentPostCreatePayload follows the standard pattern for mutations with errors, debug info, the created object, and clientMutationId for optimistic updates.
528-535: Well-structured ContentPostImage type for managing post images.The ContentPostImage type appropriately implements the Node interface and includes essential fields for referencing both the image string and its parent post.
573-584: Generic DeleteNodeInput/Payload for reusable deletion operations.The introduction of generic DeleteNodeInput and DeleteNodePayload types promotes code reuse for deletion operations across different entity types, as seen in the pageDelete mutation.
815-815: New content post creation mutation.The contentPostCreate mutation is properly added to the Mutation type with appropriate input and payload types.
822-822: Switched to generic DeleteNodeInput for page deletion.The pageDelete mutation now uses the generic DeleteNodeInput type, promoting consistency across deletion operations.
1489-1489: ContentPosts query for retrieving posts with pagination.The contentPosts query is properly added to the Query type with standard pagination parameters.
1268-1268: Added contentPosts field to Profile type.The Profile type now includes a contentPosts field with standard pagination parameters, enabling retrieval of posts by profile.
fc78bbb to
742b17e
Compare
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: 1
♻️ Duplicate comments (3)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (2)
50-69: 🛠️ Refactor suggestionAdd error handling for network failures.
While the mutation handles API errors, it lacks handling for network failures or unexpected errors.
commitMutation({ variables: { input: { content: data.content, }, }, uploadables, + onError: (error) => { + sendToast('Failed to create post. Please try again.', { type: 'error' }) + }, onCompleted(response) { const errors = response.contentPostCreate?.errors if (errors) { sendToast('Something went wrong', { type: 'error' }) setFormRelayErrors(formReturn, errors) } else { reset({ content: '' }) sendToast('Post Created Successfully', { type: 'success' }) router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`) } }, })
71-73: 🛠️ Refactor suggestionAdd confirmation dialog for unsaved changes.
Users might accidentally lose their work when clicking cancel. Add a confirmation dialog when there are unsaved changes.
const onCancel = useCallback(() => { + if (isDirty) { + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?') + if (!confirmed) return + } router.push('/posts') }, [router, isDirty])packages/components/schema.graphql (1)
505-509: 🛠️ Refactor suggestionEnhance content field validation.
The
contentfield is required but doesn't have additional validation constraints. Consider implementing schema-level validation to ensure content quality.You could implement validation using a custom constraint directive:
input ContentPostCreateInput { content: String! @constraint(minLength: 1) images: [String] clientMutationId: String }
🧹 Nitpick comments (9)
packages/components/modules/content-feed/web/ContentFeed/types.ts (1)
1-1: Consider enhancing the empty interface or converting to a type alias.The
IContentFeedPropsinterface is currently empty, which is equivalent to{}. According to TypeScript best practices:
- An empty interface doesn't provide any type safety or documentation benefits
- If no props are intended, you could use a type alias instead:
type ContentFeedProps = {}- If props will be added in the future, consider adding documentation comments explaining the intended usage
-export interface IContentFeedProps {} +/** + * Props for the ContentFeed component. + * @todo Add necessary props as component requirements develop + */ +export type ContentFeedProps = {};🧰 Tools
🪛 Biome (1.9.4)
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts (1)
10-10: Consider using a type alias instead of an empty interface.An empty interface is equivalent to
{}and doesn't provide any additional type information.-export interface INewContentPostProps {} +export type NewContentPostProps = {}🧰 Tools
🪛 Biome (1.9.4)
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
65-65: Ensure path consistency across the component.The route path used in
router.push()should be consistent with the one used inonCancel().- router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`) + router.push(`/feed/${response.contentPostCreate?.contentPost?.node?.id}`)Or alternatively, update the
onCancelfunction:const onCancel = useCallback(() => { - router.push('/posts') + router.push('/feed') }, [router])packages/design-system/components/web/dropzones/Dropzone/index.tsx (3)
59-62: Improve type safety for file array operations.The current implementation uses type assertions (
as any) which reduce type safety. Consider improving the types.- const newFiles = [...((files || []) as File[]), ...resultingFiles] - setFiles(newFiles as any) - onSelect(newFiles as any) + const currentFiles = multiple && Array.isArray(files) ? files : [] + const newFiles = [...currentFiles, ...resultingFiles] as DropzoneProps['storedImg'] + setFiles(newFiles) + onSelect(newFiles)
84-84: Consider a more explicit check for array files.The current check might cause unexpected behavior if
filesis truthy but not an array in multiple mode.- const hasFiles = Boolean((!multiple && files) || (multiple && (files as [])?.length)) + const hasFiles = multiple ? Array.isArray(files) && files.length > 0 : Boolean(files)
142-149: Add key type checking for array map operations.When mapping over file arrays, ensure proper type checking to avoid unexpected behavior.
- {(files as unknown as [])?.map((file, index) => ( + {Array.isArray(files) && files.map((file, index) => ( <DropzonePreview onFileClick={(selectedFile) => onFileClick?.(selectedFile, index)} file={file} key={`${(file as File)?.name}_${index}`} handleRemoveFile={() => handleRemove(index)} /> ))}packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)
86-89: Consider different hover styles for RemoveFileButton.The current hover styling doesn't change the background color, which might not provide enough visual feedback to users.
backgroundColor: theme.palette.grey[800], '&:hover': { - backgroundColor: theme.palette.grey[800], + backgroundColor: theme.palette.grey[900], + opacity: 0.9, },packages/components/schema.graphql (2)
484-493: ContentPost type looks good but could benefit from additional fields.The ContentPost type implements Node and includes essential fields for user ownership, content, and images. Consider adding timestamps (created/modified) and potential interaction fields (like comments, reactions) to make the content model more complete.
529-534: Consider enhancing ContentPostImage with metadata.The ContentPostImage type currently only stores the image URL and relationship to post. Consider adding metadata fields such as dimensions, file size, alt text, or caption to improve accessibility and user experience.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/types.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/package.json(1 hunks)packages/components/schema.graphql(9 hunks)packages/config/.eslintrc.js(1 hunks)packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/index.tsx(2 hunks)packages/design-system/components/web/dropzones/Dropzone/styled.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/types.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
- packages/components/modules/content-feed/web/ContentFeedImage/types.ts
- packages/config/.eslintrc.js
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/components/modules/content-feed/common/index.ts
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
- packages/components/modules/content-feed/web/index.ts
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx
- packages/components/package.json
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
🧰 Additional context used
🧬 Code Definitions (1)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (5)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
ContentPostCreateForm(1-4)UploadableContentPostFiles(6-8)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts (1)
useContentPostCreateMutation(25-26)packages/components/modules/content-feed/web/NewContentPost/constants.ts (2)
DEFAULT_CONTENT_POST_CREATE_FORM_VALUES(5-8)CONTENT_POST_CREATE_FORM_VALIDATION(10-13)packages/utils/functions/form/setFormRelayErrors/index.ts (1)
setFormRelayErrors(17-32)packages/components/modules/content-feed/web/NewContentPost/styled.tsx (3)
RootContainer(3-8)HeaderContainer(10-17)ButtonContainer(19-24)
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/ContentFeed/types.ts
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
🔇 Additional comments (11)
packages/design-system/components/web/dropzones/Dropzone/styled.tsx (2)
9-31: LGTM: The DropzoneContainer component looks well-implemented.The component has good styling and proper prop forwarding for drag-and-drop state handling.
51-74: Good implementation of conditional styling based on isMini prop.The FileWrapper component properly handles different display modes based on the isMini prop, providing good flexibility.
packages/design-system/components/web/dropzones/Dropzone/types.ts (5)
6-11: Good improvement on the InputContainerProps interface.Extending HTMLAttributes allows the component to accept all standard HTML div attributes, making it more flexible and better aligned with React patterns.
17-30: Well-structured BaseDropzoneProps interface with enhanced flexibility.The modifications to this interface improve the component's usability by:
- Making the
onRemovemethod more flexible with an optional index parameter- Adding support for customizing the title with string or JSX
- Including configuration options for action buttons, styling, and file interactions
These changes enable more diverse use cases while maintaining a clear component contract.
32-59: Good type safety implementation with specialized interfaces.Creating separate interfaces for different file handling scenarios (single/multiple files, base64/file objects) provides strong type safety and ensures proper prop validation at compile time. This approach makes the component API clearer and helps prevent runtime errors.
60-65: Well-designed preview component interface.The DropzonePreviewProps interface provides a clean contract for the file preview functionality, with appropriate handlers for removal and click actions. The optional isMini property allows for rendering flexibility.
67-71: Good use of union types for comprehensive prop definition.Using a union type for DropzoneProps allows the component to accept different combinations of props based on use case, while maintaining strong type checking. This pattern makes the component both flexible and type-safe.
packages/components/schema.graphql (4)
204-204: Good improvement to chat room update notifications.Adding the
addedParticipantsfield to both ChatRoomOnRoomUpdate and ChatRoomUpdatePayload types creates symmetry with the existingremovedParticipantsfield, providing complete information about participant changes during room updates.Also applies to: 328-328
573-584: Good implementation of generic node deletion.The DeleteNodeInput and DeleteNodePayload types provide a reusable pattern for node deletion operations, improving schema consistency. The implementation of these types alongside updating the pageDelete mutation to use them (line 822) demonstrates good GraphQL schema design.
815-815: Proper implementation of content post mutation and query.The contentPostCreate mutation and contentPosts query are correctly placed in their respective types, with appropriate inputs and return types. This matches GraphQL best practices where queries fetch data and mutations modify data.
Also applies to: 1489-1489
1268-1268: Good integration of content posts with Profile type.Adding the contentPosts field to the Profile type creates a proper relationship between users and their content, enabling efficient querying of a user's posts. The pagination parameters are also consistently implemented.
742b17e to
889c309
Compare
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: 1
♻️ Duplicate comments (3)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (2)
71-73: 🛠️ Refactor suggestionAdd confirmation dialog for unsaved changes
Users might accidentally lose their work when clicking cancel. Consider adding a confirmation dialog when there are unsaved changes.
const onCancel = useCallback(() => { + if (isDirty) { + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?') + if (!confirmed) return + } router.push('/posts') }, [router, isDirty])
41-69: 🛠️ Refactor suggestionAdd missing error handler for network failures
The mutation implementation handles API errors but lacks error handling for network failures or unexpected errors.
commitMutation({ variables: { input: { content: data.content, }, }, uploadables, + onError: (error) => { + sendToast('Failed to create post. Please try again.', { type: 'error' }) + }, onCompleted(response) { const errors = response.contentPostCreate?.errors if (errors) { sendToast('Something went wrong', { type: 'error' }) setFormRelayErrors(formReturn, errors) } else { reset({ content: '' }) sendToast('Post Created Successfully', { type: 'success' }) router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`) } }, })packages/components/schema.graphql (1)
505-509: Implement validation constraints for the content field.The
ContentPostCreateInputdoesn't specify any validation constraints on the content field, which could potentially allow empty or invalid content.Does GraphQL support field validation directives like minLength or pattern?
🧹 Nitpick comments (8)
packages/components/modules/content-feed/web/ContentFeed/types.ts (1)
1-1: Consider using a type alias for empty interface or adding planned props.The current empty interface
IContentFeedPropsdoesn't provide any valuable type information. While this is likely a placeholder for future props, TypeScript best practices suggest either:
- Add the props you expect the component to need, or
- Use a type alias instead of an empty interface if no props are needed yet
-export interface IContentFeedProps {} +// Option 1: If you plan to add props later but don't have any yet +export interface IContentFeedProps { + // TODO: Add props as needed for the ContentFeed component +} + +// Option 2: If you're sure no props are needed +export type IContentFeedProps = Record<string, never>;🧰 Tools
🪛 Biome (1.9.4)
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts (1)
10-10: Replace empty interface with type aliasAn empty interface doesn't provide any type safety benefits and is equivalent to
{}.-export interface INewContentPostProps {} +export type NewContentPostProps = Record<string, never>Alternatively, if the component doesn't need props, you could simply use React.FC without a generic type parameter.
🧰 Tools
🪛 Biome (1.9.4)
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/design-system/components/web/dropzones/Dropzone/index.tsx (3)
73-82: Add type for index parameter in handleRemoveThe
handleRemovefunction should have its parameter typed for better type safety.-const handleRemove = (index?: number) => { +const handleRemove = (index?: number): void => {
142-149: Improve key generation for file listUsing both filename and index as a key could cause issues if the same file is uploaded twice, as the name would be identical. Consider using a more unique identifier.
- key={`${(file as File)?.name}_${index}`} + key={`file_${index}_${Date.now()}`}Alternatively, if you're using modern browsers, you could use:
- key={`${(file as File)?.name}_${index}`} + key={`file_${index}_${(file as File)?.name}_${(file as File)?.lastModified || Date.now()}`}
84-164: Minimize type assertionsThere are several type assertions using
asthat could be avoided with better typing. Consider refining your types to reduce the need for these assertions.Examples:
- const newFiles = [...((files || []) as File[]), ...resultingFiles] - setFiles(newFiles as any) - onSelect(newFiles as any) + const newFiles = [...((files || []) as Array<File | string>), ...resultingFiles] + setFiles(newFiles) + onSelect(newFiles)And:
- {(files as unknown as [])?.map((file, index) => ( + {(Array.isArray(files) ? files : [])?.map((file, index) => (packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)
51-74: Consider adding accessibility attributes to the FileWrapper.The FileWrapper component provides good conditional styling based on the
isMiniprop. For better accessibility, consider adding appropriate ARIA attributes when it contains interactive elements.export const FileWrapper = styled(Box, { shouldForwardProp: (prop) => prop !== 'isMini', })<{ isMini: boolean }>(({ theme, isMini }) => isMini ? { position: 'relative', flexShrink: 0, width: '80px', height: '80px', border: `2px solid ${theme.palette.grey[200]}`, borderRadius: '12px', padding: '4px', display: 'inline-block', + // Add role or aria attributes as needed based on how this component is used + // For example, if it's clickable: role: 'button', '&:hover': { border: `2px solid black`, }, } : { padding: theme.spacing(2), display: 'flex', flexDirection: 'column', alignItems: 'center', }, )packages/design-system/components/web/dropzones/Dropzone/types.ts (2)
17-30: Add JSDoc comments to the BaseDropzoneProps interface for better documentation.While the interface is well-structured with the additional properties, adding JSDoc comments would make it more maintainable and understandable for other developers.
+/** + * Base properties for all Dropzone variants + */ export interface BaseDropzoneProps { /** Accepted file types */ accept: Accept + /** Callback when a file is removed, optionally with file index */ onRemove: (index?: number) => void + /** Text for the action button */ actionText?: string + /** Secondary descriptive text */ subTitle?: string + /** Maximum file size in bytes */ maxFileSize?: number + /** Additional options for react-dropzone */ DropzoneOptions?: Partial<DropzoneOptions> + /** Input element props */ InputProps?: InputHTMLAttributes<HTMLInputElement> + /** Title or header element */ title?: string | JSX.Element + /** Whether to include an action button */ includeActionButton: boolean + /** Custom styles for the input container */ InputContainerStyle?: CSSProperties + /** Whether multiple files can be selected */ multiple?: boolean + /** Callback when a file is clicked */ onFileClick?: (selectedFile: string | File | Blob, index?: number) => void }
60-65: Consider adding a more descriptive JSDoc for DropzonePreviewProps.Adding JSDoc comments would make the purpose of this interface clearer, especially the distinction between its properties and those in BaseDropzoneProps.
+/** + * Props for the preview component that displays individual files + * within the Dropzone + */ export interface DropzonePreviewProps { + /** Whether to display in mini/compact mode */ isMini?: boolean + /** Callback to remove the displayed file */ handleRemoveFile: () => void + /** The file to be displayed */ file: string | File | Blob + /** Optional callback when file is clicked */ onFileClick?: (selectedFile: string | File | Blob) => void }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/types.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/package.json(1 hunks)packages/components/schema.graphql(9 hunks)packages/config/.eslintrc.js(1 hunks)packages/design-system/components/web/dropzones/Dropzone/__storybook__/stories.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/index.tsx(2 hunks)packages/design-system/components/web/dropzones/Dropzone/styled.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/types.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
- packages/components/modules/content-feed/common/index.ts
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
- packages/config/.eslintrc.js
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/components/modules/content-feed/web/ContentFeedImage/types.ts
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
- packages/components/modules/content-feed/web/index.ts
- packages/components/package.json
🧰 Additional context used
🧬 Code Definitions (1)
packages/design-system/components/web/dropzones/Dropzone/index.tsx (4)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzoneProps(67-71)packages/utils/functions/file/getImageString/index.ts (1)
getImageString(3-6)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (5)
InputContainer(134-156)DropzoneContainer(9-31)AddFileWrapper(43-49)AddFileButton(33-41)ButtonContainer(158-164)packages/components/modules/content-feed/web/NewContentPost/styled.tsx (1)
ButtonContainer(19-24)
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/ContentFeed/types.ts
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: Component Test Packages
- GitHub Check: Build and Lint Packages
🔇 Additional comments (24)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
1-4: LGTM - Content post form type looks goodThe
ContentPostCreateForminterface is well-defined with a requiredcontentstring and optionalimagesarray of Files.
6-8: LGTM - Well-typed upload files structureThe
UploadableContentPostFilestype cleverly uses template literal types to define dynamically named properties for image uploads. This ensures type safety for the file upload mechanism.packages/design-system/components/web/dropzones/Dropzone/__storybook__/stories.tsx (1)
22-23: LGTM - New Dropzone story props aligned with component enhancementsThe addition of
multiple: falseandasBase64: trueprops to the Default story accurately reflects the component's updated capabilities. This provides a good example of the component's default configuration.packages/components/modules/content-feed/web/NewContentPost/index.tsx (3)
23-40: LGTM - Component and form setup properly implementedThe component is well-structured with appropriate hooks for routing, notifications, and mutation. The form is properly set up with default values, Zod resolver for validation, and blur mode for validation timing. Good practice extracting form state properties needed.
65-65:✅ Verification successful
Verify URL consistency in routing
The success handler navigates to
/posts/${id}while the cancel handler navigates to/posts. Verify that these URL paths are consistent with your application's routing structure.
🏁 Script executed:
#!/bin/bash # Check the application's routing configuration to verify consistent URL patterns # Search for route declarations or navigation to /posts rg -A 3 -B 3 --no-filename "/posts(\s|\/|\"|'|$|/\$)" --glob "*.{js,jsx,ts,tsx}" # Search for other instances of content post routing patterns rg -A 3 -B 3 --no-filename "content-post|contentPost" --glob "*.{js,jsx,ts,tsx}" | grep -i "route\|link\|push\|href"Length of output: 386
Routing Paths Verified
- The success handler uses a dynamic route (
/posts/${postId}) to navigate to individual post details, and the cancel handler routes back to the general posts listing (/posts).- Our search confirms that the dynamic routing pattern is used as expected in the success handler. There were no conflicting instances found that would suggest a deviation from the intended routing structure.
Overall, the URL paths appear consistent with the application's design. Please double-check that the dynamic route
/posts/[id]is defined in your routing configuration to ensure this behavior is fully supported.
97-97:❓ Verification inconclusive
Ensure proper type compatibility with ContentFeedImage
The form passed to ContentFeedImage must have the correct type to avoid TypeScript errors.
🌐 Web query:
How to properly pass react-hook-form's UseFormReturn to child components in TypeScript?💡 Result:
To pass
UseFormReturnto child components in TypeScript with React Hook Form, you can use either render props with explicit type generics or leverage Form Context for deep nesting. Here are two recommended approaches:1. Render Props Pattern with Type Generics
Create a parent form component that passes
UseFormReturnmethods explicitly via render props:import { UseFormReturn, useForm } from "react-hook-form"; type FormValues = { firstName: string; lastName: string; }; type FormProps = { children: (methods: UseFormReturn<FormValues>) => React.ReactNode; }; const Form = ({ children }: FormProps) => { const methods = useForm<FormValues>(); return <form onSubmit={methods.handleSubmit(console.log)}>{children(methods)}</form>; }; // Usage const ParentComponent = () => ( <Form> {(methods) => ( <> <input {...methods.register("firstName")} /> <input {...methods.register("lastName")} /> </> )} </Form> );This ensures type safety through the
FormValuesgeneric and avoids prop drilling[1][7].
2. Form Context for Deep Nesting
Use
FormProvideranduseFormContextfor complex component hierarchies:import { FormProvider, useForm, useFormContext } from "react-hook-form"; type FormValues = { email: string; password: string; }; const ParentForm = () => { const methods = useForm<FormValues>(); return ( <FormProvider {...methods}> <ChildComponent /> </FormProvider> ); }; const ChildComponent = () => { const { register } = useFormContext<FormValues>(); return <input {...register("email")} />; };Create a typed context hook for reusability:
export const useTypedFormContext = () => useFormContext<FormValues>();This approach maintains type safety across deeply nested components[1][9].
Key Considerations
- Type Definitions: Always explicitly type
useFormand context hooks with your form's value interface- Component Isolation: Child components should only consume needed methods (
register,control) rather than entireUseFormReturnobject- Validation Integration: When using validation resolvers, ensure types align with your form schema[7][10]
Both patterns leverage TypeScript's generic type system to preserve type safety while providing flexibility in component architecture. For complex forms with reusable input components, the context approach is generally preferred[2][9].
Citations:
- 1: https://react-hook-form.com/ts
- 2: https://orizens.com/blog/best_practices_for_developing_complex_form-based_apps_with_react_hook_form_and_typescript_support/
- 3: https://react-hook-form.com/advanced-usage
- 4: https://www.react-hook-form.com/get-started/
- 5: https://dev.to/mohammadfaisal/how-to-use-react-hook-form-with-typescript-3cga
- 6: https://blog.logrocket.com/react-hook-form-complete-guide/
- 7: https://dev.to/pranavb6/simple-react-hook-form-v7-tutorial-with-typescript-j78
- 8: https://www.react-hook-form.com/advanced-usage/
- 9: https://react-hook-form.com/docs/useformcontext
- 10: https://react-hook-form.com/docs/useform
Action: Verify Correct Type Assignment for ContentFeedImage’s Form Prop
Ensure that the form passed into
ContentFeedImageis explicitly typed with react-hook-form’sUseFormReturn(using the appropriate generic for your form values) so that TypeScript type-checking is not compromised. For example:
- In the parent component, initialize your form with an explicit type:
const formReturn = useForm<MyFormValues>();- In the
ContentFeedImagecomponent, update the prop definition to expect the correctly typed form:interface ContentFeedImageProps { form: UseFormReturn<MyFormValues>; }If your component hierarchy is more complex, consider using React Hook Form’s
FormProvideranduseFormContextfor deeper nesting.packages/design-system/components/web/dropzones/Dropzone/index.tsx (4)
31-41: LGTM - New props enhance component flexibilityThe added props (
includeActionButton,title,InputContainerStyle,multiple,asBase64,onFileClick) with appropriate defaults improve the component's flexibility and usability. The default values align well with the common use case.
43-43: Improved state typingThe state typing has been improved to use
DropzoneProps['storedImg'], which ensures better type safety and consistency.
48-69: LGTM - Enhanced file handling logicThe improved file handling now gracefully manages both single and multiple files with appropriate error handling and file size validation. The async/await approach for processing files is clean and maintainable.
172-183: LGTM - Good conditional rendering of action buttonsThe conditional rendering of action buttons based on the
includeActionButtonprop and file state is well implemented. This provides good flexibility for different usage scenarios.packages/design-system/components/web/dropzones/Dropzone/styled.tsx (4)
4-4: Good addition of Button to imports.The addition of the Button component from @mui/material supports the newly created AddFileButton styled component.
9-31: Well-implemented scrollable container with custom scrollbar styling.The DropzoneContainer component provides a clean, horizontally scrollable area with thoughtfully styled scrollbars, respecting the application's theme palette.
33-41: Good design for the AddFileButton component.The AddFileButton styling is consistent with Material UI patterns and uses theme colors appropriately for hover states.
92-103: Good implementation of the DropFilesContainer.The DropFilesContainer has appropriate styling for a file drop area with clear visual indicators.
packages/design-system/components/web/dropzones/Dropzone/types.ts (4)
1-1: Good import organization.The updated imports now include all necessary React types, improving code readability and type safety.
6-11: Great improvement to InputContainerProps by extending HTML attributes.Extending HTMLAttributes ensures that all standard HTML div attributes can be passed to the InputContainer component, enhancing component flexibility and developer experience.
32-37: Well-defined type variants for different Dropzone use cases.The implementation of specific interfaces for different Dropzone configurations (single/multiple and base64/file) is a good approach to type safety. It ensures the correct props are provided for each use case.
Also applies to: 39-44, 46-51, 53-58
67-71: Good use of union type for DropzoneProps.Creating a union type that encompasses all possible Dropzone configurations is a clean way to handle the type variations while ensuring type safety.
packages/components/schema.graphql (6)
203-204: Good enhancement to ChatRoom types with addedParticipants field.Adding the
addedParticipantsfield to bothChatRoomOnRoomUpdateandChatRoomUpdatePayloadtypes provides symmetry with the existingremovedParticipantsfield, enhancing the API's completeness.Also applies to: 327-329
484-493: ContentPost type is well-structured.The
ContentPosttype properly implements theNodeinterface and includes appropriate fields for user, profile, content, and images with pagination support.
528-535: ContentPostImage type is appropriately defined.The
ContentPostImagetype correctly implements theNodeinterface and establishes a clear relationship with its parentContentPostvia thepostfield.
573-584: Good addition of generic DeleteNodeInput and DeleteNodePayload.Adding these generic node deletion types promotes reusability across the schema and reduces duplication. This is a good pattern for maintaining consistency in the API.
815-815: Properly separated mutation and query operations.The
contentPostCreatemutation andcontentPostsquery are correctly placed in their respective operation types, following GraphQL best practices for separating read and write operations.Also applies to: 1489-1489
1268-1268: Well-integrated contentPosts field in the Profile type.The addition of the
contentPostsfield to theProfiletype creates a logical relationship between profiles and their posts, with proper pagination support.
889c309 to
2d8235d
Compare
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 (5)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (3)
97-97: Ensure proper type compatibility with ContentFeedImage.The form passed to ContentFeedImage must have the correct type to avoid TypeScript errors. Make sure that DEFAULT_CONTENT_POST_CREATE_FORM_VALUES in your constants.ts file correctly initializes the images array.
#!/bin/bash # Check the ContentFeedImage component props and constants file cat $(rg -l "ContentFeedImage" --type ts | head -n 1) cat $(rg -l "DEFAULT_CONTENT_POST_CREATE_FORM_VALUES" --type ts | head -n 1)
50-69: 🛠️ Refactor suggestionAdd error handling for network failures.
The mutation doesn't include an onError handler for network failures or unexpected errors. This can lead to a poor user experience if the network request fails.
commitMutation({ variables: { input: { content: data.content, }, }, uploadables, + onError: (error) => { + sendToast('Failed to create post. Please try again.', { type: 'error' }) + }, onCompleted(response) { const errors = response.contentPostCreate?.errors if (errors) { sendToast('Something went wrong', { type: 'error' }) setFormRelayErrors(formReturn, errors) } else { reset({ content: '' }) sendToast('Post Created Successfully', { type: 'success' }) router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`) } }, })
71-73: 🛠️ Refactor suggestionAdd confirmation dialog for unsaved changes.
Users might accidentally lose their work when clicking cancel. Consider adding a confirmation dialog when there are unsaved changes.
const onCancel = useCallback(() => { + if (isDirty) { + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?') + if (!confirmed) return + } router.push('/posts') }, [router, isDirty])packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)
76-90: 🛠️ Refactor suggestionAdd aria-label to RemoveFileButton for better accessibility
The
RemoveFileButtonis missing an accessibility label, which would help screen reader users understand its purpose.export const RemoveFileButton = styled('button')(({ theme }) => ({ position: 'absolute', top: '4px', right: '4px', width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '0 6px 0 6px', backgroundColor: theme.palette.grey[800], '&:hover': { backgroundColor: theme.palette.grey[800], }, + // The aria-label should be added in the component that uses this styled component + // e.g., <RemoveFileButton aria-label="Remove file" /> }))packages/components/schema.graphql (1)
505-509: Enhance Content Field ValidationThe current
ContentPostCreateInputrequires thecontentfield, but it doesn't enforce additional constraints (e.g., minimum length or pattern matching). Note that while GraphQL doesn't include built‑in validation rules beyond type requirements, you can achieve this by leveraging custom validation directives (such as via thegraphql-constraint-directivelibrary) or implementing equivalent server‐side checks.
- File:
packages/components/schema.graphql(Lines 505–509)
Suggestion: Consider adding a directive like@constraint(minLength: 1)to thecontentfield (or another appropriate constraint) so that invalid or empty content can be caught early.Does GraphQL support field validation directives like minLength or pattern?
🧹 Nitpick comments (8)
packages/components/modules/content-feed/web/ContentFeed/types.ts (1)
1-1: Consider using a type alias instead of an empty interface.An empty interface is equivalent to
{}and provides no type constraints. For components with no props, it's better to use a type alias withRecord<string, never>to explicitly indicate that no props are expected:-export interface IContentFeedProps {} +export type IContentFeedProps = Record<string, never>;If you're planning to add props in the future, consider adding placeholder properties with comments to document the intended use.
🧰 Tools
🪛 Biome (1.9.4)
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/styled.tsx (1)
3-8: Consider responsive width instead of fixed width.Using a fixed width of 600px may cause display issues on mobile devices or smaller screens. Consider making the component responsive by using a percentage width with maxWidth.
export const RootContainer = styled(Box)(() => ({ display: 'flex', - width: '600px', + width: '100%', + maxWidth: '600px', alignSelf: 'center', flexDirection: 'column', }))packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx (1)
29-40: Improve button accessibility.The main preview button lacks accessibility attributes. Adding an aria-label would improve screen reader support.
- <button style={{ height: '100%' }} type="button" onClick={() => onFileClick?.(file)}> + <button + style={{ height: '100%' }} + type="button" + onClick={() => onFileClick?.(file)} + aria-label={typeof file !== 'string' ? `Preview ${(file as File).name}` : 'Preview image'} + >packages/design-system/components/web/dropzones/Dropzone/index.tsx (2)
73-82: Consider adding a return type to handleRemoveThe
handleRemovefunction nicely handles both single and multiple file scenarios, but it would benefit from an explicit return type for better type safety.- const handleRemove = (index?: number) => { + const handleRemove = (index?: number): void => { if (multiple) { const updatedFiles = (files as File[])?.filter((file, i) => i !== index) setFiles(updatedFiles) onRemove(index) } else { setFiles(undefined) onRemove() } }
157-162: Consider using optional chaining for onFileClickWhen passing
onFileClickto theDropzonePreview, consider using optional chaining to ensure it's only called if defined.<DropzonePreview - onFileClick={onFileClick} + onFileClick={file => onFileClick?.(file)} isMini={false} file={files as string | File} handleRemoveFile={() => handleRemove()} />packages/components/schema.graphql (3)
528-535: Consider adding more metadata to ContentPostImageThe
ContentPostImagetype has basic fields but could benefit from additional metadata like:
- Upload timestamp
- Size information
- Alt text for accessibility
- Order/position for multiple images in a post
These additions would enhance the usability and accessibility of the content posts feature.
573-584: Consider adding optional reason field to DeleteNodeInputThe
DeleteNodeInputtype is a good generic solution for deletion operations, but it could be enhanced with an optional reason field to capture why an item is being deleted. This information could be valuable for logging, analytics, or user notifications.input DeleteNodeInput { id: ID! + reason: String clientMutationId: String }
1268-1268: Ensure consistent parameter ordering in Profile.contentPostsThe
contentPostsfield in theProfiletype uses a different parameter ordering than the root query version (first/last before offset/after/before). While this doesn't affect functionality, maintaining consistent parameter ordering across similar fields makes the schema more predictable for consumers.- contentPosts(first: Int, last: Int, offset: Int, after: String, before: String): ContentPostConnection! + contentPosts(offset: Int, before: String, after: String, first: Int, last: Int): ContentPostConnection!
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/types.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/package.json(1 hunks)packages/components/schema.graphql(9 hunks)packages/config/.eslintrc.js(1 hunks)packages/design-system/components/web/dropzones/Dropzone/__storybook__/stories.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/index.tsx(2 hunks)packages/design-system/components/web/dropzones/Dropzone/styled.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/types.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
- packages/components/modules/content-feed/web/ContentFeedImage/types.ts
- packages/components/modules/content-feed/common/index.ts
- packages/config/.eslintrc.js
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
- packages/design-system/components/web/dropzones/Dropzone/storybook/stories.tsx
- packages/components/modules/content-feed/web/index.ts
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/components/package.json
- packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
- packages/design-system/components/web/dropzones/Dropzone/types.ts
🧰 Additional context used
🧬 Code Definitions (2)
packages/components/modules/content-feed/web/NewContentPost/styled.tsx (2)
packages/components/modules/content-feed/web/ContentFeed/styled.tsx (3)
RootContainer(3-8)HeaderContainer(10-16)ButtonContainer(18-24)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)
ButtonContainer(158-164)
packages/design-system/components/web/dropzones/Dropzone/index.tsx (4)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzoneProps(67-71)packages/utils/functions/file/getImageString/index.ts (1)
getImageString(3-6)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (5)
InputContainer(134-156)DropzoneContainer(9-31)AddFileWrapper(43-49)AddFileButton(33-41)ButtonContainer(158-164)packages/components/modules/content-feed/web/NewContentPost/styled.tsx (1)
ButtonContainer(19-24)
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/ContentFeed/types.ts
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/types.ts
[error] 10-10: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: Component Test Packages
- GitHub Check: Build and Lint Packages
🔇 Additional comments (17)
packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx (1)
16-23: LGTM! Proper object URL management.The implementation correctly creates and revokes object URLs using the useEffect cleanup function, preventing memory leaks.
packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
71-73:❓ Verification inconclusive
Ensure consistent route paths.
There's an inconsistency between the cancel route (
/posts) and the success navigation route (/posts/${id}). Verify that these routes align with your application's navigation structure.Also applies to: 64-66
🏁 Script executed:
#!/bin/bash # Search for route patterns to check consistency rg -A 1 "router\.push\(\'/posts" --type=tsLength of output: 150
I'll need to gather additional details from the file using fixed-string searches (to avoid regex escape issues) to see both the cancel route (
/posts) and any success route using/posts/${id}. Let's run a script to print the surrounding snippet (lines 60–75) and list occurrences of both string patterns.
🏁 Script executed:
#!/bin/bash # Output a snippet around the router.push calls for context. echo "Showing lines 60-75 of packages/components/modules/content-feed/web/NewContentPost/index.tsx:" sed -n '60,75p' packages/components/modules/content-feed/web/NewContentPost/index.tsx echo "" echo "Occurrences of router.push('/posts') (used in onCancel):" rg -F "router.push('/posts" packages/components/modules/content-feed/web/NewContentPost/index.tsx echo "" echo "Occurrences of router.push('/posts/') (potentially used in success navigation):" rg -F "router.push('/posts/" packages/components/modules/content-feed/web/NewContentPost/index.tsxLength of output: 1282
Routing Consistency – Confirm Navigation Intent
The success branch navigates to a dynamic post detail route (
/posts/${response.contentPostCreate?.contentPost?.node?.id}), while the cancel action routes back to the posts feed (/posts). Please verify that this difference is intentional according to your application's navigation design. If the behaviors are meant to differ, consider adding a brief inline comment or documentation to clarify this for future maintainers.packages/design-system/components/web/dropzones/Dropzone/index.tsx (6)
43-43: Good use of TypeScript for type safetyUsing
useState<DropzoneProps['storedImg']>(storedImg)ensures that the state follows the same type as the incoming props, which improves type safety and prevents potential type errors.
48-69: Multiple file upload handling is well implementedThe onDrop function does a good job of:
- Checking for edge cases (empty files or size limits)
- Handling multiple vs single file uploads with appropriate logic
- Using Promise.all for efficient concurrent processing of multiple files
This is a robust implementation that covers the needs for both single and multiple file upload scenarios.
84-84: Clean approach for file existence checkUsing a Boolean cast on a condition that handles both single and multiple file scenarios is an elegant way to determine if there are any files present.
91-130: Well-structured UI for the empty stateThe empty state UI is well organized with appropriate visual indicators, clear instructions, and proper error handling for rejected files. The conditional rendering based on the
titleprop provides good flexibility for customization.
172-183: Action buttons with good user experienceThe action buttons are well implemented with:
- Conditional rendering based on the
includeActionButtonprop- Appropriate button variants (outlined for upload, text for remove)
- Contextual appearance (remove button only shown when files exist)
This provides a good user experience with clear actions.
131-151:❓ Verification inconclusive
Verify cast safety when mapping files
There's a potential type safety issue when casting and mapping over files:
🏁 Script executed:
#!/bin/bash # Check if there are any instances where files is used without proper type checking # Search for places where files is used as an array without checking type rg -A2 "\(files as \[" packages/design-system/components/web/dropzones/DropzoneLength of output: 709
Action: Verify Cast Safety for
filesArray MappingThe search confirms that forced casts using
(files as [])are used in multiple places (e.g., when checking the array’s length in the conditional and when mapping overfiles). Please ensure that these casts are safe—that is, verify thatfilesis indeed an array whenmultipleis true. If there's any ambiguity about the type offiles, consider replacing the forced casts with a safer check (e.g., usingArray.isArray(files)) or updating the type definitions to enforce the expected array type.packages/design-system/components/web/dropzones/Dropzone/styled.tsx (5)
9-31: Well-styled scrollable container with proper accessibilityThe
DropzoneContaineris well implemented with:
- Proper overflow handling
- Custom scrollbar styling that maintains accessibility
- Consistent use of theme colors
The scrollbar styling enhances usability for horizontal scrolling of multiple files.
33-41: Good button styling with proper hover statesThe
AddFileButtonhas appropriate sizing, background color, and hover states that make it visually distinguishable and follow MUI design patterns.
43-49: Clear visual indicator for the add file actionThe
AddFileWrapperwith its dashed border provides a clear visual indication that this is a file addition area, which improves user experience by making the action more intuitive.
51-74: Nice conditional styling based on variantThe
FileWrappercomponent cleverly uses theisMiniprop to apply different styling for two different use cases. This reduces code duplication and makes the component more reusable.
92-103: Clear drop area container with good spacingThe
DropFilesContainerhas a clear visual style with the dashed border and appropriate spacing. The centered content and consistent dimensions help create a clear target area for users to drop files into.packages/components/schema.graphql (4)
484-493: Well-structured ContentPost type with appropriate relationshipsThe
ContentPosttype implements theNodeinterface correctly and includes essential fields:
- User association for ownership
- Profile association for display context
- Content field for post text
- Images connection for associated media
- Standard ID and primary key fields
This follows GraphQL best practices and provides a solid foundation for content post functionality.
815-815: Good placement of the contentPostCreate mutationThe
contentPostCreatemutation is properly positioned within the Mutation type and has a well-defined input/output structure.
822-822: Smart reuse of DeleteNodeInput for pageDelete mutationUsing the more generic
DeleteNodeInputtype for thepageDeletemutation is a good design decision. This promotes reusability and consistency across the API, potentially allowing the same input type to be used for future delete operations.
1489-1489: Verify query placement in the schemaThe
contentPostsquery is correctly placed in the Query type, which aligns with GraphQL best practices (queries in Query type, mutations in Mutation type).
packages/components/modules/content-feed/web/NewContentPost/types.ts
Outdated
Show resolved
Hide resolved
2d8235d to
1e9302d
Compare
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: 3
♻️ Duplicate comments (5)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (3)
50-69: 🛠️ Refactor suggestionAdd error handling for network failures.
The mutation only handles API response errors but doesn't address network failures or unexpected errors.
commitMutation({ variables: { input: { content: data.content, }, }, uploadables, + onError: (error) => { + sendToast('Failed to create post. Please try again.', { type: 'error' }) + }, onCompleted(response) { // existing code... }, })
71-73: 🛠️ Refactor suggestionAdd confirmation dialog for unsaved changes.
Users might accidentally lose their work when clicking cancel. Consider adding a confirmation dialog when there are unsaved changes.
const onCancel = useCallback(() => { + if (isDirty) { + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?') + if (!confirmed) return + } router.push('/posts') }, [router, isDirty])
41-48:⚠️ Potential issueImages not included in the mutation payload.
You're preparing the
uploadablesobject with image files, but not including the image references in the mutation input. This could cause images to be uploaded but not associated with the post.commitMutation({ variables: { input: { content: data.content, + images: Object.keys(uploadables), }, }, uploadables,packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)
76-90: Add an aria-label to the RemoveFileButton for better accessibility.The RemoveFileButton is missing an accessibility label, which would help screen reader users understand its purpose.
export const RemoveFileButton = styled('button')(({ theme }) => ({ position: 'absolute', top: '4px', right: '4px', width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '0 6px 0 6px', backgroundColor: theme.palette.grey[800], '&:hover': { backgroundColor: theme.palette.grey[800], }, + // The aria-label should be added in the component that uses this styled component + // e.g., <RemoveFileButton aria-label="Remove file" /> }))packages/components/schema.graphql (1)
505-509: Enhance content field validation in ContentPostCreateInput.The current
ContentPostCreateInputrequires thecontentfield, but it doesn't enforce additional constraints (e.g., minimum length or pattern matching). Note that while GraphQL doesn't include built‑in validation rules beyond type requirements, you can achieve this by leveraging custom validation directives (such as via thegraphql-constraint-directivelibrary) or implementing equivalent server‐side checks.Consider adding a directive like
@constraint(minLength: 1)to thecontentfield (or another appropriate constraint) so that invalid or empty content can be caught early.
🧹 Nitpick comments (4)
packages/components/modules/content-feed/web/ContentFeed/types.ts (1)
1-1: Consider using a type alias instead of an empty interface.Empty interfaces are semantically equivalent to
{}in TypeScript. For components that don't require props, it's better to use a type alias:-export interface IContentFeedProps {} +export type IContentFeedProps = Record<string, never>;Alternatively, if you're planning to add properties later, consider adding comments to indicate future properties:
export interface IContentFeedProps { // TODO: Add properties for content feed configuration }🧰 Tools
🪛 Biome (1.9.4)
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/design-system/components/web/dropzones/Dropzone/index.tsx (3)
59-61: Avoid type assertions with 'any'.Using
as anybypasses TypeScript's type checking, which can lead to runtime errors. Use proper typing instead.- const newFiles = [...((files || []) as File[]), ...resultingFiles] - setFiles(newFiles as any) - onSelect(newFiles as any) + const newFiles = [...(Array.isArray(files) ? files : []), ...resultingFiles] + setFiles(newFiles) + onSelect(newFiles)
73-82: Improve null handling in the remove function.The
handleRemovefunction doesn't properly handle the case wherefilesmight be undefined in multiple mode.const handleRemove = (index?: number) => { if (multiple) { - const updatedFiles = (files as File[])?.filter((file, i) => i !== index) + const fileArray = Array.isArray(files) ? files : [] + const updatedFiles = fileArray.filter((file, i) => i !== index) setFiles(updatedFiles) onRemove(index) } else { setFiles(undefined) onRemove() } }
142-149: Add key validation in the map function.The key for mapped elements should use a more reliable unique identifier than the file name with index, as file names might contain special characters or be duplicated.
{(files as unknown as [])?.map((file, index) => ( <DropzonePreview onFileClick={(selectedFile) => onFileClick?.(selectedFile, index)} file={file} - key={`${(file as File)?.name}_${index}`} + key={typeof file === 'string' ? `str_${index}` : `file_${index}_${(file as File)?.name?.replace(/[^a-zA-Z0-9]/g, '')}`} handleRemoveFile={() => handleRemove(index)} /> ))}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/types.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/package.json(1 hunks)packages/components/schema.graphql(9 hunks)packages/config/.eslintrc.js(1 hunks)packages/design-system/components/web/dropzones/Dropzone/__storybook__/stories.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/index.tsx(2 hunks)packages/design-system/components/web/dropzones/Dropzone/styled.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/types.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
- packages/components/modules/content-feed/common/index.ts
- packages/config/.eslintrc.js
- packages/design-system/components/web/dropzones/Dropzone/storybook/stories.tsx
- packages/components/modules/content-feed/web/ContentFeedImage/types.ts
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/components/modules/content-feed/web/index.ts
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
- packages/components/package.json
- packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
- packages/design-system/components/web/dropzones/Dropzone/types.ts
🧰 Additional context used
🧬 Code Definitions (3)
packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx (2)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzonePreviewProps(60-65)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (2)
FileWrapper(51-74)RemoveFileButton(76-90)
packages/design-system/components/web/dropzones/Dropzone/index.tsx (3)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzoneProps(67-71)packages/utils/functions/file/getImageString/index.ts (1)
getImageString(3-6)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (4)
InputContainer(134-156)DropzoneContainer(9-31)AddFileWrapper(43-49)AddFileButton(33-41)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (5)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
ContentPostCreateForm(1-4)UploadableContentPostFiles(6-8)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts (1)
useContentPostCreateMutation(25-26)packages/components/modules/content-feed/web/NewContentPost/constants.ts (2)
DEFAULT_CONTENT_POST_CREATE_FORM_VALUES(5-8)CONTENT_POST_CREATE_FORM_VALIDATION(10-13)packages/utils/functions/form/setFormRelayErrors/index.ts (1)
setFormRelayErrors(17-32)packages/components/modules/content-feed/web/NewContentPost/styled.tsx (3)
RootContainer(3-8)HeaderContainer(10-17)ButtonContainer(19-24)
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/ContentFeed/types.ts
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Component Test Packages
🔇 Additional comments (23)
packages/components/modules/content-feed/web/NewContentPost/types.ts (3)
1-4: Clean interface definition for content post form.The interface is well-structured with a required content field and an optional images array, appropriately matching the form requirements.
6-8: Good use of template literal type for dynamic image keys.This type definition elegantly handles the mapping between numbered image keys and their corresponding File or Blob objects, ensuring type safety for the uploadable files.
10-10: Properly addressed empty interface concern.Using
Record<string, never>instead of an empty interface follows best practices for TypeScript, addressing the previous review comment about empty interfaces.packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx (2)
8-48: Well-implemented file preview component with proper resource cleanup.The component correctly handles both string URLs and File/Blob objects, and properly manages object URL lifecycle to prevent memory leaks. The useEffect cleanup function ensures that object URLs are revoked when the component unmounts or when the file changes.
43-45: Good accessibility with aria-label.Adding the aria-label to the remove button improves accessibility for screen readers.
packages/design-system/components/web/dropzones/Dropzone/styled.tsx (6)
4-4: Good addition of the Button import from MUI.Adding the Button import is necessary for the new AddFileButton component.
9-31: Well-implemented scrollable container with good UX details.The DropzoneContainer component has proper scrollbar styling with hover effects, providing a better user experience when viewing many files. The component correctly uses shouldForwardProp to filter out custom props.
33-41: Good implementation of the AddFileButton.The AddFileButton component uses appropriate styling for a file addition action with proper hover effects.
43-49: Good implementation of the AddFileWrapper.The AddFileWrapper provides a consistent visual container with dashed borders to clearly indicate the file addition area.
51-74: Well-implemented conditional styling for FileWrapper.The FileWrapper component correctly handles different styling based on the isMini prop, providing flexibility for various display contexts.
92-103: Good implementation of the DropFilesContainer.The DropFilesContainer provides a clear visual cue for the file drop area with appropriate styling and spacing.
packages/components/schema.graphql (12)
204-204: Good addition of addedParticipants in ChatRoomOnRoomUpdate type.This field complements the existing removedParticipants field, providing symmetry for tracking both additions and removals in room updates.
328-328: Good addition of addedParticipants in ChatRoomUpdatePayload type.This field provides a consistent response structure by returning information about both added and removed participants in a single payload.
484-493: Well-structured ContentPost type definition.The ContentPost type has all necessary fields including user, profile, content, images with appropriate pagination support, and implements the Node interface correctly.
495-503: Good implementation of ContentPostConnection for pagination.The connection type follows the Relay connection specification with proper pageInfo, edges, and count fields.
511-517: Good implementation of ContentPostCreatePayload.The payload follows GraphQL best practices by including potential errors, debugging information, the created ContentPostEdge, and a clientMutationId for tracking.
519-526: Good implementation of ContentPostEdge.The edge type correctly follows the Relay specification with node and cursor fields.
528-535: Good implementation of ContentPostImage type.The ContentPostImage type has all necessary fields and correctly establishes the relationship with its parent ContentPost.
537-545: Good implementation of ContentPostImageConnection for pagination.The connection type properly follows the Relay connection specification for pagination of content post images.
547-554: Good implementation of ContentPostImageEdge.The edge type correctly follows the Relay specification for image edges.
573-584: Good implementation of generic node deletion types.The DeleteNodeInput and DeleteNodePayload types follow GraphQL best practices for deletion operations with proper error handling, providing a reusable pattern across the schema.
815-815: Good implementation of the contentPostCreate mutation.The mutation is properly defined to accept ContentPostCreateInput and return ContentPostCreatePayload, maintaining consistency with other mutations in the schema.
1489-1489: Good implementation of the contentPosts query.The query is correctly placed in the Query type (not in Mutation) and supports standard pagination parameters.
packages/design-system/components/web/dropzones/Dropzone/dropzone-preview.tsx
Show resolved
Hide resolved
packages/components/modules/content-feed/web/ContentFeed/index.tsx
Outdated
Show resolved
Hide resolved
1e9302d to
d7d157a
Compare
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: 6
♻️ Duplicate comments (2)
packages/design-system/components/web/dropzones/Dropzone/index.tsx (1)
48-53:⚠️ Potential issueFix file size validation for multiple files
When multiple files are accepted, only the first file's size is validated. All files should be individually checked against the size limit.
onDrop: async (acceptedFiles) => { if (acceptedFiles.length === 0 || !acceptedFiles[0]) return - if ((acceptedFiles[0]?.size || 0) > maxFileSize * 1024 * 1024) { - sendToast(`This file is too large (max ${maxFileSize} MB).`, { type: 'error' }) - return - } + // Validate all files before processing + const invalidSizeFiles = acceptedFiles.filter(file => + (file.size || 0) > maxFileSize * 1024 * 1024 + ) + + if (invalidSizeFiles.length > 0) { + sendToast(`${invalidSizeFiles.length} file(s) exceed the size limit (max ${maxFileSize} MB).`, { type: 'error' }) + return + }packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)
76-90: 🛠️ Refactor suggestionAdd aria-label to the RemoveFileButton for better accessibility
The RemoveFileButton is missing an accessibility label, which would help screen reader users understand its purpose.
export const RemoveFileButton = styled('button')(({ theme }) => ({ position: 'absolute', top: '4px', right: '4px', width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '0 6px 0 6px', backgroundColor: theme.palette.grey[800], '&:hover': { backgroundColor: theme.palette.grey[800], }, + // The aria-label should be added in the component that uses this styled component + // e.g., <RemoveFileButton aria-label="Remove file" /> }))
🧹 Nitpick comments (10)
packages/components/schema.graphql (4)
201-205: Clarify the Purpose ofaddedParticipants
In theChatRoomOnRoomUpdatetype, the newaddedParticipantsfield (line 204) complements the existingremovedParticipantsfield. To improve clarity for consumers of the schema, consider adding a description (docstring) to detail its intended use, ensuring that frontend and backend teams are aligned on its semantics.
528-535: Review theContentPostImageType
The newContentPostImagetype is straightforward and integrates with theContentPostrelationship. Verify that theimagefield being typed as aStringis intentional (e.g., representing a URL or identifier). If the value is expected to be a file reference, you might consider a more specific scalar (such as a dedicated File type) for improved type safety.
573-576: Enhance Documentation forDeleteNodeInput
The newDeleteNodeInputinput type defines the minimal fields required for deletion operations. For consistency and clarity across the schema, consider adding descriptive comments for each field (e.g., what theidrepresents and the purpose ofclientMutationId).
578-584: Align Naming Conventions inDeleteNodePayload
TheDeleteNodePayloadtype contains a field nameddeletedID. To maintain consistency with the camelCase naming convention used throughout the schema, renaming this field todeletedIdis recommended.packages/design-system/components/web/dropzones/Dropzone/DropzonePreview/index.tsx (1)
16-23: Consider adding a fallback for missing filesThe useEffect cleanup function returns
undefinedwhen the file is a string, which is allowed in React but inconsistent with the return type when it's not a string.useEffect(() => { if (typeof file !== 'string') { const url = URL.createObjectURL(file) setObjectUrl(url) return () => URL.revokeObjectURL(url) } - return undefined + // Clean up function for string case + return () => {} }, [file])packages/design-system/components/web/dropzones/Dropzone/index.tsx (3)
84-84: Simplify Boolean expression for better readabilityThe current expression for checking if files exist is complex and hard to read. It can be simplified for better maintainability.
- const hasFiles = Boolean((!multiple && files) || (multiple && (files as [])?.length)) + const hasFiles = multiple + ? Array.isArray(files) && files.length > 0 + : files !== undefined && files !== null
130-151: Consider adding a maximum number of files limitThe component allows adding multiple files without a clear limit, which could lead to performance issues or unexpected behavior if too many files are added.
Consider adding a
maxFilesprop to limit the number of files that can be added:maxFiles?: numberThen modify the onDrop handler to check this limit:
if (multiple) { + if (maxFiles && (files as [])?.length >= maxFiles) { + sendToast(`Maximum of ${maxFiles} files allowed.`, { type: 'error' }) + return + } + // If maxFiles is set, limit the number of files that can be added + const remainingSlots = maxFiles ? maxFiles - ((files as [])?.length || 0) : acceptedFiles.length + const filesToProcess = acceptedFiles.slice(0, remainingSlots) - const resultingFiles = await Promise.all( - acceptedFiles.map(async (f) => (asBase64 ? getImageString(f) : f)), - ) + const resultingFiles = await Promise.all( + filesToProcess.map(async (f) => (asBase64 ? getImageString(f) : f)), + )
73-82: Improve onRemove callback parameter consistencyWhen removing files in multiple mode, the index is passed to onRemove, but in single mode, no parameters are passed. Consider making this more consistent.
const handleRemove = (index?: number) => { if (multiple) { const updatedFiles = (files as File[])?.filter((file, i) => i !== index) setFiles(updatedFiles) - onRemove(index) + onRemove(index, files && Array.isArray(files) ? files[index] : undefined) } else { setFiles(undefined) - onRemove() + onRemove(undefined, files) } }packages/design-system/components/web/dropzones/Dropzone/styled.tsx (2)
87-89: Improve hover feedback for RemoveFileButtonThe hover style for the RemoveFileButton doesn't change the appearance, which provides poor user feedback. Consider making the hover state visually distinct.
'&:hover': { - backgroundColor: theme.palette.grey[800], + backgroundColor: theme.palette.grey[900], + opacity: 0.9, },
51-74: Consider adding focus indicator for FileWrapperThe FileWrapper has hover styles but lacks focus indicators for keyboard navigation, which is important for accessibility.
export const FileWrapper = styled(Box, { shouldForwardProp: (prop) => prop !== 'isMini', })<{ isMini: boolean }>(({ theme, isMini }) => isMini ? { position: 'relative', flexShrink: 0, width: '80px', height: '80px', border: `2px solid ${theme.palette.grey[200]}`, borderRadius: '12px', padding: '4px', display: 'inline-block', '&:hover': { border: `2px solid black`, }, + '&:focus-within': { + border: `2px solid ${theme.palette.primary.main}`, + outline: 'none', + }, } : { padding: theme.spacing(2), display: 'flex', flexDirection: 'column', alignItems: 'center', + '&:focus-within': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: '2px', + }, }, )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/types.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/package.json(1 hunks)packages/components/schema.graphql(9 hunks)packages/config/.eslintrc.js(1 hunks)packages/design-system/components/web/dropzones/Dropzone/DropzonePreview/index.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/__storybook__/stories.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/index.tsx(2 hunks)packages/design-system/components/web/dropzones/Dropzone/styled.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/types.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (15)
- packages/components/modules/content-feed/web/ContentFeedImage/types.ts
- packages/config/.eslintrc.js
- packages/components/modules/content-feed/web/NewContentPost/types.ts
- packages/components/modules/content-feed/common/index.ts
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
- packages/design-system/components/web/dropzones/Dropzone/storybook/stories.tsx
- packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
- packages/components/package.json
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/components/modules/content-feed/web/index.ts
- packages/components/modules/content-feed/web/NewContentPost/index.tsx
- packages/design-system/components/web/dropzones/Dropzone/types.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/design-system/components/web/dropzones/Dropzone/DropzonePreview/index.tsx (2)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzonePreviewProps(60-65)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (2)
FileWrapper(51-74)RemoveFileButton(76-90)
packages/design-system/components/web/dropzones/Dropzone/index.tsx (3)
packages/design-system/components/web/dropzones/Dropzone/types.ts (1)
DropzoneProps(67-71)packages/utils/functions/file/getImageString/index.ts (1)
getImageString(3-6)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (5)
InputContainer(134-156)DropzoneText(126-132)DropzoneContainer(9-31)AddFileWrapper(43-49)AddFileButton(33-41)
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/ContentFeed/types.ts
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
🔇 Additional comments (9)
packages/components/schema.graphql (5)
495-503: ConfirmContentPostConnectionConforms to Relay Standards
The newContentPostConnectiontype correctly follows the Relay pagination pattern with fields forpageInfo,edges,totalCount, andedgeCount.
511-517:ContentPostCreatePayloadDefinition Looks Solid
The payload returned from the content post creation mutation is clearly defined with fields for errors, debugging information, the created Relay edge, andclientMutationId.
519-526:ContentPostEdgeRelay Edge is Implemented Correctly
The Relay edge for theContentPosttype includes the expectednodefield andcursorfor pagination. This structure aligns well with Relay conventions.
537-545:ContentPostImageConnectionFollows Expected Patterns
The new connection type for content post images is implemented in line with Relay conventions, including pagination data and total counts.
547-554:ContentPostImageEdgeRelay Edge is Well-Defined
The edge type for content post images includes all necessary components for Relay pagination.packages/design-system/components/web/dropzones/Dropzone/DropzonePreview/index.tsx (1)
1-50: LGTM: New DropzonePreview component is well implementedThe new DropzonePreview component effectively displays image previews for both string URLs and File/Blob objects, with proper cleanup of object URLs to prevent memory leaks. The component is flexible with the
isMiniprop to support different display styles.packages/design-system/components/web/dropzones/Dropzone/index.tsx (2)
142-149: Consider adding a key based on a more stable identifierUsing file names combined with array indices as React keys could potentially lead to issues if multiple files have the same name or if the array changes. If files have unique identifiers, consider using those instead.
If files don't have unique identifiers, consider creating a more stable key using a combination of name, size, and last modified date (for File objects):
- key={`${(file as File)?.name}_${index}`} + key={typeof file === 'string' + ? `string_${file}_${index}` + : `file_${(file as File)?.name}_${(file as File)?.size}_${(file as File)?.lastModified || index}`}
3-42: LGTM: Component updates significantly improve functionalityThe Dropzone component has been enhanced to support both single and multiple file uploads, with improved UI for file previews and more intuitive user interactions. The addition of the DropzonePreview component is a great separation of concerns.
packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)
9-31: LGTM: Good implementation of scrollable container with custom scrollbarThe DropzoneContainer component is well implemented with comprehensive scrollbar styling and appropriate shouldForwardProp usage to prevent unnecessary props from leaking to the DOM.
packages/design-system/components/web/dropzones/Dropzone/DropzonePreview/index.tsx
Show resolved
Hide resolved
da524a1 to
cf0fe80
Compare
|
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: 1
♻️ Duplicate comments (10)
packages/components/modules/content-feed/web/ContentFeed/types.ts (1)
1-1: Replace empty interface with a type aliasEmpty interfaces in TypeScript are equivalent to
{}and don't provide any type safety or documentation value. Since this appears to be a component props interface, it should either:
- Define the actual props the component takes, or
- Use a type alias to explicitly show it doesn't need props currently
-export interface ContentFeedProps {} +// If no props are needed currently: +export type ContentFeedProps = Record<string, never>; + +// Or if props will be added later: +// export interface ContentFeedProps { +// // Props will be added in future implementation +// }This makes the intent clearer and follows TypeScript best practices. The
Record<string, never>type explicitly indicates an object that allows no properties, which is more type-safe than an empty interface.🧰 Tools
🪛 Biome (1.9.4)
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (4)
50-69: Add error handling for network failures.The mutation implementation only handles API response errors but doesn't catch network failures or unexpected errors.
Implement an onError handler to provide feedback on network failures:
commitMutation({ variables: { input: { content: data.content, }, }, uploadables, + onError: (error) => { + sendToast('Failed to create post. Please try again.', { type: 'error' }) + }, onCompleted(response) { // Existing code... }, })
65-65: Incorrect route in post navigation.The navigation path uses
/posts/but based on past review comments, the correct route should be/feed.Update the route to maintain consistency:
- router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`) + router.push(`/feed/${response.contentPostCreate?.contentPost?.node?.id}`)
71-73: Add confirmation dialog for unsaved changes.Users might accidentally lose their work when clicking cancel.
Add a confirmation dialog when there are unsaved changes:
const onCancel = useCallback(() => { + if (isDirty) { + const confirmed = window.confirm('You have unsaved changes. Are you sure you want to leave?') + if (!confirmed) return + } - router.push('/posts') + router.push('/feed') }, [router, isDirty])
41-49:⚠️ Potential issueImages are not passed in the mutation input.
The function prepares the
uploadablesobject correctly but never passes the corresponding image keys to the GraphQL mutation in the input variables. The form supports adding images, but they won't be associated with the post.Fix by including the imageKeys in the mutation input:
commitMutation({ variables: { input: { content: data.content, + images: Object.keys(uploadables), }, }, uploadables, onCompleted(response) {packages/design-system/components/web/dropzones/Dropzone/index.tsx (2)
48-53: Improve file size validation for multiple files.When multiple files are accepted, only the first file's size is validated. All files should be checked individually.
Modify the validation to check all files:
onDrop: async (acceptedFiles) => { if (acceptedFiles.length === 0 || !acceptedFiles[0]) return - if ((acceptedFiles[0]?.size || 0) > maxFileSize * 1024 * 1024) { - sendToast(`This file is too large (max ${maxFileSize} MB).`, { type: 'error' }) - return - } + // Validate all files before processing + const invalidSizeFiles = acceptedFiles.filter(file => + (file.size || 0) > maxFileSize * 1024 * 1024 + ) + + if (invalidSizeFiles.length > 0) { + sendToast(`${invalidSizeFiles.length} file(s) exceed the size limit (max ${maxFileSize} MB).`, { type: 'error' }) + return + }
59-61: Improve type safety when setting files.Using
as anytype assertions reduces type safety. This could lead to potential runtime errors if the types don't match.Use more specific types:
- const newFiles = [...((files || []) as File[]), ...resultingFiles] - setFiles(newFiles as any) - onSelect(newFiles as any) + const currentFiles = (files || []) as Array<File | string | undefined> + const newFiles = [...currentFiles, ...resultingFiles.filter(Boolean)] + setFiles(newFiles) + onSelect(newFiles)packages/design-system/components/web/dropzones/Dropzone/styled.tsx (2)
33-41: Add focus styles to AddFileButton for keyboard navigation.The AddFileButton has hover styles but no focus styles, which is important for keyboard navigation and accessibility.
Add focus styles to improve accessibility:
export const AddFileButton = styled(Button)(({ theme }) => ({ width: '72px', height: '72px', backgroundColor: theme.palette.grey[200], borderRadius: '8px', '&:hover': { backgroundColor: theme.palette.grey[300], }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: '2px', + backgroundColor: theme.palette.grey[300], + }, }))
76-90: Add an aria-label to the RemoveFileButton for better accessibility.The RemoveFileButton is missing an accessibility label, which would help screen reader users understand its purpose.
Add a note about aria-label:
export const RemoveFileButton = styled('button')(({ theme }) => ({ position: 'absolute', top: '4px', right: '4px', width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '0 6px 0 6px', backgroundColor: theme.palette.grey[800], '&:hover': { backgroundColor: theme.palette.grey[800], }, + // The aria-label should be added in the component that uses this styled component + // e.g., <RemoveFileButton aria-label="Remove file" /> }))packages/components/schema.graphql (1)
505-509: Improve validation in ContentPostCreateInput.The
ContentPostCreateInputtype is defined with a requiredcontentfield but lacks validation constraints to ensure quality content.Consider adding a validation directive for content quality:
While GraphQL's core spec doesn't include validation directives like
minLength, you could enhance the schema with custom directives (e.g., via libraries likegraphql-constraint-directive) to enforce validation rules directly at the schema level:input ContentPostCreateInput { content: String! @constraint(minLength: 1) images: [String] clientMutationId: String }This would provide early validation at the schema level to prevent empty posts.
🧹 Nitpick comments (4)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (1)
89-94: Use proper loading state during form submission.The LoadingButton shows a loading state, but there's no visual feedback during the actual form submission process before the mutation starts.
Consider adding a loading indicator for form submission:
<LoadingButton color="inherit" type="submit" loading={isMutationInFlight} disabled={!isDirty || !isValid || isMutationInFlight} + loadingPosition="start" + startIcon={<></>} sx={{ maxWidth: 'fit-content', justifySelf: 'end' }} > Publish </LoadingButton>packages/design-system/components/web/dropzones/Dropzone/index.tsx (1)
142-149: Add key handling for keyboard navigation in file previews.The file preview elements are mapped with proper keys, but there's no keyboard interaction handling for selecting or removing files.
Enhance keyboard accessibility:
<DropzonePreview onFileClick={(selectedFile) => onFileClick?.(selectedFile, index)} file={file} key={`${(file as File)?.name}_${index}`} handleRemoveFile={() => handleRemove(index)} + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + onFileClick?.(file, index) + } else if (e.key === 'Delete' || e.key === 'Backspace') { + handleRemove(index) + } + }} />packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)
16-31: Improve scrollbar accessibility across browsers.The scrollbar customization is only using webkit prefixes, which won't work in Firefox or other non-webkit browsers.
Consider adding cross-browser scrollbar styling:
export const DropzoneContainer = styled(Box, { shouldForwardProp: (prop) => prop !== 'isDragAccept' && prop !== 'isDragReject' && prop !== 'isFocused', })(({ theme }) => ({ display: 'flex', marginBottom: '16px', overflow: 'auto', paddingBottom: '6px', '::-webkit-scrollbar': { height: '6px', }, '::-webkit-scrollbar-track': { boxShadow: `inset 0 0 1px ${theme.palette.grey[400]}`, borderRadius: '10px', }, '::-webkit-scrollbar-thumb': { background: theme.palette.grey[400], borderRadius: '10px', }, '::-webkit-scrollbar-thumb:hover': { background: theme.palette.grey[600], }, + // Firefox scrollbar styling + scrollbarWidth: 'thin', + scrollbarColor: `${theme.palette.grey[400]} transparent`, }))packages/components/schema.graphql (1)
484-493: Add GraphQL interfaces to ContentPost type.The
ContentPosttype is well-structured but doesn't implement any interfaces likeCommentsInterfaceorReactionsInterface, which would be useful for social features.Consider implementing common interfaces for social interaction:
-type ContentPost implements Node { +type ContentPost implements Node & CommentsInterface & ReactionsInterface { user: User! profile: Profile content: String! images(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ContentPostImageConnection """The ID of the object""" id: ID! pk: Int! + commentsCount: CommentsCount! + comments(offset: Int, before: String, after: String, first: Int, last: Int, q: String, orderBy: String): CommentConnection + isCommentsEnabled: Boolean! + reactionsCount: ReactionsCount + reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection + isReactionsEnabled: Boolean! + myReaction(profileId: ID): Reaction }This would allow users to comment on and react to content posts, enhancing the social aspect of the feature.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
packages/components/CHANGELOG.md(1 hunks)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts(1 hunks)packages/components/modules/content-feed/common/index.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeed/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/styled.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeed/types.ts(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/index.tsx(1 hunks)packages/components/modules/content-feed/web/ContentFeedImage/types.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/constants.ts(1 hunks)packages/components/modules/content-feed/web/NewContentPost/index.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/styled.tsx(1 hunks)packages/components/modules/content-feed/web/NewContentPost/types.ts(1 hunks)packages/components/modules/content-feed/web/index.ts(1 hunks)packages/components/package.json(2 hunks)packages/components/schema.graphql(9 hunks)packages/config/.eslintrc.js(1 hunks)packages/design-system/CHANGELOG.md(1 hunks)packages/design-system/components/web/dropzones/Dropzone/DropzonePreview/index.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/__storybook__/stories.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/index.tsx(2 hunks)packages/design-system/components/web/dropzones/Dropzone/styled.tsx(1 hunks)packages/design-system/components/web/dropzones/Dropzone/types.ts(2 hunks)packages/design-system/package.json(1 hunks)packages/wagtail/CHANGELOG.md(1 hunks)packages/wagtail/package.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (20)
- packages/components/modules/content-feed/common/index.ts
- packages/design-system/package.json
- packages/wagtail/CHANGELOG.md
- packages/config/.eslintrc.js
- packages/components/modules/content-feed/web/ContentFeedImage/types.ts
- packages/design-system/components/web/dropzones/Dropzone/storybook/stories.tsx
- packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
- packages/wagtail/package.json
- packages/components/CHANGELOG.md
- packages/components/modules/content-feed/web/ContentFeed/styled.tsx
- packages/components/modules/content-feed/web/ContentFeed/index.tsx
- packages/design-system/CHANGELOG.md
- packages/components/package.json
- packages/components/modules/content-feed/web/NewContentPost/constants.ts
- packages/components/modules/content-feed/web/NewContentPost/types.ts
- packages/components/modules/content-feed/web/NewContentPost/styled.tsx
- packages/design-system/components/web/dropzones/Dropzone/DropzonePreview/index.tsx
- packages/components/modules/content-feed/web/ContentFeedImage/index.tsx
- packages/components/modules/content-feed/web/index.ts
- packages/design-system/components/web/dropzones/Dropzone/types.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/components/modules/content-feed/web/NewContentPost/index.tsx (5)
packages/components/modules/content-feed/web/NewContentPost/types.ts (2)
ContentPostCreateForm(1-4)UploadableContentPostFiles(6-8)packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts (1)
useContentPostCreateMutation(25-26)packages/components/modules/content-feed/web/NewContentPost/constants.ts (2)
DEFAULT_CONTENT_POST_CREATE_FORM_VALUES(5-8)CONTENT_POST_CREATE_FORM_VALIDATION(10-13)packages/utils/functions/form/setFormRelayErrors/index.ts (1)
setFormRelayErrors(17-32)packages/components/modules/content-feed/web/NewContentPost/styled.tsx (3)
RootContainer(3-8)HeaderContainer(10-17)ButtonContainer(19-24)
🪛 Biome (1.9.4)
packages/components/modules/content-feed/web/ContentFeed/types.ts
[error] 1-1: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Analyze (javascript)
- GitHub Check: Component Test Packages
- GitHub Check: Build and Lint Packages
Since she is out sick, AA has confirmed the changes requested are in place.



__package_name__package update -v __package_version__Summary by CodeRabbit
New Features
Style