Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content Feed New Post #209

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Content Feed New Post #209

wants to merge 2 commits into from

Conversation

rodrigonahid
Copy link

@rodrigonahid rodrigonahid commented Feb 6, 2025

  • __package_name__ package update - v __package_version__
    • changelog_info
    • changelog_info

Summary by CodeRabbit

  • New Features
    • Launched a refreshed content feed that allows users to view and navigate posts easily.
    • Introduced a new post creation interface featuring text input combined with intuitive image uploads via drag-and-drop, including previews and removal options.
    • Added new components for managing content feeds and images, enhancing user interaction.
    • Expanded GraphQL capabilities to support content post creation and retrieval with pagination.
    • Enhanced overall UI elements to deliver a more responsive and engaging user experience.
    • New components such as ContentFeed, ContentFeedImage, and NewContentPost have been added for improved functionality.
    • Introduced new styled components for better visual organization and user experience in content management.
    • Added new GraphQL mutation for creating content posts and integrated validation for user input.
    • Introduced constants for default form values and validation logic in the content post creation process.

Copy link

changeset-bot bot commented Feb 6, 2025

⚠️ No Changeset found

Latest commit: 038efd5

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

Copy link

coderabbitai bot commented Feb 6, 2025

Walkthrough

The pull request introduces multiple exports and new components within the content-feed module. It adds a GraphQL mutation for creating content posts along with a corresponding hook. New React components are included for displaying a content feed, handling image uploads through drag-and-drop, and creating new posts. Additionally, styled components, types, and constants have been implemented to support routing, form management, and validation logic using libraries like react-hook-form and zod.

Changes

File(s) Change Summary
packages/components/index.ts Added exports for new modules: __shared__, comments, messages, navigations, content-feed/web, profiles, notifications.
packages/components/modules/content-feed/common/graphql/mutations/ContentPostCreate.ts Introduced new GraphQL mutation (ContentPostCreateMutation) and hook (useContentPostCreateMutation) for creating content posts.
packages/components/modules/content-feed/common/index.ts Added export to re-export the ContentPostCreate module.
packages/components/modules/content-feed/web/ContentFeed/index.tsx Added new React component ContentFeed with navigation logic to create new posts.
packages/components/modules/content-feed/web/ContentFeed/styled.tsx Added styled components: RootContainer, HeaderContainer, and ButtonContainer for ContentFeed.
packages/components/modules/content-feed/web/ContentFeedImage/index.tsx Added ContentFeedImage component for image upload, including drag-and-drop functionality and preview features.
packages/components/modules/content-feed/web/ContentFeedImage/styled.tsx Introduced styled components for image handling: ContentFeedImageContainer, AddFileButton, AddFileWrapper, MiniatureFileWrapper, RemoveFileButton, and DropFilesContainer.
packages/components/modules/content-feed/web/NewContentPost/constants.ts Added constants (DEFAULT_CONTENT_POST_CREATE_FORM_VALUES and CONTENT_POST_CREATE_FORM_VALIDATION) for form defaults and validation.
packages/components/modules/content-feed/web/NewContentPost/index.tsx Introduced new React component NewContentPost for content post creation with form handling and GraphQL mutation integration.
packages/components/modules/content-feed/web/NewContentPost/styled.tsx Added styled components (RootContainer, HeaderContainer, ButtonContainer) for NewContentPost and corrected a typo in RootContainer.
packages/components/modules/content-feed/web/NewContentPost/types.ts Added new types: ContentPostCreateForm and UploadableContentPostFiles for post creation data structures.
packages/components/modules/content-feed/web/index.ts Updated index to export default components (ContentFeed, ContentFeedImage, NewContentPost) and re-export types from NewContentPost.
packages/components/schema.graphql Added new types, inputs, and mutations related to ContentPost, including ContentPost, ContentPostCreateInput, and contentPostCreate mutation.
packages/components/package.json Added new export entry for the content-feed module, enhancing export capabilities for TypeScript and module imports.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant N as NewContentPost
    participant G as GraphQL Service
    participant R as Router
    participant T as Notifier

    U->>N: Fill form and click submit
    N->>G: Commit mutation with form data
    G-->>N: Return success or error response
    alt Error in mutation
        N->>T: Display error toast
    else Mutation successful
        N->>T: Display success toast
        N->>R: Redirect to the new post page
    end
Loading

Suggested reviewers

  • Ronan-Fernandes
  • deboracosilveira
  • anicioalexandre
  • pt-tsl

Poem

I'm a little rabbit, hopping through new code,
Exporting and styling on every winding road.
Mutations leap like bunnies in a field of data,
Components bloom in a UI habitat—see you later!
With a twitch of my nose and a joyful little hop,
I celebrate these changes, never gonna stop! 🐇

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/index.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)

Tip

⚡🧪 Multi-step agentic review comment chat (experimental)
  • We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
    - To enable this feature, set early_access to true under in the settings.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

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

📒 Files selected for processing (2)
  • packages/components/index.ts (1 hunks)
  • packages/components/package.json (1 hunks)
🧰 Additional context used
🪛 GitHub Check: Build and Lint Packages
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"

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Component Test Packages
🔇 Additional comments (2)
packages/components/index.ts (1)

1-7: Export patterns should be reviewed for consistency with project guidelines.

The added export statements follow the existing pattern but there are two issues to consider:

  1. Static analysis tools are flagging missing file extensions (e.g., .js, .ts) for all module paths. While many bundlers can resolve paths without extensions, consider adding them if this is required by your project's configuration.

  2. A previous review comment suggested this file could be removed. Consider clarifying whether this file should be maintained or if there's an alternative approach planned.

#!/bin/bash
# Check if similar export patterns exist elsewhere in the codebase
find . -type f -name "index.ts" -exec grep -l "export \* from" {} \; | xargs grep -l "export \* from" | wc -l
🧰 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"

packages/components/package.json (1)

63-67: LGTM! Export configuration for content-feed module looks good.

The added export configuration correctly follows the established pattern for module exports in this package. This enables proper importing of the content-feed components from consuming applications.

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

Comment on lines 10 to 13
export const CONTENT_POST_CREATE_FORM_VALIDATION = z.object({
content: z.string(),
images: z.array(z.instanceof(File)),
} satisfies Record<keyof ContentPostCreateForm, unknown>)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validation schema doesn't match type definition.

The validation schema only allows File[] while the type allows string[] | File[] | Blob[]. This mismatch could cause runtime errors.

export const CONTENT_POST_CREATE_FORM_VALIDATION = z.object({
  content: z.string(),
-  images: z.array(z.instanceof(File)),
+  images: z.array(
+    z.union([z.string(), z.instanceof(File), z.instanceof(Blob)])
+  ).optional(),
} satisfies Record<keyof ContentPostCreateForm, unknown>)
📝 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.

Suggested change
export const CONTENT_POST_CREATE_FORM_VALIDATION = z.object({
content: z.string(),
images: z.array(z.instanceof(File)),
} satisfies Record<keyof ContentPostCreateForm, unknown>)
export const CONTENT_POST_CREATE_FORM_VALIDATION = z.object({
content: z.string(),
images: z.array(
z.union([z.string(), z.instanceof(File), z.instanceof(Blob)])
).optional(),
} satisfies Record<keyof ContentPostCreateForm, unknown>)

Comment on lines 3 to 8
export const RootContainer = styled(Box)(() => ({
dispaly: 'flex',
width: '600px',
alignSelf: 'center',
flexDirection: 'column',
}))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in display property.

The display property is misspelled as dispaly which will cause the flex layout to fail.

export const RootContainer = styled(Box)(() => ({
-  dispaly: 'flex',
+  display: 'flex',
  width: '600px',
  alignSelf: 'center',
  flexDirection: 'column',
}))
📝 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.

Suggested change
export const RootContainer = styled(Box)(() => ({
dispaly: 'flex',
width: '600px',
alignSelf: 'center',
flexDirection: 'column',
}))
export const RootContainer = styled(Box)(() => ({
display: 'flex',
width: '600px',
alignSelf: 'center',
flexDirection: 'column',
}))

Comment on lines 18 to 24
export const ButtonContainer = styled(Box)(() => ({
display: 'flex',
width: 'fit-content',
flexDirection: 'row',
justifyContent: 'space-between',
gap: '10px',
}))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove redundant justifyContent property.

The justifyContent: 'space-between' is unnecessary when using gap for spacing between buttons. This combination could lead to unexpected spacing behavior.

export const ButtonContainer = styled(Box)(() => ({
  display: 'flex',
  width: 'fit-content',
  flexDirection: 'row',
-  justifyContent: 'space-between',
  gap: '10px',
}))
📝 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.

Suggested change
export const ButtonContainer = styled(Box)(() => ({
display: 'flex',
width: 'fit-content',
flexDirection: 'row',
justifyContent: 'space-between',
gap: '10px',
}))
export const ButtonContainer = styled(Box)(() => ({
display: 'flex',
width: 'fit-content',
flexDirection: 'row',
gap: '10px',
}))

Comment on lines 41 to 72
const onSubmit = handleSubmit((data: ContentPostCreateForm) => {
const uploadables: UploadableContentPostFiles = {}
const imageKeys: string[] = []

if (data.images) {
data.images.forEach((image, index) => {
uploadables[`image-${index}`] = image as File
imageKeys.push(`image-${index}`)
})
}

commitMutation({
variables: {
input: {
content: data.content,
images: imageKeys,
},
},
uploadables,
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}`)
}
},
})
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for mutation failures

The mutation error handling only covers validation errors but doesn't handle network or other GraphQL errors.

   commitMutation({
     variables: {
       input: {
         content: data.content,
         images: imageKeys,
       },
     },
     uploadables,
     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}`)
       }
     },
+    onError(error) {
+      sendToast('Failed to create post. Please try again.', { type: 'error' })
+      console.error('Post creation failed:', error)
+    },
   })
📝 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.

Suggested change
const onSubmit = handleSubmit((data: ContentPostCreateForm) => {
const uploadables: UploadableContentPostFiles = {}
const imageKeys: string[] = []
if (data.images) {
data.images.forEach((image, index) => {
uploadables[`image-${index}`] = image as File
imageKeys.push(`image-${index}`)
})
}
commitMutation({
variables: {
input: {
content: data.content,
images: imageKeys,
},
},
uploadables,
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}`)
}
},
})
})
const onSubmit = handleSubmit((data: ContentPostCreateForm) => {
const uploadables: UploadableContentPostFiles = {}
const imageKeys: string[] = []
if (data.images) {
data.images.forEach((image, index) => {
uploadables[`image-${index}`] = image as File
imageKeys.push(`image-${index}`)
})
}
commitMutation({
variables: {
input: {
content: data.content,
images: imageKeys,
},
},
uploadables,
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}`)
}
},
onError(error) {
sendToast('Failed to create post. Please try again.', { type: 'error' })
console.error('Post creation failed:', error)
},
})
})

Comment on lines 110 to 112
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
Max. File Size: 15MB
</Typography>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistent max file size message

The UI shows 15MB limit but the code enforces 10MB limit.

 <Typography variant="caption" sx={{ color: 'text.secondary' }}>
-  Max. File Size: 15MB
+  Max. File Size: 10MB
 </Typography>
📝 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.

Suggested change
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
Max. File Size: 15MB
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
Max. File Size: 10MB
</Typography>

Comment on lines 91 to 99
<Box width="100%" position="relative" height="500px" mb="24px">
<Image
src={URL.createObjectURL(selectedUploadedFile)}
alt={selectedUploadedFile.name}
fill
style={{ objectFit: 'cover', borderRadius: '8px', height: '100%', width: '100%' }}
onLoad={() => URL.revokeObjectURL(URL.createObjectURL(selectedUploadedFile))}
/>
</Box>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential memory leak in Image component

The onLoad callback creates and revokes URLs, but if the component unmounts before the image loads, the URL might not be revoked.

+  useEffect(() => {
+    if (selectedUploadedFile) {
+      const objectUrl = URL.createObjectURL(selectedUploadedFile)
+      return () => URL.revokeObjectURL(objectUrl)
+    }
+  }, [selectedUploadedFile])
+
   return (
     <Box width="100%" position="relative" height="500px" mb="24px">
       <Image
-        src={URL.createObjectURL(selectedUploadedFile)}
+        src={objectUrl}
         alt={selectedUploadedFile.name}
         fill
         style={{ objectFit: 'cover', borderRadius: '8px', height: '100%', width: '100%' }}
-        onLoad={() => URL.revokeObjectURL(URL.createObjectURL(selectedUploadedFile))}
       />
     </Box>
   )

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 132 to 149
if (files) {
for (let i = 0; i < files?.length; i += 1) {
if (
files![i] &&
DEFAULT_IMAGE_MAX_SIZE &&
files![i].size > DEFAULT_IMAGE_MAX_SIZE
) {
sendToast(
`This file is too large (max ${DEFAULT_IMAGE_MAX_SIZE / 1024 / 1024}MB).`,
{
type: 'error',
},
)
} else {
field.onChange([...formFiles, ...files])
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize file size validation

The current implementation might process files even after finding an oversized file. Consider breaking early and handling multiple files more efficiently.

 if (files) {
+  const oversizedFiles = Array.from(files).filter(file => file.size > DEFAULT_IMAGE_MAX_SIZE)
+  if (oversizedFiles.length > 0) {
+    sendToast(
+      `${oversizedFiles.length} file(s) exceed the size limit (max ${DEFAULT_IMAGE_MAX_SIZE / 1024 / 1024}MB).`,
+      { type: 'error' }
+    )
+    return
+  }
-  for (let i = 0; i < files?.length; i += 1) {
-    if (
-      files![i] &&
-      DEFAULT_IMAGE_MAX_SIZE &&
-      files![i].size > DEFAULT_IMAGE_MAX_SIZE
-    ) {
-      sendToast(
-        `This file is too large (max ${DEFAULT_IMAGE_MAX_SIZE / 1024 / 1024}MB).`,
-        {
-          type: 'error',
-        },
-      )
-    } else {
-      field.onChange([...formFiles, ...files])
-    }
-  }
+  field.onChange([...formFiles, ...files])
 }

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.

Comment on lines +10 to +13
export const CONTENT_POST_CREATE_FORM_VALIDATION = z.object({
content: z.string(),
images: z.array(z.instanceof(File)),
} satisfies Record<keyof ContentPostCreateForm, unknown>)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Align validation schema with type definition.

The validation schema for images only accepts File instances, but the ContentPostCreateForm type allows string[] and Blob[] as well. This mismatch could cause runtime validation errors.

Update the validation schema to match the type:

export const CONTENT_POST_CREATE_FORM_VALIDATION = z.object({
  content: z.string(),
  images: z.array(
    z.union([z.string(), z.instanceof(File), z.instanceof(Blob)])
  ),
} satisfies Record<keyof ContentPostCreateForm, unknown>)

Comment on lines +3 to +8
export const RootContainer = styled(Box)(() => ({
dispaly: 'flex',
width: '600px',
alignSelf: 'center',
flexDirection: 'column',
}))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in display property and consider responsive width.

  1. The display property has a typo ('dispaly').
  2. Consider using a responsive width instead of a fixed 600px to better support different screen sizes.

Apply this diff to fix the typo and make the width responsive:

 export const RootContainer = styled(Box)(() => ({
-  dispaly: 'flex',
+  display: 'flex',
-  width: '600px',
+  width: '100%',
+  maxWidth: '600px',
   alignSelf: 'center',
   flexDirection: 'column',
 }))
📝 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.

Suggested change
export const RootContainer = styled(Box)(() => ({
dispaly: 'flex',
width: '600px',
alignSelf: 'center',
flexDirection: 'column',
}))
export const RootContainer = styled(Box)(() => ({
display: 'flex',
width: '100%',
maxWidth: '600px',
alignSelf: 'center',
flexDirection: 'column',
}))

Comment on lines +38 to +39
const DEFAULT_IMAGE_FORMATS = 'image/png, image/gif, image/jpeg'
const DEFAULT_IMAGE_MAX_SIZE = 10 * 1024 * 1024
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 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: 110-112

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

Comment on lines +1891 to +1932
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!
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove duplicate type definitions.

The ContentPost related types are defined twice in the schema - once at lines 482-532 and again at lines 1891-1932. This duplication should be removed to prevent confusion and potential conflicts.

Remove these duplicate type definitions at lines 1891-1932 as they're identical to the ones already defined earlier in the file.

Comment on lines +780 to 782
contentPostCreate(input: ContentPostCreateInput!): ContentPostCreatePayload
contentPosts(offset: Int, before: String, after: String, first: Int, last: Int): ContentPostConnection
commentCreate(input: CommentCreateInput!): CommentCreatePayload
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Separate query and mutation operations.

Line 781 includes contentPosts as a field in the Mutation type, but this appears to be a query operation. Queries should be defined in the Query type, not the Mutation type.

type Mutation {
  // other mutations...
  contentPostCreate(input: ContentPostCreateInput!): ContentPostCreatePayload
-  contentPosts(offset: Int, before: String, after: String, first: Int, last: Int): ContentPostConnection
  // other mutations...
}

The contentPosts query is already correctly defined in the Query type at line 1453.

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

Suggested change
contentPostCreate(input: ContentPostCreateInput!): ContentPostCreatePayload
contentPosts(offset: Int, before: String, after: String, first: Int, last: Int): ContentPostConnection
commentCreate(input: CommentCreateInput!): CommentCreatePayload
type Mutation {
// other mutations...
contentPostCreate(input: ContentPostCreateInput!): ContentPostCreatePayload
commentCreate(input: CommentCreateInput!): CommentCreatePayload
// other mutations...
}

Comment on lines +503 to +506
content: String!
images: [String]
clientMutationId: String
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider validating content field at the schema level.

The ContentPostCreateInput doesn't specify any validation constraints on the content field. Consider adding directives to ensure content meets minimum quality requirements.


🌐 Web query:

Does GraphQL support field validation directives like minLength or pattern?

💡 Result:

Yes, GraphQL supports field validation directives like minLength or pattern through custom schema directives and third-party libraries, though these are not part of the core GraphQL specification. Here's how it works:

Built-in vs. Custom Directives

  1. Core GraphQL provides basic type validation (e.g., String, Int) but lacks advanced constraints like minLength[7][9].
  2. Custom Directives extend this functionality:
    • Libraries like graphql-constraint-directive add validation rules via @constraint[3][4][5]:
      input ReviewInput {
        email: String! @constraint(format: "email", maxLength: 255)
        password: String! @constraint(minLength: 8)
      }
    • Apollo Server uses packages like apollo-validation-directives for validators such as @stringLength and @listLength[1].

Common Validation Patterns

Directive Use Case Example
@constraint Multi-purpose validation @constraint(min: 5, pattern: "^\\d+$")[4][6]
@stringLength String length bounds @stringLength(min: 3, max: 50)[1]
@listLength Array size limits @listLength(max: 10)[1]
@pattern Regex matching @pattern(regexp: "^[A-Za-z]+$")[1][4]

Implementation Methods

  1. Schema-First:
    const { constraintDirective } = require('graphql-constraint-directive');
    const schema = makeExecutableSchema({
      typeDefs,
      schemaDirectives: { constraint: constraintDirective }
    });
  2. Code-First (Apollo/GraphQL .NET):
    Field<StringGraphType>("username")
      .ApplyDirective("length", "min", 5, "max", 20)[2][6];

These solutions inject validation errors into resolver contexts while keeping schema documentation self-contained[1][4]. Popular implementations include:

  • Apollo's validation directives[1]
  • .NET's LengthDirective[2]
  • Java's SmallRye mapping Bean Validation to constraints[6]

For production use, always combine directive-based validation with additional server-side checks for security[3][5].

Citations:


Action Required: Implement Schema-Level Validation for the "content" Field

The current schema for the ContentPostCreateInput defines the "content" field simply as a String without any validation constraints. Although GraphQL’s core specification doesn’t include advanced validation like minLength or pattern, you can enforce these rules using custom directives (e.g., via libraries such as graphql-constraint-directive or similar tools).

  • Suggestion: Enhance the "content" field with a custom directive (for example, @constraint(minLength: 10)) to enforce minimum quality requirements.
  • Action: Integrate the appropriate validation library and update the schema accordingly to provide early input validation at the schema level.

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

@@ -0,0 +1,7 @@
export * from './modules/__shared__'
export * from './modules/comments'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file can be removed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes @rodrigonahid please remove it

RemoveFileButton,
} from './styled'

interface IContentFeedImageProps {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets move this to a types file and then add to the index file.

Also, this types seems a little bit weird. Why is images type equals to never[]?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's also avoid I prefix on type interfaces

Copy link

@@ -0,0 +1,7 @@
export * from './modules/__shared__'
export * from './modules/comments'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes @rodrigonahid please remove it

const router = useRouter()

const onNewPost = useCallback(() => {
router.push('/new-post')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this route configurable? just in case clients need this in different routes. We can keep using '/new-post' as default

RemoveFileButton,
} from './styled'

interface IContentFeedImageProps {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's also avoid I prefix on type interfaces

form.setValue('images', updatedFiles as never, { shouldValidate: true })
}

const handleDragEnter = (e: React.DragEvent) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets import DragEvent directly, right now it's undefined

</Box>
)}

{(isDragging || !formFiles?.length) && (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could've reused the design-system Dropzone component

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.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants