[NSoC'26] Add Image Upload Gallery with Masonry Layout and Persistence#149
[NSoC'26] Add Image Upload Gallery with Masonry Layout and Persistence#149Priyanka0205-CSE wants to merge 6 commits into
Conversation
|
Warning Rate limit exceeded
To continue reviewing without waiting, purchase usage credits in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe PR introduces a complete React + TypeScript + Vite example application showcasing the urBackend SDK. The ChangesSnapGrid SDK Example Application
Sequence DiagramssequenceDiagram
participant User
participant App
participant SDK as urBackend SDK
participant Storage as Storage Service
participant API as Local API<br/>(localhost:1235)
participant UI as UI State
User->>App: Click "Start Uploading"
App->>App: Open file picker
User->>App: Select image file
App->>SDK: sdk.storage.upload(file)
SDK->>Storage: Upload to backend
Storage-->>SDK: Return {url, ...}
SDK-->>App: Return upload result
App->>App: Extract imageUrl from result.url
App->>API: POST /api/images { url }
API-->>App: Image persisted
App->>UI: Update images array
UI-->>User: Show success alert + render new image
sequenceDiagram
participant User
participant App
participant API as Local API<br/>(localhost:1235)
participant MasonryGrid
participant UI as UI
User->>App: Component mount / Navigate to gallery
App->>App: useEffect triggers
App->>API: Fetch GET /api/images
API-->>App: Return image URLs
App->>UI: setState({ images })
App->>MasonryGrid: Pass images array
MasonryGrid->>MasonryGrid: Render image cards with skeleton loaders
User->>MasonryGrid: Image loads from URL
MasonryGrid->>UI: Fade in image, hide skeleton
User->>MasonryGrid: Hover over card
UI->>UI: Show overlay with like button & category
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 20
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (5)
examples/sdk-snapgrid/src/pages/HomePage.tsx-40-44 (1)
40-44:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd descriptive alt text for the gallery cards.
These images are part of the gallery content, but
alt=""makes them invisible to screen readers. A short label per card is enough here.♿ Proposed fix
- alt="" + alt={`Placeholder artwork ${i}`}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/sdk-snapgrid/src/pages/HomePage.tsx` around lines 40 - 44, The gallery images in HomePage (the <img> with className "post-card-image" using src `https://picsum.photos/300/40${i}`) currently have alt="" which hides them from screen readers; update the alt to a short descriptive label (e.g., use the card title if available or a fallback like "Gallery image {i+1}" using the loop index variable i) so each image has meaningful alt text while keeping it brief and relevant.examples/sdk-snapgrid/src/index.css-1-1 (1)
1-1:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix the current stylelint blockers in this stylesheet.
@importnotation (Line 1), keyword casing (Line 26), and deprecatedclipusage in.sr-only(Line 101) are flagged and should be updated to unblock lint.Suggested fix
-@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700;900&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap'); +@import 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700;900&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap'; @@ - --font-display: 'Playfair Display', Georgia, serif; + --font-display: 'Playfair Display', georgia, serif; @@ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; - clip: rect(0,0,0,0); + clip-path: inset(50%); + white-space: nowrap; border: 0; }Also applies to: 26-26, 94-103
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/sdk-snapgrid/src/index.css` at line 1, Update the stylesheet to satisfy stylelint: move or reformat the font import so it's a valid top-level `@import` (use `@import` "https://fonts.googleapis.com/..." or ensure the existing `@import` is the very first statement), normalize all CSS keyword casing to lowercase (fix the token flagged around the rule in the file, e.g., change any 'None', 'Transparent', 'Important', etc. to 'none', 'transparent', '!important'), and replace the deprecated clip usage in the .sr-only rule with a modern equivalent (remove clip: rect(...) and add clip-path: inset(50%) plus a -webkit-clip-path fallback while keeping the rest of the .sr-only positioning/visibility declarations intact).examples/sdk-snapgrid/src/components/Navbar/Navbar.module.css-45-50 (1)
45-50:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRename keyframes to kebab-case to satisfy stylelint.
The animation name at Line 45 / Line 48 violates the configured
keyframes-name-pattern.Suggested fix
-.logoMark { +.logoMark { @@ - animation: pulseMark 3s ease-in-out infinite; + animation: pulse-mark 3s ease-in-out infinite; } -@keyframes pulseMark { +@keyframes pulse-mark { 0%, 100% { filter: drop-shadow(0 0 8px rgba(201, 168, 76, 0.6)); } 50% { filter: drop-shadow(0 0 16px rgba(201, 168, 76, 0.9)); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/sdk-snapgrid/src/components/Navbar/Navbar.module.css` around lines 45 - 50, The keyframes name pulseMark violates keyframes-name-pattern; rename the animation identifier to kebab-case (e.g., pulse-mark) and update the animation property usage (animation: pulse-mark 3s ease-in-out infinite;) and the `@keyframes` declaration (`@keyframes` pulse-mark { ... }) so both references match the new kebab-case name (update any other occurrences of pulseMark in the stylesheet).examples/sdk-snapgrid/src/components/MasonryGrid/MasonryGrid.tsx-18-18 (1)
18-18:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse descriptive
alttext per imageLine 18 uses the same generic alt for all images. Provide a distinct, meaningful alt string (or mark as decorative when appropriate) to improve screen-reader usability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/sdk-snapgrid/src/components/MasonryGrid/MasonryGrid.tsx` at line 18, In MasonryGrid.tsx update the hardcoded alt="Uploaded" on the <img> used in the MasonryGrid component to provide per-image descriptive text (for example use a property from the image item such as item.title, item.description, or item.altText) or, if the image is purely decorative, set alt="" (empty string) and appropriate aria/presentation attributes; modify the JSX that renders the image (the <img> with alt="Uploaded") to use the image-specific value or an empty alt accordingly.examples/sdk-snapgrid/src/App.css-985-989 (1)
985-989:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winReplace the deprecated
clipfallback in.sr-only.
clipis deprecated here;clip-path: inset(50%)gives the same visually-hidden behavior without relying on the old property.🛠️ Suggested fix
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; - clip: rect(0,0,0,0); border: 0; + clip-path: inset(50%); border: 0; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/sdk-snapgrid/src/App.css` around lines 985 - 989, The .sr-only utility uses the deprecated clip property; update the .sr-only rule to remove clip: rect(0,0,0,0) and add an equivalent clip-path rule (e.g., clip-path: inset(50%)) while keeping the existing position, size, padding, margin, overflow and border values to preserve visually-hidden behavior; locate the .sr-only CSS selector in App.css and replace the clip declaration with the clip-path declaration for modern browsers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/public-api/src/app.js`:
- Around line 81-100: The public in-memory store images (used by
app.post("/api/images") and returned by app.get("/api/images")) is unbounded and
uses Date.now() for ids which can collide; fix by adding a hard cap and eviction
policy (e.g., MAX_IMAGES constant and drop oldest entries when pushing new ones)
and replace Date.now() id generation with a collision-safe generator (e.g., a
UUID or an incrementing counter variable) and validate/limit the incoming URL
size before accepting to prevent memory exhaustion from large payloads. Ensure
these changes are applied inside the POST handler that currently pushes to
images and that the GET handler still returns the bounded images array.
In `@apps/public-api/src/controllers/storage.controller.js`:
- Around line 337-338: The code trusts req.query.fileName / req.params.fileName
and constructs filePath by joining with UPLOADS_DIR, allowing path traversal;
fix by validating/sanitizing the incoming fileName before joining:
reject/nullify names with path separators or suspicious segments, prefer using
path.basename(fileName) or a strict regex whitelist (alphanumeric, dashes,
underscores, dots) to normalize the name, then compute filePath =
path.join(UPLOADS_DIR, safeName) and after path.join assert
path.resolve(filePath).startsWith(path.resolve(UPLOADS_DIR)); apply this
validation to both places that read req.query.fileName / req.params.fileName and
the variables fileName/filePath so reads and writes cannot escape UPLOADS_DIR
and reject/return an error for invalid names.
- Around line 311-315: The controller currently returns mixed response shapes
(e.g., direct res.json({signedUrl...}), res.json({ok:true}), and raw error
objects) which violates the API contract; update the response payloads in the
storage controller handlers that call res.json at/around the signedUrl block,
the ok:true block, and the error blocks so they always return { success:
boolean, data: object, message: string } — e.g., replace res.json({signedUrl...,
token..., filePath...}) with res.json({ success: true, data: { signedUrl, token,
filePath }, message: "" }), replace res.json({ ok: true }) with res.json({
success: true, data: {}, message: "" }), and on failures use the AppError class
and pass the error to next() (do not expose raw DB errors or throw raw errors)
so all error responses follow { success: false, data: {}, message:
"user-friendly message" } while keeping existing identifiers like signedUrl,
fileName, filePath, and token intact.
- Around line 340-351: The code currently pipes req directly into the file write
stream (req.pipe(writeStream)) with no size guard; modify the upload handler to
enforce a maximum byte limit by inserting a counting/limiting Transform between
req and writeStream that tracks bytes written, destroys the pipeline and deletes
any partial file when the limit is exceeded, and responds with a 413 status;
handle premature client disconnects and stream errors by cleaning up filePath
and ensuring writeStream.on("finish") still returns 200 only when under the
limit and writeStream.on("error") logs the error and returns a 500 (or 413 if
triggered by the limiter) so that writeStream, the limiter, and req are properly
destroyed to avoid unbounded disk usage.
- Around line 187-190: The code currently uses Project.findOne(), which can pick
an arbitrary tenant; instead retrieve the authenticated project from the request
context (e.g., req.project or res.locals.project or req.user.project depending
on your auth middleware) and assign that to the local variable project used by
isProjectStorageExternal(project), getBucket(project), and
normalizeProjectPath(project._id, path); also add a guard to return/throw a 403
or appropriate error if the authenticated project is missing to prevent
cross-project access.
In `@apps/public-api/src/routes/storage.js`:
- Around line 29-33: The storage routes (router.post('/upload-request' ->
requestUpload, router.post('/upload-confirm' -> confirmUpload,
router.put('/upload-direct' -> uploadDirect, router.get('/file/:fileName' ->
serveFile) are registered without auth/usage guards; update these route
registrations to include the authentication and guard middleware (verifyApiKey
and/or requireSecretKey as appropriate), attach checkUsageLimits for mutating
endpoints (upload-request, upload-confirm, upload-direct), and add the global or
per-route rate limiting middleware (e.g., rateLimiter) to prevent abuse—ensure
middleware order is correct (auth first, usage checks next, rate limiting where
needed) so the handlers (requestUpload, confirmUpload, uploadDirect, serveFile)
are never exposed unauthenticated.
In `@examples/sdk-snapgrid/src/App.css`:
- Around line 1016-1028: The bottom CSS block is overriding earlier section
spacing for .hero, .gallery-section, and .navbar-inner globally; remove these
global rules (the second set that sets border-left/right:none !important and the
duplicate width/padding block) or move them into the intended mobile breakpoint
so they only apply on small viewports; search for the selectors .hero,
.gallery-section, and .navbar-inner in the file and either delete the duplicate
override block or relocate its declarations into the mobile media query where
the mobile-specific layout is defined.
In `@examples/sdk-snapgrid/src/App.tsx`:
- Around line 13-23: fetchImages inside the useEffect assumes a 200 JSON payload
and hardcodes "http://localhost:1235/api/images"; update fetchImages to use the
configured API base (call to "/api/images" or env-driven origin) and add error
handling: check res.ok before parsing, wrap the fetch/parse in try/catch, verify
data && Array.isArray(data.images) before mapping, and only call setImages when
the data is valid (otherwise keep previous state or set a safe default) so the
gallery doesn't blank on network/response errors.
- Around line 32-65: The upload flow in handleUploadClick treats the upload as
successful before the backend persistence and naively appends "?raw=true" which
breaks if result.url already has a query string; after calling
sdk.storage.upload (result), build the image URL using a
URL/URLSearchParams-safe approach (add raw=true to search params rather than
string concatenation), POST that URL to /api/images, check the fetch response
with res.ok and throw or handle errors if not ok, and only call
setImages((prev)=>[...prev, imageUrl]) and show the success alert after the
backend confirms persistence; ensure errors from either sdk.storage.upload or
the POST are caught and reported.
In `@examples/sdk-snapgrid/src/components/Hero/Hero.module.css`:
- Around line 57-60: Rename all `@keyframes` declarations that use camelCase to
kebab-case (for example change ambientBreathe to ambient-breathe) and update
every animation/animation-name reference that uses those keys (e.g., the
animation properties that currently reference ambientBreathe and other camelCase
keys) to the new kebab-case names so the `@keyframes` declarations and their
usages match; ensure you update both shorthand animation and animation-name
properties across the CSS module.
In `@examples/sdk-snapgrid/src/components/MasonryGrid/MasonryGrid.module.css`:
- Around line 65-71: The CSS fails stylelint: rename the keyframe identifier
"cardIn" to a lint-compliant kebab-case name (e.g., "card-in") and update any
uses of animation: cardIn to animation: card-in; also remove or normalize stray
empty lines before declarations to satisfy declaration-empty-line-before (ensure
consistent single-line spacing between rules/declarations and no extra blank
lines inside rule blocks). Apply the same changes to the other keyframe blocks
and animation usages referenced (the other keyframe names and animation usages
at the noted occurrences) so all keyframe names and spacing follow the project's
keyframes-name-pattern and declaration-empty-line-before rules.
- Around line 52-54: Replace the deprecated page-break-inside declaration in
MasonryGrid.module.css: keep break-inside: avoid as the primary rule, remove
page-break-inside, and add a non-deprecated vendor fallback for legacy
WebKit/Safari engines (e.g., the -webkit-… column/break-inside variant) so the
masonry items still avoid breaking inside on older Safari browsers.
In `@examples/sdk-snapgrid/src/components/Navbar/Navbar.tsx`:
- Around line 112-124: Replace the clickable div used for logout with a semantic
button to restore keyboard accessibility: change the element with className
styles.avatar that calls sdk.auth.logout() and setUser(null) into a <button
type="button">, keep the onClick handler calling sdk.auth.logout() and
setUser(null), preserve the img src logic (user?.avatar ||
"https://i.pravatar.cc/36") and styles.avatar class on the button so styling
remains unchanged, and add an accessible label (e.g., aria-label="Log out" or
visually hidden text) to the button to match the existing login button pattern.
- Around line 95-103: Replace the incorrect OAuth calls in the Navbar click
handler by using the SDK's social flow: call sdk.auth.socialStart('google') to
get the redirect URL and navigate the browser to it (instead of
sdk.auth.loginWithProvider), remove the setTimeout and sdk.auth.getUser usage,
and implement handling of the OAuth callback elsewhere to call
sdk.auth.socialExchange({ rtCode, provider: 'google' }) and then sdk.auth.me()
to hydrate user state (setUser) after a successful exchange; ensure Navbar only
initiates the redirect and the app-level callback route performs socialExchange
+ me and updates setUser.
In `@examples/sdk-snapgrid/src/hooks/usePosts.ts`:
- Around line 10-21: The load callback in usePosts can apply stale async results
when multiple fetches overlap (an earlier fetch resolving after a later one), so
modify load to ignore out-of-date responses: introduce a per-call identifier
(e.g., a local requestId counter or AbortController signal) captured at the
start of load, pass it through/associate it with the fetchPosts call, and only
call setPosts or setError if the current requestId/abort signal still matches
(or is not aborted) when the promise resolves; update cleaning logic so the
effect that triggers load increments the requestId or aborts previous controller
when category/search change to prevent older responses from overwriting newer
state (refer to load, fetchPosts, setPosts, setError, and the hook’s dependency
array).
In `@examples/sdk-snapgrid/src/lib/urbackend.ts`:
- Around line 1-6: Delete the module urbackend.ts entirely because it
incorrectly imports a non-existent createClient, uses wrong config keys
(projectId/publicKey vs UrBackendConfig's apiKey and optional baseUrl),
hardcodes demo credentials instead of using environment variables, and is
unused; replace any future usages with the established pattern in
services/sdk.ts which calls the default urBackend function and reads
apiKey/baseUrl from env vars. Ensure no other files import createClient from
this module and remove the file from the project and version control.
In `@examples/sdk-snapgrid/src/services/sdk.ts`:
- Around line 3-5: The code passes
import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY directly to urBackend, which can
be undefined and later causes a crash in UrBackendClient when calling
this.apiKey.startsWith; update the sdk initialization to validate the
publishable key first (check import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY) and
throw a clear, early Error if it's missing or empty before calling urBackend (so
the UrBackendClient constructor never receives undefined).
In `@package.json`:
- Line 20: Multiple workspaces declare conflicting versions of the dependency
"@urbackend/sdk", causing inconsistent behavior; update every workspace's
package.json that lists "@urbackend/sdk" to the same versioning strategy (either
a single semver range such as ^0.4.1 or a consistent local file: reference) so
all workspaces resolve the same SDK source; ensure you change the root
package.json and all example/client/server package.json entries that reference
"@urbackend/sdk", run the monorepo package manager to regenerate the lockfile
(install) and verify builds/tests to confirm consistent resolution.
In `@packages/common/src/config/redis.js`:
- Around line 3-7: The module currently exports client, subscriber, and
publisher as null which causes null reference errors; change the module.exports
to provide real Redis connections (or an initialization API) instead of nulls:
implement and export an initializeRedis (or createRedisClients) function that
instantiates the Redis client, subscriber, and publisher (using your project's
Redis lib) and export getters like getClient, getSubscriber, getPublisher (or
export the initialized objects) so session.manager.js, refreshToken.js,
redisCaching.js and the Queue constructors receive valid connection objects
rather than null; ensure module.exports no longer contains { client: null,
subscriber: null, publisher: null } and that any code using
client/subscriber/publisher calls the initializer first or uses the getters.
In `@README.md`:
- Around line 278-296: Update the README's posts schema so it matches the
example app types: replace the flat authorId/authorName fields with an author
object (e.g., author: { id: string, name: string }) and add the missing image
metadata and tagging fields (width: number, height: number, tags: string[]) to
the documented post shape for the posts collection; ensure the documented keys
and types align exactly with examples/sdk-snapgrid/src/types/index.ts and
mention createdAt and likes as before.
---
Minor comments:
In `@examples/sdk-snapgrid/src/App.css`:
- Around line 985-989: The .sr-only utility uses the deprecated clip property;
update the .sr-only rule to remove clip: rect(0,0,0,0) and add an equivalent
clip-path rule (e.g., clip-path: inset(50%)) while keeping the existing
position, size, padding, margin, overflow and border values to preserve
visually-hidden behavior; locate the .sr-only CSS selector in App.css and
replace the clip declaration with the clip-path declaration for modern browsers.
In `@examples/sdk-snapgrid/src/components/MasonryGrid/MasonryGrid.tsx`:
- Line 18: In MasonryGrid.tsx update the hardcoded alt="Uploaded" on the <img>
used in the MasonryGrid component to provide per-image descriptive text (for
example use a property from the image item such as item.title, item.description,
or item.altText) or, if the image is purely decorative, set alt="" (empty
string) and appropriate aria/presentation attributes; modify the JSX that
renders the image (the <img> with alt="Uploaded") to use the image-specific
value or an empty alt accordingly.
In `@examples/sdk-snapgrid/src/components/Navbar/Navbar.module.css`:
- Around line 45-50: The keyframes name pulseMark violates
keyframes-name-pattern; rename the animation identifier to kebab-case (e.g.,
pulse-mark) and update the animation property usage (animation: pulse-mark 3s
ease-in-out infinite;) and the `@keyframes` declaration (`@keyframes` pulse-mark {
... }) so both references match the new kebab-case name (update any other
occurrences of pulseMark in the stylesheet).
In `@examples/sdk-snapgrid/src/index.css`:
- Line 1: Update the stylesheet to satisfy stylelint: move or reformat the font
import so it's a valid top-level `@import` (use `@import`
"https://fonts.googleapis.com/..." or ensure the existing `@import` is the very
first statement), normalize all CSS keyword casing to lowercase (fix the token
flagged around the rule in the file, e.g., change any 'None', 'Transparent',
'Important', etc. to 'none', 'transparent', '!important'), and replace the
deprecated clip usage in the .sr-only rule with a modern equivalent (remove
clip: rect(...) and add clip-path: inset(50%) plus a -webkit-clip-path fallback
while keeping the rest of the .sr-only positioning/visibility declarations
intact).
In `@examples/sdk-snapgrid/src/pages/HomePage.tsx`:
- Around line 40-44: The gallery images in HomePage (the <img> with className
"post-card-image" using src `https://picsum.photos/300/40${i}`) currently have
alt="" which hides them from screen readers; update the alt to a short
descriptive label (e.g., use the card title if available or a fallback like
"Gallery image {i+1}" using the loop index variable i) so each image has
meaningful alt text while keeping it brief and relevant.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 303fd215-7982-4c71-bb44-9e6f3339543d
⛔ Files ignored due to path filters (7)
examples/sdk-snapgrid/package-lock.jsonis excluded by!**/package-lock.jsonexamples/sdk-snapgrid/public/favicon.svgis excluded by!**/*.svgexamples/sdk-snapgrid/public/icons.svgis excluded by!**/*.svgexamples/sdk-snapgrid/src/assets/hero.pngis excluded by!**/*.pngexamples/sdk-snapgrid/src/assets/react.svgis excluded by!**/*.svgexamples/sdk-snapgrid/src/assets/vite.svgis excluded by!**/*.svgpackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (33)
README.mdapps/public-api/.gitignoreapps/public-api/src/app.jsapps/public-api/src/controllers/storage.controller.jsapps/public-api/src/routes/storage.jsexamples/sdk-snapgrid/.gitignoreexamples/sdk-snapgrid/README.mdexamples/sdk-snapgrid/eslint.config.jsexamples/sdk-snapgrid/index.htmlexamples/sdk-snapgrid/package.jsonexamples/sdk-snapgrid/src/App.cssexamples/sdk-snapgrid/src/App.tsxexamples/sdk-snapgrid/src/components/Hero/Hero.module.cssexamples/sdk-snapgrid/src/components/Hero/Hero.tsxexamples/sdk-snapgrid/src/components/MasonryGrid/MasonryGrid.module.cssexamples/sdk-snapgrid/src/components/MasonryGrid/MasonryGrid.tsxexamples/sdk-snapgrid/src/components/Navbar/Navbar.module.cssexamples/sdk-snapgrid/src/components/Navbar/Navbar.tsxexamples/sdk-snapgrid/src/hooks/usePosts.tsexamples/sdk-snapgrid/src/hooks/useUpload.tsexamples/sdk-snapgrid/src/index.cssexamples/sdk-snapgrid/src/lib/urbackend.tsexamples/sdk-snapgrid/src/main.tsxexamples/sdk-snapgrid/src/pages/HomePage.tsxexamples/sdk-snapgrid/src/services/postsService.tsexamples/sdk-snapgrid/src/services/sdk.tsexamples/sdk-snapgrid/src/types/index.tsexamples/sdk-snapgrid/tsconfig.app.jsonexamples/sdk-snapgrid/tsconfig.jsonexamples/sdk-snapgrid/tsconfig.node.jsonexamples/sdk-snapgrid/vite.config.tspackage.jsonpackages/common/src/config/redis.js
| let images = []; | ||
|
|
||
| app.post("/api/images", (req, res) => { | ||
| const { url } = req.body; | ||
|
|
||
| if (!url) { | ||
| return res.status(400).json({ error: "URL required" }); | ||
| } | ||
|
|
||
| images.push({ | ||
| id: Date.now(), | ||
| url, | ||
| }); | ||
|
|
||
| res.json({ success: true }); | ||
| }); | ||
|
|
||
| app.get("/api/images", (req, res) => { | ||
| res.json({ images }); | ||
| }); |
There was a problem hiding this comment.
Bound the public in-memory image store.
/api/images is unauthenticated and images never evicts old entries, so a simple POST flood can grow the array until the process runs out of memory. Date.now() also isn't collision-safe for IDs if uploads land in the same millisecond.
🛡️ Safer in-memory fallback
+ const { randomUUID } = require('crypto');
- let images = [];
+ const MAX_IMAGES = 500;
+ let images = [];
app.post("/api/images", (req, res) => {
const { url } = req.body;
@@
- images.push({
- id: Date.now(),
- url,
- });
+ images.push({ id: randomUUID(), url });
+ if (images.length > MAX_IMAGES) {
+ images.shift();
+ }📝 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.
| let images = []; | |
| app.post("/api/images", (req, res) => { | |
| const { url } = req.body; | |
| if (!url) { | |
| return res.status(400).json({ error: "URL required" }); | |
| } | |
| images.push({ | |
| id: Date.now(), | |
| url, | |
| }); | |
| res.json({ success: true }); | |
| }); | |
| app.get("/api/images", (req, res) => { | |
| res.json({ images }); | |
| }); | |
| const { randomUUID } = require('crypto'); | |
| const MAX_IMAGES = 500; | |
| let images = []; | |
| app.post("/api/images", (req, res) => { | |
| const { url } = req.body; | |
| if (!url) { | |
| return res.status(400).json({ error: "URL required" }); | |
| } | |
| images.push({ id: randomUUID(), url }); | |
| if (images.length > MAX_IMAGES) { | |
| images.shift(); | |
| } | |
| res.json({ success: true }); | |
| }); | |
| app.get("/api/images", (req, res) => { | |
| res.json({ images }); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/public-api/src/app.js` around lines 81 - 100, The public in-memory store
images (used by app.post("/api/images") and returned by app.get("/api/images"))
is unbounded and uses Date.now() for ids which can collide; fix by adding a hard
cap and eviction policy (e.g., MAX_IMAGES constant and drop oldest entries when
pushing new ones) and replace Date.now() id generation with a collision-safe
generator (e.g., a UUID or an incrementing counter variable) and validate/limit
the incoming URL size before accepting to prevent memory exhaustion from large
payloads. Ensure these changes are applied inside the POST handler that
currently pushes to images and that the GET handler still returns the bounded
images array.
| return res.json({ | ||
| signedUrl: `http://localhost:1235/api/storage/upload-direct?fileName=${fileName}`, | ||
| token: "dummy-token", | ||
| filePath: fileName, | ||
| }); |
There was a problem hiding this comment.
Normalize controller responses to the required API contract
These changed endpoints return mixed shapes ({signedUrl...}, {ok:true}, {error:...}), which breaks the controller response contract expected in this repo.
As per coding guidelines, All API endpoints return: { success: bool, data: {}, message: "" }. Use AppError class for errors — never raw throw, never expose MongoDB errors to client.
Also applies to: 321-325, 344-349, 358-359
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/public-api/src/controllers/storage.controller.js` around lines 311 -
315, The controller currently returns mixed response shapes (e.g., direct
res.json({signedUrl...}), res.json({ok:true}), and raw error objects) which
violates the API contract; update the response payloads in the storage
controller handlers that call res.json at/around the signedUrl block, the
ok:true block, and the error blocks so they always return { success: boolean,
data: object, message: string } — e.g., replace res.json({signedUrl...,
token..., filePath...}) with res.json({ success: true, data: { signedUrl, token,
filePath }, message: "" }), replace res.json({ ok: true }) with res.json({
success: true, data: {}, message: "" }), and on failures use the AppError class
and pass the error to next() (do not expose raw DB errors or throw raw errors)
so all error responses follow { success: false, data: {}, message:
"user-friendly message" } while keeping existing identifiers like signedUrl,
fileName, filePath, and token intact.
| export const sdk = urBackend({ | ||
| apiKey: import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY, | ||
| baseUrl: import.meta.env.VITE_URBACKEND_URL, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "sdk.ts" | grep -E "snapgrid|examples"Repository: geturbackend/urBackend
Length of output: 110
🏁 Script executed:
cat examples/sdk-snapgrid/src/services/sdk.tsRepository: geturbackend/urBackend
Length of output: 246
🏁 Script executed:
rg -l "@urbackend/sdk" --type ts --type jsRepository: geturbackend/urBackend
Length of output: 296
🏁 Script executed:
find . -type f -name "*.ts" -o -name "*.js" | xargs grep -l "urBackend" | head -10Repository: geturbackend/urBackend
Length of output: 537
🏁 Script executed:
find . -path "*/node_modules" -prune -o -type d -name "sdk" -printRepository: geturbackend/urBackend
Length of output: 86
🏁 Script executed:
fd -t f "index" packages/@urbackend/sdk/src --type fRepository: geturbackend/urBackend
Length of output: 181
🏁 Script executed:
ls -la packages/@urbackend/sdk/Repository: geturbackend/urBackend
Length of output: 138
🏁 Script executed:
ls -la packages/Repository: geturbackend/urBackend
Length of output: 233
🏁 Script executed:
cat package.json | grep -A 20 "dependencies"Repository: geturbackend/urBackend
Length of output: 206
🏁 Script executed:
cat examples/sdk-snapgrid/package.json | grep -A 20 "@urbackend"Repository: geturbackend/urBackend
Length of output: 670
🏁 Script executed:
cat examples/sdk-kanban/client/src/lib/ub.jsRepository: geturbackend/urBackend
Length of output: 1363
🏁 Script executed:
cat examples/sdk-kanban/server/index.js | head -30Repository: geturbackend/urBackend
Length of output: 1003
🏁 Script executed:
cat examples/sdk-snapgrid/src/lib/urbackend.tsRepository: geturbackend/urBackend
Length of output: 196
🏁 Script executed:
rg "urBackend\(" -A 5 --type ts --type js | head -60Repository: geturbackend/urBackend
Length of output: 3443
🏁 Script executed:
cat sdks/urbackend-sdk/src/index.tsRepository: geturbackend/urBackend
Length of output: 725
🏁 Script executed:
cat sdks/urbackend-sdk/src/client.ts | head -80Repository: geturbackend/urBackend
Length of output: 2142
🏁 Script executed:
rg "class UrBackendClient" -A 20 sdks/urbackend-sdk/src/Repository: geturbackend/urBackend
Length of output: 1555
Fail fast when the publishable key is missing.
On line 4, import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY can be undefined. The SDK's UrBackendClient constructor calls this.apiKey.startsWith(...) without validation, which will crash at runtime with TypeError: Cannot read property 'startsWith' of undefined.
Suggested fix
import urBackend from "@urbackend/sdk";
+const apiKey = import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY;
+if (!apiKey) {
+ throw new Error("Missing VITE_URBACKEND_PUBLISHABLE_KEY");
+}
+
export const sdk = urBackend({
- apiKey: import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY,
+ apiKey,
baseUrl: import.meta.env.VITE_URBACKEND_URL,
});📝 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.
| export const sdk = urBackend({ | |
| apiKey: import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY, | |
| baseUrl: import.meta.env.VITE_URBACKEND_URL, | |
| import urBackend from "@urbackend/sdk"; | |
| const apiKey = import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY; | |
| if (!apiKey) { | |
| throw new Error("Missing VITE_URBACKEND_PUBLISHABLE_KEY"); | |
| } | |
| export const sdk = urBackend({ | |
| apiKey, | |
| baseUrl: import.meta.env.VITE_URBACKEND_URL, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/sdk-snapgrid/src/services/sdk.ts` around lines 3 - 5, The code
passes import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY directly to urBackend,
which can be undefined and later causes a crash in UrBackendClient when calling
this.apiKey.startsWith; update the sdk initialization to validate the
publishable key first (check import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY) and
throw a clear, early Error if it's missing or empty before calling urBackend (so
the UrBackendClient constructor never receives undefined).
| "concurrently": "^9.2.1" | ||
| }, | ||
| "dependencies": { | ||
| "@urbackend/sdk": "^0.4.0", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n '"@urbackend/sdk"' --glob 'package.json'Repository: geturbackend/urBackend
Length of output: 422
Consolidate @urbackend/sdk versions across workspaces.
The version is split across multiple workspaces:
package.json(root):^0.4.0examples/sdk-snapgrid/package.json:^0.4.1examples/sdk-kanban/client/package.json:^0.2.8examples/sdk-kanban/server/package.json:file:../../../sdks/urbackend-sdk(local)
With three different version ranges installed, the monorepo risks inconsistent SDK behavior depending on dependency resolution order. Align to a single version range across all workspaces (or use local file references consistently where appropriate).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` at line 20, Multiple workspaces declare conflicting versions of
the dependency "@urbackend/sdk", causing inconsistent behavior; update every
workspace's package.json that lists "@urbackend/sdk" to the same versioning
strategy (either a single semver range such as ^0.4.1 or a consistent local
file: reference) so all workspaces resolve the same SDK source; ensure you
change the root package.json and all example/client/server package.json entries
that reference "@urbackend/sdk", run the monorepo package manager to regenerate
the lockfile (install) and verify builds/tests to confirm consistent resolution.
| module.exports = { | ||
| client: null, | ||
| subscriber: null, | ||
| publisher: null, | ||
| }; No newline at end of file |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the redis.js file
cat packages/common/src/config/redis.jsRepository: geturbackend/urBackend
Length of output: 177
🏁 Script executed:
# Find all files importing this redis module
rg "from.*redis\.js|require.*redis\.js" --type js --type tsRepository: geturbackend/urBackend
Length of output: 48
🏁 Script executed:
# Look for auth-related redis usage patterns
rg "project:auth:refresh:session|project:auth:oauth:state" --type js --type tsRepository: geturbackend/urBackend
Length of output: 345
🏁 Script executed:
# Search for where redis client is initialized or assigned values
rg "redis\.client|redis\.subscriber|redis\.publisher" --type js --type ts -A 2Repository: geturbackend/urBackend
Length of output: 48
🏁 Script executed:
# Look for BullMQ queue initialization patterns
rg "new Queue|Bull\(" --type js --type ts -A 2 -B 1Repository: geturbackend/urBackend
Length of output: 939
🏁 Script executed:
# Look for where 'connection' variable comes from in queue files
rg "connection.*redis|redis.*connection|import.*redis" packages/common/src/queues/ --type js --type ts -B 3 -A 1Repository: geturbackend/urBackend
Length of output: 895
🏁 Script executed:
# Check the queue files to see how they import the redis config
head -20 packages/common/src/queues/webhookQueue.js
head -20 packages/common/src/queues/emailQueue.jsRepository: geturbackend/urBackend
Length of output: 1354
🏁 Script executed:
# Find all usages of the redis module exports
rg "\.client|\.subscriber|\.publisher" packages/common/src/ --type js --type ts -B 2 -A 2Repository: geturbackend/urBackend
Length of output: 412
🏁 Script executed:
# Look for session/auth related files
fd "session|auth" packages/common/src/ --type f | head -20Repository: geturbackend/urBackend
Length of output: 207
🏁 Script executed:
# Check session.manager.js to see how it uses redis
cat packages/common/src/utils/session.manager.jsRepository: geturbackend/urBackend
Length of output: 3393
🏁 Script executed:
# Check the auth controller to see if it uses redis
cat packages/common/src/controllers/userAuth.controller.js | head -50Repository: geturbackend/urBackend
Length of output: 153
🏁 Script executed:
# Look for where getRefreshSessionKey is used
rg "getRefreshSessionKey|project:auth:refresh:session" --type js --type ts -B 2 -A 2Repository: geturbackend/urBackend
Length of output: 3303
🏁 Script executed:
# Check if there's any redis client initialization or setup elsewhere
rg "redis\s*=|redis\." --type js --type ts | grep -v node_modules | head -30Repository: geturbackend/urBackend
Length of output: 2897
🏁 Script executed:
# Look for try-catch or null checks around redis usage
rg "redis\.client|connection\." packages/common/src/ --type js --type ts -B 3 -A 3Repository: geturbackend/urBackend
Length of output: 5195
Redis module exports null values, causing immediate runtime failures on all session and queue operations.
Exporting { client: null, subscriber: null, publisher: null } causes null reference errors when code attempts to call methods like .get(), .set(), .smembers(), and .sadd(). This breaks:
- Session persistence:
session.manager.jscallsredis.get(),redis.set(),redis.smembers(),redis.srem() - Token refresh flow:
refreshToken.jscallsredis.set()andredis.sadd() - Cache operations:
redisCaching.jscallsredis.get(),redis.set(),redis.del() - BullMQ queues: webhookQueue, emailQueue, authEmailQueue pass null connection to Queue constructor
Per coding guidelines, this violates the requirement to preserve session persistence—"Existing sessions will break."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/common/src/config/redis.js` around lines 3 - 7, The module currently
exports client, subscriber, and publisher as null which causes null reference
errors; change the module.exports to provide real Redis connections (or an
initialization API) instead of nulls: implement and export an initializeRedis
(or createRedisClients) function that instantiates the Redis client, subscriber,
and publisher (using your project's Redis lib) and export getters like
getClient, getSubscriber, getPublisher (or export the initialized objects) so
session.manager.js, refreshToken.js, redisCaching.js and the Queue constructors
receive valid connection objects rather than null; ensure module.exports no
longer contains { client: null, subscriber: null, publisher: null } and that any
code using client/subscriber/publisher calls the initializer first or uses the
getters.
|
@Priyanka0205-CSE after resolving the coderabbit suggested issues |
|
@coderabbitai |
|
Tip For best results, initiate chat on the files or code changes.
📋 Changes IntroducedNew Example App (
Backend Changes (
Other Changes
🚧 Blockers Before Merge
✅ When to Merge?Not yet. At minimum, the following must be fixed first:
|
|
Hi @yash-pouranik, Thanks for the feedback Thanks for pointing it out I’ll update the PR accordingly. |
| console.log("✅ Storage routes updated - auth removed"); | ||
| router.post('/upload-request', requestUpload); | ||
| router.post('/upload-confirm', confirmUpload); | ||
| router.put('/upload-direct', uploadDirect); | ||
| router.get('/file/:fileName', serveFile); |
There was a problem hiding this comment.
why did u make changes in these files - under apps/*
???
@Priyanka0205-CSE
| let images = []; | ||
|
|
||
| app.post("/api/images", (req, res) => { | ||
| const { url } = req.body; | ||
|
|
||
| if (!url) { | ||
| return res.status(400).json({ error: "URL required" }); | ||
| } | ||
|
|
||
| images.push({ | ||
| id: Date.now(), | ||
| url, | ||
| }); | ||
|
|
||
| res.json({ success: true }); | ||
| }); | ||
|
|
||
| app.get("/api/images", (req, res) => { | ||
| res.json({ images }); | ||
| }); |
There was a problem hiding this comment.
Bro what is this???
Why a single change in these file?
you were only told to create example in /examples
then why changes in these files???
| module.exports = { | ||
| client: null, | ||
| subscriber: null, | ||
| publisher: null, | ||
| }; No newline at end of file |
There was a problem hiding this comment.
what is this???
I mean I tooo suggest everyone to use AI but this is total childish ness
There was a problem hiding this comment.
I went beyond the required scope and made changes in apps/* and packages/*, which I shouldn’t have done since the task was only to work inside /examples
I’ll revert those changes and keep the implementation limited to the /examples directory as expected.
Sorry about that
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
examples/sdk-snapgrid/src/components/Navbar/Navbar.tsx (2)
102-113:⚠️ Potential issue | 🟠 Major | ⚡ Quick winLogout control is a non-interactive
div— inaccessible to keyboard users.The clickable element at lines 102–113 uses a
divwithonClick, which cannot be focused or activated via keyboard. Replace it with a semantic<button>(matching the Login button pattern already used in this component).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/sdk-snapgrid/src/components/Navbar/Navbar.tsx` around lines 102 - 113, The avatar logout control currently uses a non-interactive div (className styles.avatar) with an onClick handler calling sdk.auth.logout() and setUser(null); replace that div with a semantic <button type="button"> (matching the Login button pattern used elsewhere) keeping the same className and the async onClick logic, ensure the img child remains, add an accessible name (aria-label="Log out" or visually-hidden text) so keyboard users can focus and activate it, and preserve styling so behavior and appearance remain unchanged.
36-43:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
sdk.auth.getUser()does not exist on the SDK's auth module.Per
examples/sdk-snapgrid/src/services/sdk.tsandsdks/urbackend-sdk/src/modules/auth.ts, the correct method to fetch the current user issdk.auth.me().getUser()is not defined, socheckUserwill always throw anduserwill remainnullforever — making the avatar branch unreachable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/sdk-snapgrid/src/components/Navbar/Navbar.tsx` around lines 36 - 43, The checkUser function currently calls the nonexistent sdk.auth.getUser(), causing it to always throw; update checkUser to call sdk.auth.me() instead, await the result and pass it to setUser so the user state is populated (preserving the existing try/catch logic around the call), ensuring the avatar branch becomes reachable; locate the checkUser function and replace sdk.auth.getUser() with sdk.auth.me() and keep setUser(currentUser) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/sdk-snapgrid/src/components/Navbar/Navbar.tsx`:
- Around line 18-24: The Navbar accepts an onUploadClick prop and defines
UploadIcon but never uses them, so add an Upload button in the Actions area that
renders the UploadIcon and calls onUploadClick; modify the Navbar component's
Actions section (where the mobile search toggle and login/avatar are rendered)
to include a button element (or existing Button component) with an onClick
handler bound to onUploadClick, accessible aria-label (e.g., "Upload"), and the
UploadIcon as its child so the upload trigger appears and wires up correctly.
---
Duplicate comments:
In `@examples/sdk-snapgrid/src/components/Navbar/Navbar.tsx`:
- Around line 102-113: The avatar logout control currently uses a
non-interactive div (className styles.avatar) with an onClick handler calling
sdk.auth.logout() and setUser(null); replace that div with a semantic <button
type="button"> (matching the Login button pattern used elsewhere) keeping the
same className and the async onClick logic, ensure the img child remains, add an
accessible name (aria-label="Log out" or visually-hidden text) so keyboard users
can focus and activate it, and preserve styling so behavior and appearance
remain unchanged.
- Around line 36-43: The checkUser function currently calls the nonexistent
sdk.auth.getUser(), causing it to always throw; update checkUser to call
sdk.auth.me() instead, await the result and pass it to setUser so the user state
is populated (preserving the existing try/catch logic around the call), ensuring
the avatar branch becomes reachable; locate the checkUser function and replace
sdk.auth.getUser() with sdk.auth.me() and keep setUser(currentUser) unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 48c78176-7ebf-4164-9c54-fda7184a401c
📒 Files selected for processing (1)
examples/sdk-snapgrid/src/components/Navbar/Navbar.tsx
| const UploadIcon = () => ( | ||
| <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"> | ||
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> | ||
| <polyline points="17 8 12 3 7 8" /> | ||
| <line x1="12" y1="3" x2="12" y2="15" /> | ||
| </svg> | ||
| ); |
There was a problem hiding this comment.
onUploadClick prop and UploadIcon are defined but never used — upload button is absent from the Navbar.
UploadIcon is declared (lines 18–24) and onUploadClick is accepted as a required prop (line 7, destructured at line 26), but the rendered Actions section (lines 83–115) contains only a mobile search toggle and the login/avatar — there is no button that calls onUploadClick or renders <UploadIcon />. Since the PR's primary purpose is image uploading, the Navbar is missing its core trigger entirely.
🐛 Proposed fix — add the Upload button to the Actions section
<div className={styles.actions}>
<button
className={styles.mobileSearch}
onClick={() => setMobileSearchOpen(v => !v)}
aria-label="Toggle search"
>
<SearchIcon />
</button>
+ <button
+ className={styles.uploadBtn}
+ onClick={onUploadClick}
+ aria-label="Upload image"
+ >
+ <UploadIcon />
+ <span>Upload</span>
+ </button>
{!user ? (Also applies to: 84-114
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/sdk-snapgrid/src/components/Navbar/Navbar.tsx` around lines 18 - 24,
The Navbar accepts an onUploadClick prop and defines UploadIcon but never uses
them, so add an Upload button in the Actions area that renders the UploadIcon
and calls onUploadClick; modify the Navbar component's Actions section (where
the mobile search toggle and login/avatar are rendered) to include a button
element (or existing Button component) with an onClick handler bound to
onUploadClick, accessible aria-label (e.g., "Upload"), and the UploadIcon as its
child so the upload trigger appears and wires up correctly.
|
Hi @yash-pouranik, |
|
@yash-pouranik |
|
why there are .jpg files in apps/public/src/upload?? |
|
@yash-pouranik |
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (3)
examples/sdk-snapgrid/src/services/sdk.ts (1)
3-5:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFail fast when the publishable key is missing.
On Line 4,
apiKeycan beundefined;UrBackendClientcallsstartsWithand can throw at runtime during initialization.Suggested fix
import urBackend from "@urbackend/sdk"; +const apiKey = import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY?.trim(); +if (!apiKey) { + throw new Error("Missing VITE_URBACKEND_PUBLISHABLE_KEY"); +} + export const sdk = urBackend({ - apiKey: import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY, + apiKey, baseUrl: import.meta.env.VITE_URBACKEND_URL, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/sdk-snapgrid/src/services/sdk.ts` around lines 3 - 5, The sdk initialization passes a possibly undefined apiKey to urBackend (which will create UrBackendClient that calls startsWith), so add a fail-fast check before calling urBackend: read import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY into a local constant (apiKey), and if it's falsy throw a clear Error like "Missing VITE_URBACKEND_PUBLISHABLE_KEY publishable key" so initialization halts immediately; then pass that validated apiKey into urBackend when creating the exported sdk.examples/sdk-snapgrid/src/components/Hero/Hero.module.css (1)
63-305:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winNormalize remaining keyframe names to kebab-case.
Several names still violate the lint pattern (e.g.,
ambient-breatheSm,heroFadeUp,dotPulse,fadeIn,scrollDrop) and should be renamed along with all animation references.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/sdk-snapgrid/src/components/Hero/Hero.module.css` around lines 63 - 305, Rename all camelCase/mixed keyframe identifiers to kebab-case and update every reference: change `@keyframes` ambient-breatheSm -> `@keyframes` ambient-breathe-sm and update .ambientLeft { animation-name: ambient-breatheSm } to use ambient-breathe-sm; change `@keyframes` heroFadeUp -> `@keyframes` hero-fade-up and update .content { animation: heroFadeUp ... } to hero-fade-up; change `@keyframes` dotPulse -> `@keyframes` dot-pulse and update .eyebrowDot { animation: dotPulse ... } to dot-pulse; change `@keyframes` fadeIn -> `@keyframes` fade-in and update .scrollHint { animation: fadeIn ... } to fade-in; change `@keyframes` scrollDrop -> `@keyframes` scroll-drop and update .scrollLine { animation: scrollDrop ... } to scroll-drop; ensure every `@keyframes` declaration and every CSS property (animation, animation-name) that referenced ambient-breatheSm, heroFadeUp, dotPulse, fadeIn, or scrollDrop is updated consistently.examples/sdk-snapgrid/src/hooks/usePosts.ts (1)
10-21:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard
loadagainst stale async responses.An older request can still resolve last and overwrite newer search/category results (Lines 10–21), causing stale UI state.
Proposed fix
-import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; ... export function usePosts(category: Category, search: string) { + const requestIdRef = useRef(0); const [posts, setPosts] = useState<Post[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); const load = useCallback(async () => { + const requestId = ++requestIdRef.current; setLoading(true); setError(null); try { const data = await fetchPosts(category, search); + if (requestId !== requestIdRef.current) return; setPosts(data); } catch (e) { + if (requestId !== requestIdRef.current) return; setError('Failed to load posts. Please try again.'); } finally { + if (requestId !== requestIdRef.current) return; setLoading(false); } }, [category, search]);Also applies to: 23-26
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/sdk-snapgrid/src/hooks/usePosts.ts` around lines 10 - 21, The load callback can be overwritten by out-of-order async responses; fix by introducing a request-tracking guard (e.g., a requestId ref or AbortController) that you increment/create before awaiting fetchPosts and capture in the closure, then only call setPosts/setError/setLoading if the captured id matches the current ref (or the signal isn't aborted). Apply the same pattern to any other async updater in this file (the other fetch block referenced around setPosts/setError) so stale responses cannot overwrite newer category/search results.
🧹 Nitpick comments (2)
examples/sdk-snapgrid/src/hooks/useUpload.ts (1)
12-26: ⚡ Quick winPrevent overlapping uploads from corrupting progress state.
Line 12 currently allows concurrent
upload(...)calls, which can interleavesetProgressupdates and produce inconsistent stage/percent messaging.Proposed fix
-import { useState } from 'react'; +import { useRef, useState } from 'react'; ... export function useUpload() { + const inFlightRef = useRef(false); const [progress, setProgress] = useState<UploadProgress>({ stage: 'idle', percent: 0, message: '', }); const upload = async (payload: UploadPayload) => { + if (inFlightRef.current) return; + inFlightRef.current = true; setProgress({ stage: 'uploading', percent: 0, message: 'Uploading your creation…' }); try { await simulateUpload(payload, (pct) => { if (pct < 80) { setProgress({ stage: 'uploading', percent: pct, message: 'Uploading your creation…' }); } else { setProgress({ stage: 'processing', percent: pct, message: 'Processing & optimising…' }); } }); setProgress({ stage: 'done', percent: 100, message: 'Published successfully!' }); } catch { setProgress({ stage: 'error', percent: 0, message: 'Upload failed. Please retry.' }); + } finally { + inFlightRef.current = false; } };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/sdk-snapgrid/src/hooks/useUpload.ts` around lines 12 - 26, The upload function currently allows concurrent calls that interleave setProgress updates; add a reentrancy guard (e.g., an isUploading boolean/ref) inside the hook so upload(UploadPayload) returns early when an upload is already in flight, set isUploading=true at the start of upload and clear it in a finally block so it is always reset, and ensure simulateUpload is awaited inside that guarded region and that the catch still sets the error progress via setProgress; reference the upload function, simulateUpload callback, and setProgress to locate where to add the guard and the finally reset.examples/sdk-snapgrid/src/components/Hero/Hero.tsx (1)
4-15: ⚡ Quick winAlign prop contract with
handleScrollbehavior.
onScrollToGalleryis required (Line 5), so the fallback branch in Lines 13–15 is dead code. Either make the prop optional or remove the fallback path.Proposed fix (keep fallback, make prop optional)
interface HeroProps { - onScrollToGallery: () => void; + onScrollToGallery?: () => void; onUploadClick: () => void; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/sdk-snapgrid/src/components/Hero/Hero.tsx` around lines 4 - 15, The Hero component's prop contract is inconsistent with handleScroll: either remove the fallback or make onScrollToGallery optional; update the HeroProps interface to declare onScrollToGallery?: () => void (leave onUploadClick as required) so the existing handleScroll check remains valid, leaving the Hero React.FC and handleScroll implementation unchanged; ensure any callers of Hero are updated where necessary to handle the now-optional onScrollToGallery prop.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/sdk-snapgrid/src/App.css`:
- Line 7: Fix the three Stylelint errors: update the `@import` statement (the
`@import` rule at top of file) to use plain import notation without the url()
wrapper; find the font-family declaration referenced around line 44 and ensure
the generic family token is lowercase (e.g., change "Sans-Serif" ->
"sans-serif"); and locate uses of the deprecated property name "clip"
(occurrences around lines 985–990) and replace them with "clip-path" while
preserving the same value syntax or converting values to a valid clip-path form.
In `@examples/sdk-snapgrid/src/components/Navbar/Navbar.module.css`:
- Around line 45-51: The keyframe name pulseMark violates kebab-case; rename the
`@keyframes` identifier (pulseMark) to kebab-case (e.g., pulse-mark) and update
any references to it in this file—specifically the animation property that
currently uses "pulseMark"—so the `@keyframes` rule and the animation declaration
use the same kebab-case name.
In `@examples/sdk-snapgrid/src/index.css`:
- Around line 1-107: Replace the top-level `@import` url(...) with a quoted
`@import` string (change `@import` url('https://fonts.googleapis.com/...') to
`@import` "https://fonts.googleapis.com/...?display=swap"); normalize keyword case
in the :root font variables by lowercasing generic family names referenced in
--font-display and --font-body (e.g., change Georgia -> georgia, ensure
system-ui and sans-serif are lowercase); and modernize the .sr-only rule by
removing the deprecated clip property and using clip-path (e.g., replace clip:
rect(0,0,0,0); with clip-path: inset(50%); while preserving position, width,
height, overflow and border to maintain accessibility hiding behavior).
In `@examples/sdk-snapgrid/src/pages/HomePage.tsx`:
- Around line 40-44: The gallery images rendered with className
"post-card-image" in HomePage currently use empty alt attributes; update the img
alt to a meaningful, descriptive string (e.g., include the gallery index
variable i and any available post/title metadata) so the element is accessible —
replace alt="" with something like a constructed description using i and the
associated post title or caption, ensuring this change is applied where the
image is rendered in the HomePage component.
In `@examples/sdk-snapgrid/src/types/index.ts`:
- Around line 26-29: UploadPayload.category currently accepts the filter-only
value 'all' via the Category type; update the types so UploadPayload uses a
category type that excludes 'all' (either remove 'all' from the exported
Category union or create a new UploadCategory/NonFilterCategory type and use
that in the UploadPayload interface). Change the reference in the UploadPayload
declaration to the new/adjusted type (e.g., replace Category with
UploadCategory) and ensure any places constructing UploadPayload use only valid
upload categories.
---
Duplicate comments:
In `@examples/sdk-snapgrid/src/components/Hero/Hero.module.css`:
- Around line 63-305: Rename all camelCase/mixed keyframe identifiers to
kebab-case and update every reference: change `@keyframes` ambient-breatheSm ->
`@keyframes` ambient-breathe-sm and update .ambientLeft { animation-name:
ambient-breatheSm } to use ambient-breathe-sm; change `@keyframes` heroFadeUp ->
`@keyframes` hero-fade-up and update .content { animation: heroFadeUp ... } to
hero-fade-up; change `@keyframes` dotPulse -> `@keyframes` dot-pulse and update
.eyebrowDot { animation: dotPulse ... } to dot-pulse; change `@keyframes` fadeIn
-> `@keyframes` fade-in and update .scrollHint { animation: fadeIn ... } to
fade-in; change `@keyframes` scrollDrop -> `@keyframes` scroll-drop and update
.scrollLine { animation: scrollDrop ... } to scroll-drop; ensure every
`@keyframes` declaration and every CSS property (animation, animation-name) that
referenced ambient-breatheSm, heroFadeUp, dotPulse, fadeIn, or scrollDrop is
updated consistently.
In `@examples/sdk-snapgrid/src/hooks/usePosts.ts`:
- Around line 10-21: The load callback can be overwritten by out-of-order async
responses; fix by introducing a request-tracking guard (e.g., a requestId ref or
AbortController) that you increment/create before awaiting fetchPosts and
capture in the closure, then only call setPosts/setError/setLoading if the
captured id matches the current ref (or the signal isn't aborted). Apply the
same pattern to any other async updater in this file (the other fetch block
referenced around setPosts/setError) so stale responses cannot overwrite newer
category/search results.
In `@examples/sdk-snapgrid/src/services/sdk.ts`:
- Around line 3-5: The sdk initialization passes a possibly undefined apiKey to
urBackend (which will create UrBackendClient that calls startsWith), so add a
fail-fast check before calling urBackend: read
import.meta.env.VITE_URBACKEND_PUBLISHABLE_KEY into a local constant (apiKey),
and if it's falsy throw a clear Error like "Missing
VITE_URBACKEND_PUBLISHABLE_KEY publishable key" so initialization halts
immediately; then pass that validated apiKey into urBackend when creating the
exported sdk.
---
Nitpick comments:
In `@examples/sdk-snapgrid/src/components/Hero/Hero.tsx`:
- Around line 4-15: The Hero component's prop contract is inconsistent with
handleScroll: either remove the fallback or make onScrollToGallery optional;
update the HeroProps interface to declare onScrollToGallery?: () => void (leave
onUploadClick as required) so the existing handleScroll check remains valid,
leaving the Hero React.FC and handleScroll implementation unchanged; ensure any
callers of Hero are updated where necessary to handle the now-optional
onScrollToGallery prop.
In `@examples/sdk-snapgrid/src/hooks/useUpload.ts`:
- Around line 12-26: The upload function currently allows concurrent calls that
interleave setProgress updates; add a reentrancy guard (e.g., an isUploading
boolean/ref) inside the hook so upload(UploadPayload) returns early when an
upload is already in flight, set isUploading=true at the start of upload and
clear it in a finally block so it is always reset, and ensure simulateUpload is
awaited inside that guarded region and that the catch still sets the error
progress via setProgress; reference the upload function, simulateUpload
callback, and setProgress to locate where to add the guard and the finally
reset.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 09af4939-7a3d-4c4c-be6e-ebe9930f49e2
⛔ Files ignored due to path filters (6)
examples/sdk-snapgrid/package-lock.jsonis excluded by!**/package-lock.jsonexamples/sdk-snapgrid/public/favicon.svgis excluded by!**/*.svgexamples/sdk-snapgrid/public/icons.svgis excluded by!**/*.svgexamples/sdk-snapgrid/src/assets/hero.pngis excluded by!**/*.pngexamples/sdk-snapgrid/src/assets/react.svgis excluded by!**/*.svgexamples/sdk-snapgrid/src/assets/vite.svgis excluded by!**/*.svg
📒 Files selected for processing (26)
.gitignoreexamples/sdk-snapgrid/.gitignoreexamples/sdk-snapgrid/README.mdexamples/sdk-snapgrid/eslint.config.jsexamples/sdk-snapgrid/index.htmlexamples/sdk-snapgrid/package.jsonexamples/sdk-snapgrid/src/App.cssexamples/sdk-snapgrid/src/App.tsxexamples/sdk-snapgrid/src/components/Hero/Hero.module.cssexamples/sdk-snapgrid/src/components/Hero/Hero.tsxexamples/sdk-snapgrid/src/components/MasonryGrid/MasonryGrid.module.cssexamples/sdk-snapgrid/src/components/MasonryGrid/MasonryGrid.tsxexamples/sdk-snapgrid/src/components/Navbar/Navbar.module.cssexamples/sdk-snapgrid/src/components/Navbar/Navbar.tsxexamples/sdk-snapgrid/src/hooks/usePosts.tsexamples/sdk-snapgrid/src/hooks/useUpload.tsexamples/sdk-snapgrid/src/index.cssexamples/sdk-snapgrid/src/main.tsxexamples/sdk-snapgrid/src/pages/HomePage.tsxexamples/sdk-snapgrid/src/services/postsService.tsexamples/sdk-snapgrid/src/services/sdk.tsexamples/sdk-snapgrid/src/types/index.tsexamples/sdk-snapgrid/tsconfig.app.jsonexamples/sdk-snapgrid/tsconfig.jsonexamples/sdk-snapgrid/tsconfig.node.jsonexamples/sdk-snapgrid/vite.config.ts
✅ Files skipped from review due to trivial changes (1)
- examples/sdk-snapgrid/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
- examples/sdk-snapgrid/tsconfig.json
- examples/sdk-snapgrid/eslint.config.js
- examples/sdk-snapgrid/tsconfig.node.json
| Fonts: Fraunces (display) + DM Sans (body) | ||
| ───────────────────────────────────────────────────────────────────────────── */ | ||
|
|
||
| @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,600;0,9..144,700;1,9..144,300;1,9..144,400&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,300&display=swap'); |
There was a problem hiding this comment.
Three Stylelint [error]-level violations will block CI.
🛠️ Proposed fixes
Line 7 — remove the url() wrapper from @import (import-notation):
-@import url('https://fonts.googleapis.com/css2?family=Fraunces:...');
+@import 'https://fonts.googleapis.com/css2?family=Fraunces:...';Line 44 — lowercase the generic font family keyword (value-keyword-case):
- --font-display: 'Fraunces', Georgia, serif;
+ --font-display: 'Fraunces', georgia, serif;Line 988 — replace deprecated clip with clip-path (property-no-deprecated):
.sr-only {
position: absolute; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden;
- clip: rect(0,0,0,0); border: 0;
+ clip-path: inset(50%); white-space: nowrap; border: 0;
}Also applies to: 44-44, 985-990
🧰 Tools
🪛 Stylelint (17.10.0)
[error] 7-7: Expected "url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,600;0,9..144,700;1,9..144,300;1,9..144,400&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,300&display=swap')" to be "'https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,600;0,9..144,700;1,9..144,300;1,9..144,400&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,300&display=swap'" (import-notation)
(import-notation)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/sdk-snapgrid/src/App.css` at line 7, Fix the three Stylelint errors:
update the `@import` statement (the `@import` rule at top of file) to use plain
import notation without the url() wrapper; find the font-family declaration
referenced around line 44 and ensure the generic family token is lowercase
(e.g., change "Sans-Serif" -> "sans-serif"); and locate uses of the deprecated
property name "clip" (occurrences around lines 985–990) and replace them with
"clip-path" while preserving the same value syntax or converting values to a
valid clip-path form.
| @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700;900&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap'); | ||
|
|
||
| :root { | ||
| --black: #080808; | ||
| --black-2: #0f0f0f; | ||
| --black-3: #161616; | ||
| --black-4: #1e1e1e; | ||
| --black-5: #2a2a2a; | ||
| --gold: #c9a84c; | ||
| --gold-light: #e8c97e; | ||
| --gold-dim: #8a6f2e; | ||
| --gold-glow: rgba(201, 168, 76, 0.18); | ||
| --gold-glow-strong: rgba(201, 168, 76, 0.35); | ||
| --white: #f5f0e8; | ||
| --white-dim: #b0a898; | ||
| --white-muted: #5a554d; | ||
| --radius-sm: 6px; | ||
| --radius-md: 12px; | ||
| --radius-lg: 20px; | ||
| --radius-xl: 32px; | ||
| --shadow-card: 0 4px 24px rgba(0,0,0,0.6), 0 1px 4px rgba(0,0,0,0.4); | ||
| --shadow-gold: 0 0 32px rgba(201, 168, 76, 0.22), 0 0 8px rgba(201, 168, 76, 0.1); | ||
| --shadow-nav: 0 8px 40px rgba(0,0,0,0.7); | ||
| --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); | ||
| --transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1); | ||
| --font-display: 'Playfair Display', Georgia, serif; | ||
| --font-body: 'DM Sans', system-ui, sans-serif; | ||
| --nav-height: 72px; | ||
| } | ||
|
|
||
| *, *::before, *::after { | ||
| box-sizing: border-box; | ||
| margin: 0; | ||
| padding: 0; | ||
| } | ||
|
|
||
| html { | ||
| scroll-behavior: smooth; | ||
| } | ||
|
|
||
| body { | ||
| background-color: var(--black); | ||
| color: var(--white); | ||
| font-family: var(--font-body); | ||
| font-size: 15px; | ||
| line-height: 1.6; | ||
| -webkit-font-smoothing: antialiased; | ||
| -moz-osx-font-smoothing: grayscale; | ||
| min-height: 100vh; | ||
| overflow-x: hidden; | ||
| } | ||
|
|
||
| a { | ||
| color: inherit; | ||
| text-decoration: none; | ||
| } | ||
|
|
||
| button { | ||
| cursor: pointer; | ||
| font-family: var(--font-body); | ||
| border: none; | ||
| background: none; | ||
| } | ||
|
|
||
| input, textarea { | ||
| font-family: var(--font-body); | ||
| } | ||
|
|
||
| img { | ||
| display: block; | ||
| max-width: 100%; | ||
| } | ||
|
|
||
| ::-webkit-scrollbar { | ||
| width: 6px; | ||
| } | ||
| ::-webkit-scrollbar-track { | ||
| background: var(--black-2); | ||
| } | ||
| ::-webkit-scrollbar-thumb { | ||
| background: var(--black-5); | ||
| border-radius: 3px; | ||
| } | ||
| ::-webkit-scrollbar-thumb:hover { | ||
| background: var(--gold-dim); | ||
| } | ||
|
|
||
| ::selection { | ||
| background: var(--gold-glow-strong); | ||
| color: var(--gold-light); | ||
| } | ||
|
|
||
| /* Utility */ | ||
| .sr-only { | ||
| position: absolute; | ||
| width: 1px; | ||
| height: 1px; | ||
| padding: 0; | ||
| margin: -1px; | ||
| overflow: hidden; | ||
| clip: rect(0,0,0,0); | ||
| border: 0; | ||
| } | ||
| html { | ||
| scroll-behavior: smooth; | ||
| scroll-padding-top: 100px; | ||
| } No newline at end of file |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat examples/sdk-snapgrid/src/index.cssRepository: geturbackend/urBackend
Length of output: 2302
🏁 Script executed:
find . -name ".stylelintrc*" -o -name "stylelint.config.*" | head -20Repository: geturbackend/urBackend
Length of output: 86
🏁 Script executed:
cat .stylelintrc.jsonRepository: geturbackend/urBackend
Length of output: 1426
Resolve current stylelint violations in this stylesheet.
Two flagged items are present: import notation (Line 1) and keyword case (Line 26). Additionally, the deprecated clip property (Line 101) should be modernized for accessibility best practices.
Suggested fix
-@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700;900&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap');
+@import 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700;900&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap';
@@
- --font-display: 'Playfair Display', Georgia, serif;
+ --font-display: 'Playfair Display', georgia, serif;
@@
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
- clip: rect(0,0,0,0);
+ clip-path: inset(50%);
+ white-space: nowrap;
border: 0;
}📝 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 url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700;900&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap'); | |
| :root { | |
| --black: #080808; | |
| --black-2: #0f0f0f; | |
| --black-3: #161616; | |
| --black-4: #1e1e1e; | |
| --black-5: #2a2a2a; | |
| --gold: #c9a84c; | |
| --gold-light: #e8c97e; | |
| --gold-dim: #8a6f2e; | |
| --gold-glow: rgba(201, 168, 76, 0.18); | |
| --gold-glow-strong: rgba(201, 168, 76, 0.35); | |
| --white: #f5f0e8; | |
| --white-dim: #b0a898; | |
| --white-muted: #5a554d; | |
| --radius-sm: 6px; | |
| --radius-md: 12px; | |
| --radius-lg: 20px; | |
| --radius-xl: 32px; | |
| --shadow-card: 0 4px 24px rgba(0,0,0,0.6), 0 1px 4px rgba(0,0,0,0.4); | |
| --shadow-gold: 0 0 32px rgba(201, 168, 76, 0.22), 0 0 8px rgba(201, 168, 76, 0.1); | |
| --shadow-nav: 0 8px 40px rgba(0,0,0,0.7); | |
| --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| --transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1); | |
| --font-display: 'Playfair Display', Georgia, serif; | |
| --font-body: 'DM Sans', system-ui, sans-serif; | |
| --nav-height: 72px; | |
| } | |
| *, *::before, *::after { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| html { | |
| scroll-behavior: smooth; | |
| } | |
| body { | |
| background-color: var(--black); | |
| color: var(--white); | |
| font-family: var(--font-body); | |
| font-size: 15px; | |
| line-height: 1.6; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| a { | |
| color: inherit; | |
| text-decoration: none; | |
| } | |
| button { | |
| cursor: pointer; | |
| font-family: var(--font-body); | |
| border: none; | |
| background: none; | |
| } | |
| input, textarea { | |
| font-family: var(--font-body); | |
| } | |
| img { | |
| display: block; | |
| max-width: 100%; | |
| } | |
| ::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--black-2); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--black-5); | |
| border-radius: 3px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--gold-dim); | |
| } | |
| ::selection { | |
| background: var(--gold-glow-strong); | |
| color: var(--gold-light); | |
| } | |
| /* Utility */ | |
| .sr-only { | |
| position: absolute; | |
| width: 1px; | |
| height: 1px; | |
| padding: 0; | |
| margin: -1px; | |
| overflow: hidden; | |
| clip: rect(0,0,0,0); | |
| border: 0; | |
| } | |
| html { | |
| scroll-behavior: smooth; | |
| scroll-padding-top: 100px; | |
| } | |
| `@import` 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700;900&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap'; | |
| :root { | |
| --black: `#080808`; | |
| --black-2: `#0f0f0f`; | |
| --black-3: `#161616`; | |
| --black-4: `#1e1e1e`; | |
| --black-5: `#2a2a2a`; | |
| --gold: `#c9a84c`; | |
| --gold-light: `#e8c97e`; | |
| --gold-dim: `#8a6f2e`; | |
| --gold-glow: rgba(201, 168, 76, 0.18); | |
| --gold-glow-strong: rgba(201, 168, 76, 0.35); | |
| --white: `#f5f0e8`; | |
| --white-dim: `#b0a898`; | |
| --white-muted: `#5a554d`; | |
| --radius-sm: 6px; | |
| --radius-md: 12px; | |
| --radius-lg: 20px; | |
| --radius-xl: 32px; | |
| --shadow-card: 0 4px 24px rgba(0,0,0,0.6), 0 1px 4px rgba(0,0,0,0.4); | |
| --shadow-gold: 0 0 32px rgba(201, 168, 76, 0.22), 0 0 8px rgba(201, 168, 76, 0.1); | |
| --shadow-nav: 0 8px 40px rgba(0,0,0,0.7); | |
| --transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| --transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1); | |
| --font-display: 'Playfair Display', georgia, serif; | |
| --font-body: 'DM Sans', system-ui, sans-serif; | |
| --nav-height: 72px; | |
| } | |
| *, *::before, *::after { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| html { | |
| scroll-behavior: smooth; | |
| } | |
| body { | |
| background-color: var(--black); | |
| color: var(--white); | |
| font-family: var(--font-body); | |
| font-size: 15px; | |
| line-height: 1.6; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| a { | |
| color: inherit; | |
| text-decoration: none; | |
| } | |
| button { | |
| cursor: pointer; | |
| font-family: var(--font-body); | |
| border: none; | |
| background: none; | |
| } | |
| input, textarea { | |
| font-family: var(--font-body); | |
| } | |
| img { | |
| display: block; | |
| max-width: 100%; | |
| } | |
| ::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--black-2); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--black-5); | |
| border-radius: 3px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--gold-dim); | |
| } | |
| ::selection { | |
| background: var(--gold-glow-strong); | |
| color: var(--gold-light); | |
| } | |
| /* Utility */ | |
| .sr-only { | |
| position: absolute; | |
| width: 1px; | |
| height: 1px; | |
| padding: 0; | |
| margin: -1px; | |
| overflow: hidden; | |
| clip-path: inset(50%); | |
| white-space: nowrap; | |
| border: 0; | |
| } | |
| html { | |
| scroll-behavior: smooth; | |
| scroll-padding-top: 100px; | |
| } |
🧰 Tools
🪛 Stylelint (17.10.0)
[error] 1-1: Expected "url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700;900&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap')" to be "'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700;900&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&display=swap'" (import-notation)
(import-notation)
[error] 101-101: Deprecated property "clip" (property-no-deprecated)
(property-no-deprecated)
[error] 26-26: Expected "Georgia" to be "georgia" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/sdk-snapgrid/src/index.css` around lines 1 - 107, Replace the
top-level `@import` url(...) with a quoted `@import` string (change `@import`
url('https://fonts.googleapis.com/...') to `@import`
"https://fonts.googleapis.com/...?display=swap"); normalize keyword case in the
:root font variables by lowercasing generic family names referenced in
--font-display and --font-body (e.g., change Georgia -> georgia, ensure
system-ui and sans-serif are lowercase); and modernize the .sr-only rule by
removing the deprecated clip property and using clip-path (e.g., replace clip:
rect(0,0,0,0); with clip-path: inset(50%); while preserving position, width,
height, overflow and border to maintain accessibility hiding behavior).
| export interface UploadPayload { | ||
| title: string; | ||
| category: Category; | ||
| tags: string[]; |
There was a problem hiding this comment.
Exclude 'all' from upload payload category.
UploadPayload.category on Line 28 should not accept the filter-only value 'all'.
Suggested fix
export interface UploadPayload {
title: string;
- category: Category;
+ category: Exclude<Category, 'all'>;
tags: string[];
file: File;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/sdk-snapgrid/src/types/index.ts` around lines 26 - 29,
UploadPayload.category currently accepts the filter-only value 'all' via the
Category type; update the types so UploadPayload uses a category type that
excludes 'all' (either remove 'all' from the exported Category union or create a
new UploadCategory/NonFilterCategory type and use that in the UploadPayload
interface). Change the reference in the UploadPayload declaration to the
new/adjusted type (e.g., replace Category with UploadCategory) and ensure any
places constructing UploadPayload use only valid upload categories.
|
@yash-pouranik please review :) |
|
Okay lastly, I will pull this to my local then test and then will see what next. |
There was a problem hiding this comment.
PR Review Feedback
Hi @Priyanka0205-CSE , thanks for putting this initial PR together! The UI foundation looks good and it's awesome that you've integrated the storage module.
However, looking back at the original issue specifications, there are several core requirements missing. The goal of this example is to be a comprehensive showcase of the urbackend-sdk (specifically Auth, Database, and Storage working together under RLS).
Here are the items that need to be addressed before we can merge this:
1. Replace Local Fetch with SDK Database Methods
Currently, App.tsx makes direct HTTP fetch requests to a local API (http://localhost:1235/api/images).
- Action: Please completely remove the local
fetchcalls. Usesdk.db.getAll<Post>('posts')to fetch the feed andsdk.db.insert('posts', data)to create new posts.
2. Implement Authentication & Profiling
The issue explicitly requested Social Auth and a "My Gallery" feature. Right now, the app is completely public with no user context.
- Action: Implement authentication using the SDK (
sdk.auth.loginor the Social Auth methods). - Action: Create a "My Gallery" / Creator Dashboard where users can view, edit, or delete only their own uploads.
3. Align with the Requested Schema (posts collection)
The current implementation only saves an image URL. The spec requires a more detailed schema to showcase database capabilities.
- Action: The collection should be named
posts. - Action: The data model needs to include:
imageUrl(String),prompt(String),category(String), andownerId(managed by urBackend).
4. Implement Interactive Modals & Filtering
The goal is to build a prompt-sharing platform, not just an image viewer.
- Action: Clicking an image in the masonry grid should open a detailed modal showing the full prompt and a "Copy Prompt" button.
- Action: Add smart filtering (by category or keywords in the prompt).
5. UI/UX Expectations (Toasts & Alerts)
Currently, the app uses native browser alert("Upload successful!").
- Action: Replace native alerts with beautiful Toast notifications (e.g., using Shadcn UI or Sonner) for uploads, auth changes, and copy-prompt actions.
- Action: Ensure there are beautiful "Empty States" if the gallery or the user's private dashboard has no posts yet.
6. README Updates
Once the above is implemented, please ensure your README.md documents:
- The exact
postscollection schema. - The RLS (Row-Level Security) configuration required in the dashboard (Read:
public-read, Write:private).
Summary
This is a great start, but we really need it to align with the "High Quality" spec outlined in the issue. Since you mentioned having experience with auth and dashboards, I'm confident you can knock this out! Please refer to the SDK documentation for the exact syntax for Auth and DB operations.
Let me know if you get stuck on any of the SDK methods!
|
@yash-pouranik |
🚀 Overview
This PR implements an image upload and gallery feature using the urBackend SDK.
✨ Features
🛠️ Technical Details
/api/images)🧪 Testing
📌 Improvements
.gitignoreto exclude uploaded files from repository📷 Screenshots
Summary by CodeRabbit
New Features
Documentation