Skip to content

Conversation

@rodrigonahid
Copy link
Contributor

@rodrigonahid rodrigonahid commented Feb 6, 2025

  • __package_name__ package update - v __package_version__
    • changelog_info
    • changelog_info

Summary by CodeRabbit

  • New Features

    • Launched an interactive content feed for browsing and creating posts with text and images.
    • Introduced a streamlined post creation form that validates input and provides real-time notifications.
    • Enhanced image upload functionality with drag-and-drop support, live previews, and easy removal options.
    • Added a new component for creating content posts and improved navigation to the post creation page.
    • Introduced new GraphQL mutation for content post creation, facilitating backend integration.
    • Added a new component for image uploads within the content post creation form.
  • Style

    • Refined the visual layout and component styling across the content feed and upload interfaces for a smoother user experience.
    • Introduced new styled components for improved UI in the dropzone and content creation areas.

@changeset-bot
Copy link

changeset-bot bot commented Feb 6, 2025

⚠️ No Changeset found

Latest commit: cf0fe80

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Feb 6, 2025

Walkthrough

This 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

File(s) Changed Change Summary
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts
packages/components/modules/content-feed/common/index.ts
Added a new GraphQL mutation (ContentPostCreateMutationQuery) and custom hook (useContentPostCreateMutation); updated re-export of content-feed functionalities.
packages/components/modules/content-feed/web/ContentFeed/*
packages/components/modules/content-feed/web/ContentFeedImage/*
packages/components/modules/content-feed/web/ContentFeed/*/types.ts
Introduced new React components for content feed display and image upload handling, including new styled components and TypeScript interfaces.
packages/components/modules/content-feed/web/NewContentPost/* Added a new form component (NewContentPost) for content post creation with default form values, Zod schema validation, form submission logic, redirection, and associated styled components and types.
packages/components/schema.graphql
packages/components/package.json
Extended the GraphQL schema with new ContentPost types, connections, inputs, mutations, queries, and added a new export entry for the content-feed module.
packages/config/.eslintrc.js Disabled the react/no-array-index-key ESLint rule.
packages/design-system/components/web/dropzones/Dropzone/**/* Enhanced the Dropzone component with new props (includeActionButton, title, InputContainerStyle), improved file handling, additional styled components, updated type definitions, and modified storybook configuration; added a new DropzonePreview component for file preview and removal.

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
Loading
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
Loading

Suggested labels

approved

Poem

I hop through lines of freshly spun code,
New posts and images on a joyful road.
A mutation blooms where data takes flight,
With styled vibes and dropzones bright.
This rabbit cheers with a happy beat—
In every change, new magic we meet!
🐇✨

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/components/modules/content-feed/web/ContentFeed/index.tsx

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: Cannot read config file: /packages/components/.eslintrc.js
Error: Cannot find module '@baseapp-frontend/config/.eslintrc-with-restricted-paths.js'
Require stack:

  • /packages/components/.eslintrc.js
  • /node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/cli-engine.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/eslint.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/index.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/bin/eslint.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1248:15)
    at Module._load (node:internal/modules/cjs/loader:1074:27)
    at TracingChannel.traceSync (node:diagnostics_channel:315:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
    at Module.require (node:internal/modules/cjs/loader:1339:12)
    at require (node:internal/modules/helpers:135:16)
    at Object. (/packages/components/.eslintrc.js:1:18)
    at Module._compile (node:internal/modules/cjs/loader:1546:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1691:10)
    at Module.load (node:internal/modules/cjs/loader:1317:32)
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: Cannot read config file: /packages/components/.eslintrc.js
Error: Cannot find module '@baseapp-frontend/config/.eslintrc-with-restricted-paths.js'
Require stack:

  • /packages/components/.eslintrc.js
  • /node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/cli-engine.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/eslint.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/index.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/bin/eslint.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1248:15)
    at Module._load (node:internal/modules/cjs/loader:1074:27)
    at TracingChannel.traceSync (node:diagnostics_channel:315:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
    at Module.require (node:internal/modules/cjs/loader:1339:12)
    at require (node:internal/modules/helpers:135:16)
    at Object. (/packages/components/.eslintrc.js:1:18)
    at Module._compile (node:internal/modules/cjs/loader:1546:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1691:10)
    at Module.load (node:internal/modules/cjs/loader:1317:32)
packages/components/modules/content-feed/web/ContentFeed/styled.tsx

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: Cannot read config file: /packages/components/.eslintrc.js
Error: Cannot find module '@baseapp-frontend/config/.eslintrc-with-restricted-paths.js'
Require stack:

  • /packages/components/.eslintrc.js
  • /node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli-engine/cli-engine.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/eslint.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/eslint/index.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/lib/cli.js
  • /node_modules/.pnpm/eslint@8.57.1/node_modules/eslint/bin/eslint.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1248:15)
    at Module._load (node:internal/modules/cjs/loader:1074:27)
    at TracingChannel.traceSync (node:diagnostics_channel:315:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
    at Module.require (node:internal/modules/cjs/loader:1339:12)
    at require (node:internal/modules/helpers:135:16)
    at Object. (/packages/components/.eslintrc.js:1:18)
    at Module._compile (node:internal/modules/cjs/loader:1546:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1691:10)
    at Module.load (node:internal/modules/cjs/loader:1317:32)
  • 16 others
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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 images field 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 accessibility

The 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 accessibility

The 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 feedback

The 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 component

Move 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8de60b1 and 957515f.

📒 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.

Copy link

@coderabbitai coderabbitai bot left a 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 images array 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 ButtonContainer uses both gap and justifyContent: 'space-between' which might conflict. Since you're using gap for spacing, space-between is 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:

  1. Adding a TypeScript interface for future props
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 957515f and ae03fd9.

📒 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 satisfies operator 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.

Copy link

@coderabbitai coderabbitai bot left a 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 issue

Add 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

📥 Commits

Reviewing files that changed from the base of the PR and between ae03fd9 and 6c551c8.

📒 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])

@anicioalexandre anicioalexandre added the in review PR is currently being reviewed. label Mar 12, 2025
@rodrigonahid rodrigonahid force-pushed the content-feed-new-post branch from 6c551c8 to f1707b4 Compare March 13, 2025 23:03
Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6c551c8 and f1707b4.

📒 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 onError callback to handle scenarios such as network failures, timeouts, or unexpected exceptions. This is a repeat of a previous review comment; adding an onError callback 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 content field has no minimum length or pattern constraints. Consider adding a custom directive or adopting a library like graphql-constraint-directive to 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.

contentPosts in the Mutation type appears more like a query operation and might already exist in the Query type at line 1453. Consider removing or relocating it to the Query type. This is a repeat of a previous review comment.


1891-1932: Remove duplicate type definitions for ContentPost and related entities.

These lines repeat the same definitions for ContentPost, ContentPostConnection, and ContentPostImage that 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.

Copy link

@coderabbitai coderabbitai bot left a 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 issue

Fix 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 * 1024

Also applies to: 101-102


62-71: ⚠️ Potential issue

Add 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 suggestion

Add 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 issue

Duplicate 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 interface

The IContentFeedProps interface is currently empty, which is equivalent to {}. According to static analysis, this is a suspicious pattern.

You could:

  1. Add properties that will be needed by the ContentFeed component, or
  2. Use a type alias instead:
-export interface IContentFeedProps {
-
-}
+export type IContentFeedProps = {}
  1. If no props are planned for this component, you could remove the interface entirely and use FC without 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 configurable

For 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

📥 Commits

Reviewing files that changed from the base of the PR and between 038efd5 and 6756f3c.

📒 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 flexibility

Hardcoding 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 tsx

Length 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.ts to:

    -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 using rg --type ts --type tsx failed 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 isDirty and isValid for 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 the content field in the ContentPostCreateInput type 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!
-}

@anicioalexandre anicioalexandre added the needs changes PR was reviewed, and changes are required. label Mar 26, 2025
@davidbermudez-tsl davidbermudez-tsl force-pushed the content-feed-new-post branch 2 times, most recently from 6cdd7bc to e386cca Compare April 1, 2025 16:15
Copy link

@coderabbitai coderabbitai bot left a 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 the clientMutationId. 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 new ContentPostImage type is set up to represent images associated with a content post. It would be beneficial to validate the image field (for example, by checking for a valid URL format) and to ensure that the relationship to the ContentPost is correctly handled in your resolvers.


573-576: DeleteNodeInput input type:
This generic input type for deletion operations is straightforward, accepting an id and a clientMutationId. 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:

  1. If the component truly doesn't need props, consider:

    export type IContentFeedProps = Record<string, never>;
  2. 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 issue

Fix 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 issue

Properly 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-null content, an optional list of images, and a clientMutationId. Consider whether you might need to enforce extra validations on the content field (for example, a minimum length) using custom directives. Additionally, if image metadata becomes relevant, you may want to evolve the images field into a more structured type later on.

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Improve 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6cdd7bc and e386cca.

⛔ Files ignored due to path filters (2)
  • packages/components/__generated__/ActivityLogsFragment.graphql.ts is excluded by !**/__generated__/**
  • packages/components/__generated__/RemoveMemberMutation.graphql.ts is 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 * 1024

Or 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 in handleOnChange. 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.
INewContentPostProps is imported but remains empty and unused in this component.


51-71: Add network error handling.
A previous review suggested handling network failures using onError. Currently only server-side errors are handled via onCompleted.


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 omits images. 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 contentPostCreate did not return any conclusive evidence regarding whether the backend mutation requires the images field in its input. Consequently, please manually verify the backend schema or API documentation to confirm if the images field is expected as part of the mutation input. If the backend does require images (beyond handling uploadables), update the mutation payload in packages/components/modules/content-feed/web/NewContentPost/index.tsx accordingly.

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 contentPostCreate mutation. 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.

Copy link

@coderabbitai coderabbitai bot left a 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 issue

Fix 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 issue

Clarify support for images vs. videos.

The placeholder text mentions "images and videos," but the accept prop only allows PNG, GIF, and JPEG image formats. Either update the text to mention only images or update the accept prop 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 issue

Add 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 issue

Add 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 suggestion

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('/posts')
  }, [router])
🧹 Nitpick comments (5)
packages/components/modules/content-feed/web/NewContentPost/types.ts (1)

10-10: Replace empty interface with type alias and remove I prefix.

Empty interfaces provide no additional type information and should be replaced with type aliases. Additionally, the I prefix 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 maxFileSize prop is set to 15, 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 any type 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 [])?.length uses 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

📥 Commits

Reviewing files that changed from the base of the PR and between e386cca and 7df5bd9.

📒 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-key rule 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 jsx

Length of output: 88


Action Required: Verify Array Index Keys Usage

The ESLint config currently disables the react/no-array-index-key rule, 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 .tsx and .jsx files:

#!/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 the isMini prop.

The FileWrapper component correctly applies different styles based on the isMini prop, 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.

Copy link

@coderabbitai coderabbitai bot left a 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 issue

Fix 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 suggestion

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('/posts')
  }, [router, isDirty])

Don't forget to add isDirty to the dependency array of the useCallback.


50-69: 🛠️ Refactor suggestion

Add 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 interface

The current empty interface IContentFeedProps doesn'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 interface

Similar 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 accessibility

When 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 accessibility

The 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 casting

When 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 File in 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 any that 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7df5bd9 and fc78bbb.

📒 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 form

The interface properly defines the structure for creating a content post with appropriate types for each field.


6-8: LGTM: Well-defined type for uploadable files

The 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 handling

The 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 tsx

Length of output: 188


Action: Verify the Route Configuration for Post Navigation
The NewContentPost/index.tsx component navigates with

router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`)

after post creation. Please ensure that your application's routing configuration includes a corresponding /posts/:id route (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 addedParticipants field is now consistently included in both ChatRoomOnRoomUpdate and ChatRoomUpdatePayload types, 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.

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Add 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 suggestion

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, isDirty])
packages/components/schema.graphql (1)

505-509: 🛠️ Refactor suggestion

Enhance content field validation.

The content field 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 IContentFeedProps interface is currently empty, which is equivalent to {}. According to TypeScript best practices:

  1. An empty interface doesn't provide any type safety or documentation benefits
  2. If no props are intended, you could use a type alias instead: type ContentFeedProps = {}
  3. 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 in onCancel().

-          router.push(`/posts/${response.contentPostCreate?.contentPost?.node?.id}`)
+          router.push(`/feed/${response.contentPostCreate?.contentPost?.node?.id}`)

Or alternatively, update the onCancel function:

  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 files is 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

📥 Commits

Reviewing files that changed from the base of the PR and between fc78bbb and 742b17e.

📒 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:

  1. Making the onRemove method more flexible with an optional index parameter
  2. Adding support for customizing the title with string or JSX
  3. 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 addedParticipants field to both ChatRoomOnRoomUpdate and ChatRoomUpdatePayload types creates symmetry with the existing removedParticipants field, 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.

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

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('/posts')
 }, [router, isDirty])

41-69: 🛠️ Refactor suggestion

Add 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 ContentPostCreateInput doesn'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 IContentFeedProps doesn't provide any valuable type information. While this is likely a placeholder for future props, TypeScript best practices suggest either:

  1. Add the props you expect the component to need, or
  2. 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 alias

An 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 handleRemove

The handleRemove function 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 list

Using 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 assertions

There are several type assertions using as that 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 isMini prop. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 742b17e and 889c309.

📒 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 good

The ContentPostCreateForm interface is well-defined with a required content string and optional images array of Files.


6-8: LGTM - Well-typed upload files structure

The UploadableContentPostFiles type 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 enhancements

The addition of multiple: false and asBase64: true props 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 implemented

The 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 UseFormReturn to 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 UseFormReturn methods 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 FormValues generic and avoids prop drilling[1][7].


2. Form Context for Deep Nesting

Use FormProvider and useFormContext for 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 useForm and context hooks with your form's value interface
  • Component Isolation: Child components should only consume needed methods (register, control) rather than entire UseFormReturn object
  • 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:


Action: Verify Correct Type Assignment for ContentFeedImage’s Form Prop

Ensure that the form passed into ContentFeedImage is explicitly typed with react-hook-form’s UseFormReturn (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 ContentFeedImage component, 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 FormProvider and useFormContext for deeper nesting.

packages/design-system/components/web/dropzones/Dropzone/index.tsx (4)

31-41: LGTM - New props enhance component flexibility

The 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 typing

The state typing has been improved to use DropzoneProps['storedImg'], which ensures better type safety and consistency.


48-69: LGTM - Enhanced file handling logic

The 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 buttons

The conditional rendering of action buttons based on the includeActionButton prop 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 addedParticipants field to both ChatRoomOnRoomUpdate and ChatRoomUpdatePayload types provides symmetry with the existing removedParticipants field, enhancing the API's completeness.

Also applies to: 327-329


484-493: ContentPost type is well-structured.

The ContentPost type properly implements the Node interface and includes appropriate fields for user, profile, content, and images with pagination support.


528-535: ContentPostImage type is appropriately defined.

The ContentPostImage type correctly implements the Node interface and establishes a clear relationship with its parent ContentPost via the post field.


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 contentPostCreate mutation and contentPosts query 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 contentPosts field to the Profile type creates a logical relationship between profiles and their posts, with proper pagination support.

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Add 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 suggestion

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('/posts')
  }, [router, isDirty])
packages/design-system/components/web/dropzones/Dropzone/styled.tsx (1)

76-90: 🛠️ Refactor suggestion

Add aria-label to 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

The current ContentPostCreateInput requires the content field, 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 the graphql-constraint-directive library) or implementing equivalent server‐side checks.

  • File: packages/components/schema.graphql (Lines 505–509)
    Suggestion: Consider adding a directive like @constraint(minLength: 1) to the content field (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 with Record<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 handleRemove

The handleRemove function 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 onFileClick

When passing onFileClick to the DropzonePreview, 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 ContentPostImage

The ContentPostImage type 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 DeleteNodeInput

The DeleteNodeInput type 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.contentPosts

The contentPosts field in the Profile type 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

📥 Commits

Reviewing files that changed from the base of the PR and between 889c309 and 2d8235d.

📒 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=ts

Length 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.tsx

Length 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 safety

Using 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 implemented

The onDrop function does a good job of:

  1. Checking for edge cases (empty files or size limits)
  2. Handling multiple vs single file uploads with appropriate logic
  3. 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 check

Using 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 state

The 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 title prop provides good flexibility for customization.


172-183: Action buttons with good user experience

The action buttons are well implemented with:

  1. Conditional rendering based on the includeActionButton prop
  2. Appropriate button variants (outlined for upload, text for remove)
  3. 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/Dropzone

Length of output: 709


Action: Verify Cast Safety for files Array Mapping

The 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 over files). Please ensure that these casts are safe—that is, verify that files is indeed an array when multiple is true. If there's any ambiguity about the type of files, consider replacing the forced casts with a safer check (e.g., using Array.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 accessibility

The DropzoneContainer is well implemented with:

  1. Proper overflow handling
  2. Custom scrollbar styling that maintains accessibility
  3. Consistent use of theme colors

The scrollbar styling enhances usability for horizontal scrolling of multiple files.


33-41: Good button styling with proper hover states

The AddFileButton has 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 action

The AddFileWrapper with 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 variant

The FileWrapper component cleverly uses the isMini prop 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 spacing

The DropFilesContainer has 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 relationships

The ContentPost type implements the Node interface 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 mutation

The contentPostCreate mutation is properly positioned within the Mutation type and has a well-defined input/output structure.


822-822: Smart reuse of DeleteNodeInput for pageDelete mutation

Using the more generic DeleteNodeInput type for the pageDelete mutation 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 schema

The contentPosts query is correctly placed in the Query type, which aligns with GraphQL best practices (queries in Query type, mutations in Mutation type).

Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Add 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 suggestion

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('/posts')
  }, [router, isDirty])

41-48: ⚠️ Potential issue

Images not included in the mutation payload.

You're preparing the uploadables object 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 ContentPostCreateInput requires the content field, 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 the graphql-constraint-directive library) or implementing equivalent server‐side checks.

Consider adding a directive like @constraint(minLength: 1) to the content field (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 any bypasses 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 handleRemove function doesn't properly handle the case where files might 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2d8235d and 1e9302d.

📒 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.

Copy link

@coderabbitai coderabbitai bot left a 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 issue

Fix 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 suggestion

Add 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 of addedParticipants
In the ChatRoomOnRoomUpdate type, the new addedParticipants field (line 204) complements the existing removedParticipants field. 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 the ContentPostImage Type
The new ContentPostImage type is straightforward and integrates with the ContentPost relationship. Verify that the image field being typed as a String is 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 for DeleteNodeInput
The new DeleteNodeInput input 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 the id represents and the purpose of clientMutationId).


578-584: Align Naming Conventions in DeleteNodePayload
The DeleteNodePayload type contains a field named deletedID. To maintain consistency with the camelCase naming convention used throughout the schema, renaming this field to deletedId is recommended.

packages/design-system/components/web/dropzones/Dropzone/DropzonePreview/index.tsx (1)

16-23: Consider adding a fallback for missing files

The useEffect cleanup function returns undefined when 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 readability

The 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 limit

The 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 maxFiles prop to limit the number of files that can be added:

maxFiles?: number

Then 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 consistency

When 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 RemoveFileButton

The 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 FileWrapper

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1e9302d and d7d157a.

📒 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: Confirm ContentPostConnection Conforms to Relay Standards
The new ContentPostConnection type correctly follows the Relay pagination pattern with fields for pageInfo, edges, totalCount, and edgeCount.


511-517: ContentPostCreatePayload Definition Looks Solid
The payload returned from the content post creation mutation is clearly defined with fields for errors, debugging information, the created Relay edge, and clientMutationId.


519-526: ContentPostEdge Relay Edge is Implemented Correctly
The Relay edge for the ContentPost type includes the expected node field and cursor for pagination. This structure aligns well with Relay conventions.


537-545: ContentPostImageConnection Follows 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: ContentPostImageEdge Relay 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 implemented

The 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 isMini prop 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 identifier

Using 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 functionality

The 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 scrollbar

The DropzoneContainer component is well implemented with comprehensive scrollbar styling and appropriate shouldForwardProp usage to prevent unnecessary props from leaking to the DOM.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Apr 9, 2025

Copy link

@coderabbitai coderabbitai bot left a 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 alias

Empty 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:

  1. Define the actual props the component takes, or
  2. 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 issue

Images are not passed in the mutation input.

The function prepares the uploadables object 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 any type 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 ContentPostCreateInput type is defined with a required content field 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 like graphql-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 ContentPost type is well-structured but doesn't implement any interfaces like CommentsInterface or ReactionsInterface, 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

📥 Commits

Reviewing files that changed from the base of the PR and between da524a1 and cf0fe80.

📒 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

@davidbermudez-tsl davidbermudez-tsl dismissed priscilladeroode’s stale review April 9, 2025 22:04

Since she is out sick, AA has confirmed the changes requested are in place.

@davidbermudez-tsl davidbermudez-tsl merged commit 2b4c3c5 into master Apr 9, 2025
8 checks passed
@nossila nossila deleted the content-feed-new-post branch May 26, 2025 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in review PR is currently being reviewed. needs changes PR was reviewed, and changes are required.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants