-
Notifications
You must be signed in to change notification settings - Fork 0
Feat: Add email field and export input type for inviteMemberSchema #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughLarge-scale code style standardization across 80+ files, converting double quotes to single quotes, adding trailing semicolons, and normalizing formatting while maintaining functional equivalence. Includes minor UI enhancements such as form state synchronization and dropdown menu refinements. Changes
Sequence DiagramsequenceDiagram
participant User
participant Form as CreateProjectForm
participant State as Form State
participant Handler as onSubmit Handler
User->>Form: Select Template
Form->>State: form.setValue('templateId', selectedTemplateId)
Note over State: Sync selected template<br/>to form state
State-->>Form: Update form value
User->>Form: Submit Form
Form->>Handler: Trigger onSubmit
Handler->>Handler: Build payload with synced templateId
Handler-->>User: Success/Error Response
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes The changes follow a highly consistent, repetitive pattern (quote normalization, semicolons, whitespace) across the vast majority of files, reducing per-file complexity. However, the substantial breadth (~80+ files) and a handful of functional modifications ( Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (9)
src/components/models/templates/TemplateCard.tsx (1)
13-21: Clickable Card lacks keyboard access and role (a11y).Add role, tabIndex, and keyboard activation; include focus-visible styles.
Apply this diff:
<Card - className={cn( - 'cursor-pointer hover:border-primary transition-colors', + className={cn( + 'cursor-pointer hover:border-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2', isSelected && 'border-primary ring-2 ring-primary ring-offset-2', className )} onClick={() => onSelect(template.id)} + role="button" + tabIndex={0} + aria-label={`Select template ${template.name}`} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onSelect(template.id); + } + }} {...props} >src/contexts/LoadingContext.tsx (1)
1-1: Add ‘use client’ directive to client components using React hooksAdd the following as the first line in src/contexts/LoadingContext.tsx (and any other .tsx/.ts files that use useState/useContext/useEffect, e.g. src/hooks/use-action.ts):
+'use client'; import React, { createContext, useContext, useState, ReactNode } from 'react';src/middleware.ts (1)
19-25: Matcher vs. runtime check mismatch makes/projectsunprotected without a tokenYour
config.matcherapplies middleware to/projects/:path*, but the no‑token branch redirects only for paths starting with/protected. Result: unauthenticated access to/projectsslips through. Also, the..._protectedsuffix in the first matcher looks unintended.Fix by aligning matcher with intent and removing the redundant runtime path check. Two options:
Option A (protect both /protected and /projects):
export async function middleware(request: NextRequest) { const token = request.cookies.get('auth_token')?.value; if (token) { try { await verifyJWT(token); return NextResponse.next(); } catch (error) { console.error('JWT verification failed:', error); return NextResponse.redirect(new URL('/signin', request.url)); } } - // If no token, redirect to sign-in page for protected routes - // You might want to define which routes are protected - if (request.nextUrl.pathname.startsWith('/protected')) { - return NextResponse.redirect(new URL('/signin', request.url)); - } - - return NextResponse.next(); + // If no token, redirect (these routes are scoped by config.matcher) + return NextResponse.redirect(new URL('/signin', request.url)); } export const config = { - matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)_protected', '/projects/:path*'] + matcher: ['/protected/:path*', '/projects/:path*'] };Option B (protect only /protected):
- matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)_protected', '/projects/:path*'] + matcher: ['/protected/:path*']Choose one based on intended protected surface. Avoid mixing broad matchers with ad‑hoc
pathnamechecks—let matcher define scope, and always redirect on missing token within middleware.src/app/globals.css (1)
73-77: Applyingborderto all elements is likely unintended; useborder-borderinstead.
@apply border;on*adds a 1px border to every element. Typically only the border color token is applied globally.Apply this change and consider setting body colors:
@layer base { * { - @apply border; + @apply border-border; } + body { + @apply bg-background text-foreground; + } }src/hooks/use-debounce.ts (1)
7-7: Fix debounce default to respect 0ms delays.delay || 500 coerces 0 to 500. Use nullish coalescing.
- const timer = setTimeout(() => setDebouncedValue(value), delay || 500); + const timer = setTimeout(() => setDebouncedValue(value), delay ?? 500);src/components/models/projects/InviteMemberDialog.tsx (1)
20-20: Critical: Incorrect type usage - Zod schema used as TypeScript type.
inviteMemberSchemais a Zod schema object and cannot be used directly as a TypeScript type parameter. According to the PR objectives, anInviteMemberInputtype alias should be exported from@/actions/collaboration.Apply this diff to use the correct type:
- const form = useForm<inviteMemberSchema>({ + const form = useForm<InviteMemberInput>({Alternatively, if
InviteMemberInputis not exported, use:- const form = useForm<inviteMemberSchema>({ + const form = useForm<z.infer<typeof inviteMemberSchema>>({And add the import:
+import { z } from 'zod';README.md (1)
1-148: Mismatch between PR description and changes
PR description claims inviteMemberSchema insrc/actions/collaboration.tswas updated (added required email field and exported schema plus inferred type), but that file is unchanged in this PR. Include those edits or update the description to match the actual changes.src/components/models/projects/CreateProjectForm.tsx (1)
33-35: Wrap setValue in useEffect to prevent unnecessary re-renders.Calling
form.setValueunconditionally on every render will mark the form as dirty on each render cycle and may trigger unnecessary re-renders. This should be wrapped in auseEffectwithselectedTemplateIdas a dependency.Apply this diff to fix the issue:
+import { useEffect } from 'react'; + export function CreateProjectForm({ selectedTemplateId, setDialogOpen }: CreateProjectFormProps) { const form = useForm<CreateProjectFormValues>({ mode: 'onChange', resolver: zodResolver(CreateProjectSchema), reValidateMode: 'onChange', defaultValues: { title: '', type: ProjectType.article, templateId: selectedTemplateId } }); - // Update default value for templateId when selectedTemplateId changes - // This ensures the form reflects the currently selected template in the dialog - form.setValue('templateId', selectedTemplateId); + // Update templateId when selectedTemplateId changes + useEffect(() => { + form.setValue('templateId', selectedTemplateId); + }, [form, selectedTemplateId]);src/components/common/Filter/FilterDropdown.tsx (1)
29-37: State synchronization gap: URL changes won't update local state.The
useEffectsyncsselectedValueto URL params, but doesn't sync URL changes back toselectedValue. If the user navigates using browser back/forward buttons, the URL will update butselectedValuewill remain stale, causing the dropdown to display an incorrect value.Consider updating the effect to sync bidirectionally or removing
searchParamsfrom the dependency array:useEffect(() => { + // Sync URL params to local state + const currentValue = searchParams.get(paramName)?.toString() || ''; + if (currentValue !== selectedValue) { + setSelectedValue(currentValue); + return; + } + + // Sync local state to URL params const params = new URLSearchParams(searchParams); if (selectedValue) { params.set(paramName, selectedValue); } else { params.delete(paramName); } replace(`${pathname}?${params.toString()}`); }, [selectedValue, paramName, pathname, replace, searchParams]);
♻️ Duplicate comments (1)
src/components/models/auth/SignupForm.tsx (1)
25-26: Password helper text vs schema. Helper says 8 chars; schema (current main) enforces 6. See my auth.ts comment proposing min(8) to align.Also applies to: 81-85
🧹 Nitpick comments (20)
src/actions/projects/deleteProject/schema.ts (1)
3-5: Tighten input validation and disallow unknown fields.Current
id: z.string()accepts empty strings and the object allows extraneous keys.Consider:
-export const deleteProjectSchema = z.object({ - id: z.string() -}); +export const deleteProjectSchema = z + .object({ + // If IDs are UUIDs, prefer `.uuid()`; otherwise at least trim + min length: + // id: z.string().uuid() + id: z.string().trim().min(1, 'id is required'), + }) + .strict();If your IDs are UUIDs/CUIDs, swap
.trim().min(1)for.uuid()or.cuid2(). Please confirm the expected ID format to choose the correct validator.src/lib/jwt.ts (1)
7-15: Dev stub looks fine; consider clearer prod guard/message.No functional change; OK. Optionally, tighten the guard and clarify the error to avoid confusion about Edge vs non‑Edge runtimes.
Proposed tweak:
- if (APP_ENV === 'production') { - throw new Error('verifyJWT should not be called in production in an Edge environment'); - } + if (APP_ENV === 'production') { + throw new Error('verifyJWT is a dev-only stub and must never run in production'); + }Also verify APP_ENV reliably equals 'production' across all deploy targets so this stub cannot execute in prod by misconfiguration.
src/components/layouts/Header.tsx (1)
44-49: Avoid nesting interactive elements (Link wrapping Button).
Next.js Link typically renders an anchor, resulting in . This is invalid HTML and hurts accessibility/keyboard/screen-reader behavior. Prefer rendering the Button as the anchor via asChild.Apply this diff:
- <Link href='/profile'> - <Button variant='ghost' size='sm' className='flex items-center gap-2'> - <User size={16} /> - Profile - </Button> - </Link> + <Button asChild variant='ghost' size='sm' className='flex items-center gap-2'> + <Link href='/profile'> + <User size={16} /> + Profile + </Link> + </Button>src/contexts/LoadingContext.tsx (1)
13-14: LGTM on formatting; consider memoizing context value.Current change is fine. Optionally memoize the value to avoid recreating the object on unrelated rerenders.
Apply within this block:
export function LoadingProvider({ children }: { children: ReactNode }) { const [isLoading, setLoading] = useState(false); - return <LoadingContext.Provider value={{ isLoading, setLoading }}>{children}</LoadingContext.Provider>; + const value = React.useMemo(() => ({ isLoading, setLoading }), [isLoading]); + return <LoadingContext.Provider value={value}>{children}</LoadingContext.Provider>; }Also update imports:
import React, { createContext, useContext, useMemo, useState, ReactNode } from 'react';CODE_OF_CONDUCT.md (1)
55-56: Unify “email” spellingUse a single form across the doc (prefer “email” over “e‑mail”) for consistency.
-Examples of representing our community include using an official e-mail address, +Examples of representing our community include using an official email address,src/components/common/Search/SearchInput.tsx (2)
20-28: Guard replace and avoid trailing “?”; depend on stable query stringPrevent redundant navigations and clean URLs. Compare current vs next query, and omit “?” when empty. Also depend on
searchParams.toString()to avoid identity churn.useEffect(() => { - const params = new URLSearchParams(searchParams); - if (debouncedSearchValue) { - params.set('search', debouncedSearchValue); - } else { - params.delete('search'); - } - replace(`${pathname}?${params.toString()}`); - }, [debouncedSearchValue, pathname, replace, searchParams]); + const nextParams = new URLSearchParams(searchParams); + if (debouncedSearchValue) { + nextParams.set('search', debouncedSearchValue); + } else { + nextParams.delete('search'); + } + const next = nextParams.toString(); + const current = searchParams.toString(); + if (next !== current) { + replace(next ? `${pathname}?${next}` : pathname); + } + }, [debouncedSearchValue, pathname, replace, searchParams.toString()]);Optional (keeps input in sync when navigating via back/forward or external links):
useEffect(() => { const fromUrl = searchParams.get('search') ?? ''; if (fromUrl !== searchValue) setSearchValue(fromUrl); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParams.toString()]);
17-17: Simplify initial state; avoid unnecessary toStringUse nullish coalescing;
get()already returnsstring | null.- const [searchValue, setSearchValue] = useState(searchParams.get('search')?.toString() || ''); + const [searchValue, setSearchValue] = useState(searchParams.get('search') ?? '');src/hooks/use-mobile.ts (3)
3-3: Deduplicate MOBILE_BREAKPOINT: import shared constant from src/lib/responsiveAvoid drift by reusing the exported constant.
-import * as React from 'react'; +import * as React from 'react'; +import { MOBILE_BREAKPOINT } from '../lib/responsive'; - -const MOBILE_BREAKPOINT = 768;Based on relevant code snippet (src/lib/responsive.ts).
9-15: Use mql.matches and add Safari fallback for MediaQueryList listenersAlign state updates with the media query and support older Safari.
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - }; - mql.addEventListener('change', onChange); - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - return () => mql.removeEventListener('change', onChange); + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); + const onChange = (e: MediaQueryListEvent) => setIsMobile(e.matches); + // Safari <14 fallback + if (typeof mql.addEventListener === 'function') { + mql.addEventListener('change', onChange); + } else { + // @ts-expect-error: older Safari support + mql.addListener(onChange); + } + setIsMobile(mql.matches); + return () => { + if (typeof mql.removeEventListener === 'function') { + mql.removeEventListener('change', onChange); + } else { + // @ts-expect-error: older Safari support + mql.removeListener(onChange); + } + };
6-6: Simplify state typing and returnNo need for boolean | undefined + double-bang; initialize false and return directly.
- const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined); + const [isMobile, setIsMobile] = React.useState(false); ... - return !!isMobile; + return isMobile;Also applies to: 18-18
src/lib/constants.ts (1)
3-3: Consider secure cookie naming and centralization.If you set Secure, Path=/, and no Domain, adopt the “__Host-” prefix for stronger cookie scoping; also consider centralizing cookie name + options in one export.
src/components/models/user/ProfileForm.tsx (1)
41-43: Accessibility: add aria-invalid/aria-describedby and announce errorsImprove screen reader support by linking inputs to error text and marking invalid state.
- <Input id='name' {...form.register('name')} /> - {form.formState.errors.name && <p className='text-sm text-red-500'>{form.formState.errors.name.message}</p>} + <Input + id='name' + aria-invalid={!!form.formState.errors.name} + aria-describedby={form.formState.errors.name ? 'name-error' : undefined} + {...form.register('name')} + /> + {form.formState.errors.name && ( + <p id='name-error' role='alert' className='text-sm text-red-500'> + {form.formState.errors.name.message} + </p> + )} ... - <Input id='email' type='email' {...form.register('email')} disabled /> - {form.formState.errors.email && <p className='text-sm text-red-500'>{form.formState.errors.email.message}</p>} + <Input + id='email' + type='email' + aria-invalid={!!form.formState.errors.email} + aria-describedby={form.formState.errors.email ? 'email-error' : undefined} + {...form.register('email')} + /* readOnly or disabled per chosen approach above */ + /> + {form.formState.errors.email && ( + <p id='email-error' role='alert' className='text-sm text-red-500'> + {form.formState.errors.email.message} + </p> + )}Also applies to: 46-47
src/actions/projects/createProject/logic.ts (1)
19-20: Prefer nullish coalescing for default content.Use ?? so empty strings are preserved intentionally and only null/undefined default to ''.
Apply:
- content: content || '' + content: content ?? ''src/components/pages/home/HomePageContainer.tsx (1)
26-26: Optional: surface the error to the user and use structured logging.Consider a toast/alert for UX and routing errors to a logger service instead of console.error.
src/actions/user.ts (2)
27-27: AwaitrevalidatePathfor deterministic cache invalidation.- revalidatePath('/profile'); + await revalidatePath('/profile');Also applies to: 64-64
44-45: Limit fetched fields to what you need (passwordHash) to reduce exposure.- const user = await prisma.user.findUnique({ - where: { id: userId } - }); + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { passwordHash: true } + });postcss.config.mjs (1)
4-5: Remove redundantautoprefixerentry from postcss.config.mjs
Tailwind v4’s@tailwindcss/postcssuses Lightning CSS for vendor prefixing, so explicitautoprefixercan be omitted unless you require custom browser targets.src/actions/collaboration.ts (3)
8-8: Normalize email; nonempty() is redundantRecommend trimming and lowercasing to avoid duplicate users due to casing/whitespace; .email() already rejects empty strings.
- email: z.string().email().nonempty(), + email: z.string().trim().toLowerCase().email(),
17-17: Fix lint: unused ctx.user parameterDestructured user is unused; rename to _user to satisfy lint rule.
- async ({ projectId, email, role }, { user }) => { + async ({ projectId, email, role }, { user: _user }) => { @@ - async ({ projectId, memberId }, { user }) => { + async ({ projectId, memberId }, { user: _user }) => { @@ - async ({ projectId, memberId, role }, { user }) => { + async ({ projectId, memberId, role }, { user: _user }) => { @@ - async ({ projectId }, { user }) => { + async ({ projectId }, { user: _user }) => {Also applies to: 46-46, 69-69, 93-93
26-34: Guard against duplicate membership (idempotent invite)Creating without checking may throw on unique (projectId,userId). Check first and no-op if already a member.
- await prisma.projectMember.create({ - data: { - projectId, - userId: existingUser.id, - role - } - }); + const existingMember = await prisma.projectMember.findUnique({ + where: { projectId_userId: { projectId, userId: existingUser.id } } + }); + if (!existingMember) { + await prisma.projectMember.create({ + data: { projectId, userId: existingUser.id, role } + }); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (68)
.prettierrc(1 hunks)CODE_OF_CONDUCT.md(2 hunks)README.md(2 hunks)eslint.config.mjs(1 hunks)postcss.config.mjs(1 hunks)src/actions/analytics.ts(1 hunks)src/actions/collaboration.ts(1 hunks)src/actions/projects/createProject/action.ts(1 hunks)src/actions/projects/createProject/logic.ts(1 hunks)src/actions/projects/createProject/schema.ts(1 hunks)src/actions/projects/deleteProject/schema.ts(1 hunks)src/actions/projects/getProjects.ts(1 hunks)src/actions/user.ts(1 hunks)src/app/(general)/page.tsx(1 hunks)src/app/globals.css(1 hunks)src/app/profile/page.tsx(1 hunks)src/components/common/ErrorBoundary.tsx(1 hunks)src/components/common/Filter/FilterDropdown.tsx(3 hunks)src/components/common/Search/SearchInput.tsx(3 hunks)src/components/common/Skeleton/FormSkeleton.tsx(1 hunks)src/components/common/Skeleton/TableSkeleton.tsx(3 hunks)src/components/layouts/Header.tsx(2 hunks)src/components/models/analytics/ProjectStats.tsx(1 hunks)src/components/models/auth/SigninForm.tsx(2 hunks)src/components/models/auth/SignupForm.tsx(2 hunks)src/components/models/projects/CreateProjectDialog.tsx(1 hunks)src/components/models/projects/CreateProjectForm.tsx(4 hunks)src/components/models/projects/DeleteProjectDialog.tsx(3 hunks)src/components/models/projects/ExportDialog.tsx(3 hunks)src/components/models/projects/InviteMemberDialog.tsx(3 hunks)src/components/models/projects/ProjectMembersTable.tsx(4 hunks)src/components/models/projects/ProjectsTable.tsx(2 hunks)src/components/models/templates/TemplateCard.tsx(1 hunks)src/components/models/templates/TemplateSelector.tsx(1 hunks)src/components/models/user/ChangePasswordForm.tsx(3 hunks)src/components/models/user/ProfileForm.tsx(2 hunks)src/components/pages/home/HomePageContainer.tsx(2 hunks)src/components/ui/alert.tsx(1 hunks)src/components/ui/card.tsx(1 hunks)src/components/ui/checkbox.tsx(1 hunks)src/components/ui/dialog.tsx(3 hunks)src/components/ui/dropdown-menu.tsx(7 hunks)src/components/ui/form.tsx(1 hunks)src/components/ui/input.tsx(1 hunks)src/components/ui/label.tsx(1 hunks)src/components/ui/select.tsx(5 hunks)src/components/ui/separator.tsx(1 hunks)src/components/ui/sheet.tsx(1 hunks)src/components/ui/sidebar.tsx(5 hunks)src/components/ui/skeleton.tsx(1 hunks)src/components/ui/table.tsx(1 hunks)src/components/ui/tabs.tsx(1 hunks)src/components/ui/tooltip.tsx(2 hunks)src/contexts/LoadingContext.tsx(1 hunks)src/hooks/use-debounce.ts(1 hunks)src/hooks/use-mobile.ts(1 hunks)src/lib/action.ts(4 hunks)src/lib/constants.ts(1 hunks)src/lib/export.ts(1 hunks)src/lib/jwt.ts(1 hunks)src/lib/permissions.ts(1 hunks)src/lib/prisma.ts(1 hunks)src/lib/responsive.ts(0 hunks)src/lib/utils.ts(1 hunks)src/lib/validations/auth.ts(1 hunks)src/lib/validations/projects.ts(1 hunks)src/middleware.ts(1 hunks)tailwind.config.js(1 hunks)
💤 Files with no reviewable changes (1)
- src/lib/responsive.ts
🧰 Additional context used
🧬 Code graph analysis (36)
src/components/models/projects/ProjectsTable.tsx (2)
src/components/common/Search/SearchInput.tsx (1)
SearchInput(12-38)src/components/common/Filter/FilterDropdown.tsx (1)
FilterDropdown(22-60)
src/actions/projects/createProject/action.ts (1)
src/lib/templates.ts (1)
projectTemplates(8-27)
src/components/ui/skeleton.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/ui/dialog.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/hooks/use-mobile.ts (1)
src/lib/responsive.ts (1)
MOBILE_BREAKPOINT(1-1)
src/actions/projects/createProject/logic.ts (3)
src/actions/projects/createProject/schema.ts (1)
CreateProjectInput(12-12)src/lib/result.ts (1)
Result(18-18)src/lib/prisma.ts (1)
prisma(7-7)
src/lib/constants.ts (1)
src/lib/env.ts (2)
IS_PRODUCTION(12-12)APP_ENV(12-12)
src/components/ui/separator.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/common/ErrorBoundary.tsx (1)
src/components/common/BasicAlert.tsx (1)
BasicAlert(5-21)
src/actions/projects/getProjects.ts (2)
src/lib/result.ts (1)
Result(18-18)src/lib/prisma.ts (1)
prisma(7-7)
src/components/models/projects/CreateProjectDialog.tsx (1)
src/components/models/templates/TemplateSelector.tsx (1)
TemplateSelector(9-22)
src/components/ui/label.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/models/auth/SigninForm.tsx (1)
src/lib/result.ts (1)
error(38-44)
src/actions/analytics.ts (2)
src/lib/action.ts (1)
protectedAction(67-98)src/lib/prisma.ts (1)
prisma(7-7)
src/components/ui/tabs.tsx (2)
src/components/common/Tabs.tsx (1)
Tabs(18-54)src/lib/utils.ts (1)
cn(4-6)
src/components/common/Skeleton/FormSkeleton.tsx (1)
src/components/ui/skeleton.tsx (1)
Skeleton(7-7)
src/components/ui/alert.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/ui/sheet.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/app/(general)/page.tsx (5)
src/lib/session.ts (1)
getSession(22-25)src/lib/async.ts (1)
safeAwait(3-10)src/actions/projects/getProjects.ts (1)
getProjects(11-26)src/actions/analytics.ts (1)
getProjectAnalytics(7-42)src/components/common/BasicAlert.tsx (1)
BasicAlert(5-21)
src/components/common/Search/SearchInput.tsx (1)
src/hooks/use-debounce.ts (1)
useDebounce(3-15)
src/actions/collaboration.ts (2)
src/lib/action.ts (1)
protectedAction(67-98)src/lib/prisma.ts (1)
prisma(7-7)
src/components/ui/card.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/lib/export.ts (1)
src/actions/projects/getProjects.ts (1)
getProjects(11-26)
src/components/ui/sidebar.tsx (2)
src/hooks/use-mobile.ts (1)
useIsMobile(5-19)src/lib/utils.ts (1)
cn(4-6)
src/components/ui/tooltip.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/ui/input.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/models/projects/DeleteProjectDialog.tsx (2)
src/components/ui/dialog.tsx (3)
DialogDescription(116-116)DialogHeader(118-118)DialogFooter(117-117)src/components/ui/button.tsx (1)
Button(56-56)
src/components/models/projects/InviteMemberDialog.tsx (2)
src/components/common/Form/FormInput.tsx (1)
FormInput(6-72)src/components/common/Form/FormSelect.tsx (1)
FormSelect(20-56)
src/actions/user.ts (2)
src/lib/action.ts (2)
authActionClient(33-65)getAuthenticatedUserId(100-106)src/lib/prisma.ts (1)
prisma(7-7)
src/components/ui/table.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/ui/select.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/ui/form.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/components/ui/checkbox.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
src/lib/action.ts (1)
src/lib/session.ts (1)
getSession(22-25)
src/components/models/user/ChangePasswordForm.tsx (1)
src/components/common/BasicAlert.tsx (1)
BasicAlert(5-21)
src/components/ui/dropdown-menu.tsx (1)
src/lib/utils.ts (1)
cn(4-6)
🪛 GitHub Actions: Lint and Build
src/contexts/LoadingContext.tsx
[error] 1-1: React useState (state hook) used in a server component. Mark the component tree as Client Component with 'use client' or refactor to avoid client-side hooks in server components.
src/components/models/user/ChangePasswordForm.tsx
[error] 10-10: Client components cannot use React useState. The component must be a Client Component (marked with 'use client').
🪛 GitHub Check: lint-and-build
src/components/models/projects/ProjectMembersTable.tsx
[warning] 1-1:
'useState' is defined but never used
src/app/(general)/page.tsx
[warning] 20-20:
'analyticsResult' is assigned a value but never used
[warning] 20-20:
'analyticsError' is assigned a value but never used
[warning] 6-6:
'ProjectStats' is defined but never used
[warning] 5-5:
'ExportDialog' is defined but never used
src/actions/collaboration.ts
[warning] 93-93:
'user' is defined but never used. Allowed unused args must match /^_/u
[warning] 69-69:
'user' is defined but never used. Allowed unused args must match /^_/u
[warning] 46-46:
'user' is defined but never used. Allowed unused args must match /^_/u
[warning] 17-17:
'user' is defined but never used. Allowed unused args must match /^_/u
🪛 LanguageTool
CODE_OF_CONDUCT.md
[grammar] ~20-~20: There might be a mistake here.
Context: ...empathy and kindness toward other people - Being respectful of differing opinions, ...
(QB_NEW_EN)
[grammar] ~21-~21: There might be a mistake here.
Context: ...ng opinions, viewpoints, and experiences - Giving and gracefully accepting construc...
(QB_NEW_EN)
[grammar] ~22-~22: There might be a mistake here.
Context: ...acefully accepting constructive feedback - Accepting responsibility and apologizing...
(QB_NEW_EN)
[grammar] ~24-~24: There might be a mistake here.
Context: ...akes, and learning from the experience - Focusing on what is best not just for us...
(QB_NEW_EN)
[grammar] ~32-~32: There might be a mistake here.
Context: ...ion or advances of any kind - Trolling, insulting or derogatory comments, and p...
(QB_NEW_EN)
[style] ~32-~32: Try using a synonym here to strengthen your wording.
Context: ...ind - Trolling, insulting or derogatory comments, and personal or political attacks - Pu...
(COMMENT_REMARK)
[grammar] ~109-~109: There might be a mistake here.
Context: ...inappropriate behavior, harassment of an individual, or aggression toward or disp...
(QB_NEW_EN)
🔇 Additional comments (54)
src/components/models/templates/TemplateSelector.tsx (1)
11-11: LGTM: formatting-only changeQuote normalization in JSX; no semantic impact. Ensure ESLint/Prettier config enforces this to avoid churn.
src/lib/permissions.ts (1)
1-1: LGTM — import style normalization only.No functional change; safe to merge.
src/actions/projects/deleteProject/schema.ts (1)
1-1: LGTM on import style.Consistent single quotes; no behavior change.
src/components/layouts/Header.tsx (1)
28-29: No behavior change; formatting is fine.
The multi-line formatting of errorMessage is equivalent to previous logic. LGTM.src/components/ui/sidebar.tsx (1)
1-677: LGTM! Formatting standardization applied correctly.The formatting changes across this file are consistent and correct. All modifications are style-related (double→single quotes, semicolons, explicit type literals) with no functional impact.
src/components/models/templates/TemplateCard.tsx (1)
27-27: Style-only change looks good.Consistent single quotes; no behavioral impact.
CODE_OF_CONDUCT.md (4)
20-26: Formatting-only changes look goodBullet wrap/punctuation adjustments preserve meaning.
30-37: LGTM on standards section tweaksLine-wrapping only; no semantic changes.
108-110: LGTM on permanent ban paragraphPure reflow; content unchanged.
117-128: All external links resolve with HTTP 200 Verified each URL returns 200 after following redirects; no dead links detected.src/components/common/Search/SearchInput.tsx (1)
1-1: LGTM on formatting updatesImports, directive, and className quoting changes look good.
Also applies to: 3-6, 35-35
src/app/globals.css (1)
1-2: Import statements and Tailwind configuration verified
Project uses tailwindcss v4.1.14 and tw-animate-css v1.3.8; no legacy@tailwinddirectives found.src/lib/utils.ts (1)
1-2: LGTM — style-only; no behavior change.Also applies to: 5-5
src/components/models/projects/CreateProjectDialog.tsx (1)
20-25: Verified: styling changes and schema/type exports all confirmed.The CreateProjectDialog styling changes are good with no behavioral impact. The PR objective is also complete:
inviteMemberSchemais properly defined insrc/actions/collaboration.ts(line 6–10) with email validation (z.string().email().nonempty()), andInviteMemberInputtype is correctly exported (line 12) and in use across the codebase.src/actions/projects/createProject/action.ts (1)
19-21: LGTM – formatting only. No functional change; keep as-is..prettierrc (1)
10-10: LGTM – config formatting. No semantic changes.src/lib/prisma.ts (1)
7-9: LGTM – singleton pattern intact. Matches common Next.js Prisma usage; no behavior change.src/components/common/Skeleton/TableSkeleton.tsx (1)
16-16: LGTM – formatting only. No runtime changes.Also applies to: 26-26
src/lib/validations/projects.ts (1)
7-7: LGTM – schema unchanged. Formatting only.Also applies to: 10-10
src/components/models/projects/ProjectsTable.tsx (1)
22-26: LGTM — formatting-only changes.Also applies to: 27-32, 53-58, 62-62
src/components/common/ErrorBoundary.tsx (1)
35-38: LGTM! Formatting changes only.The quote normalization aligns with the project-wide style standardization. No functional changes detected.
src/components/ui/tabs.tsx (1)
1-42: LGTM! Formatting standardization only.Quote normalization and semicolon additions with no functional changes. Aligns with project-wide style updates.
src/components/models/projects/DeleteProjectDialog.tsx (1)
1-67: LGTM! Formatting changes only.Quote standardization and minor text formatting. No behavioral changes detected.
tailwind.config.js (1)
3-7: LGTM! Configuration formatting update.Array formatting simplified with no semantic changes to Tailwind configuration.
src/components/ui/sheet.tsx (1)
1-103: LGTM! Formatting standardization only.Single-quote normalization and data-slot attribute updates with no functional changes. Component API remains unchanged.
eslint.config.mjs (1)
1-61: LGTM! Configuration formatting update.Quote standardization with no changes to ESLint rules or configuration behavior.
src/components/ui/checkbox.tsx (1)
1-29: LGTM! Formatting standardization only.Quote normalization and semicolon additions with no functional changes. Component API unchanged.
src/components/models/projects/InviteMemberDialog.tsx (1)
24-26: Confirm inviteMemberSchema includes email and exports InviteMemberInput
The schema in src/actions/collaboration.ts definesemail: z.string().email().nonempty()and exportsInviteMemberInput.src/app/profile/page.tsx (1)
7-16: LGTM: formatting-only changesrc/actions/collaboration.ts (1)
94-99: PII exposure check: returning member emailsConfirm that exposing each member’s email to all project members is intended and compliant. If not, drop email from selection or gate behind ADMIN.
src/components/ui/label.tsx (1)
11-14: LGTM: formatting-only; public API unchangedsrc/components/common/Skeleton/FormSkeleton.tsx (1)
9-16: LGTM: formatting-only changesrc/components/ui/separator.tsx (1)
1-28: LGTM: Consistent formatting updates.The changes apply consistent quote style and semicolon usage without altering functionality.
src/components/ui/dialog.tsx (1)
1-123: LGTM: Formatting standardization applied consistently.All changes are stylistic (quote normalization, semicolons) with no impact on component behavior or public API.
src/components/ui/input.tsx (1)
1-21: LGTM: Formatting updates only.The quote style and type literal changes maintain identical semantics.
src/components/models/user/ChangePasswordForm.tsx (1)
22-110: LGTM: Formatting standardization applied.All changes are quote style normalization and minor formatting adjustments without altering the component's logic or behavior.
src/components/ui/table.tsx (1)
1-75: LGTM: Consistent formatting updates.The changes normalize quote styles and data-slot attributes without affecting component functionality.
src/components/models/analytics/ProjectStats.tsx (1)
1-72: LGTM: Formatting updates maintain functionality.All changes are stylistic (quote normalization, attribute formatting) with no impact on the chart rendering or component behavior.
src/components/ui/tooltip.tsx (1)
1-48: LGTM: Formatting standardization.The changes apply consistent quote style and semicolons without altering component behavior.
README.md (1)
36-66: LGTM: Documentation improvements enhance clarity.The added code blocks make the installation steps more actionable and easier to follow.
src/components/models/projects/CreateProjectForm.tsx (2)
46-46: LGTM!The error message construction is correctly simplified to a single-line expression while preserving the same fallback chain.
57-57: LGTM!Formatting changes align with the project-wide style standardization.
Also applies to: 80-80
src/components/ui/skeleton.tsx (1)
1-7: LGTM!Formatting changes align with the project-wide style standardization (single quotes, consistent import/export formatting).
src/components/models/projects/ProjectMembersTable.tsx (1)
2-16: LGTM!Formatting changes (quote normalization, import restructuring) align with the project-wide style standardization. The queryKey array update and all other changes are purely stylistic with no behavioral impact.
Also applies to: 33-103, 106-106
src/actions/projects/getProjects.ts (1)
11-24: LGTM!Formatting changes align with the project-wide style standardization (single quotes, function signature formatting).
src/components/ui/select.tsx (1)
1-160: LGTM!Formatting changes align with the project-wide style standardization (single quotes for string literals, data-slot attributes, and type annotations).
src/components/ui/form.tsx (1)
1-138: LGTM!Formatting changes align with the project-wide style standardization (single quotes for imports, string literals, data-slot attributes, and type annotations). The signature change for
FormItemfromReact.ComponentProps<"div">toReact.ComponentProps<'div'>is purely stylistic.src/components/ui/card.tsx (1)
1-56: LGTM!Formatting changes align with the project-wide style standardization (single quotes for imports, data-slot attributes, and type annotations). All Card component signatures are consistently updated.
src/components/models/auth/SigninForm.tsx (1)
35-35: LGTM!Formatting changes align with the project-wide style standardization. The error message construction is correctly simplified to a single-line expression, and the button formatting is consistently updated.
Also applies to: 63-63
src/components/ui/alert.tsx (1)
1-48: LGTM! Formatting standardization applied correctly.All changes in this file are purely stylistic—converting double quotes to single quotes and adding trailing semicolons. The component logic and public API remain unchanged.
src/lib/export.ts (1)
1-29: LGTM! Formatting and type signature standardization.The changes maintain functional equivalence while applying single-quote conventions consistently. The export logic remains correct.
src/components/models/projects/ExportDialog.tsx (1)
24-36: Excellent refactor: export logic properly encapsulated.The new
handleExportfunction cleanly separates async export logic from the component's render flow, with proper loading states and error handling via toast notifications.src/components/ui/dropdown-menu.tsx (2)
150-162: Well-structured component additions and refactoring.The internal renaming to
DropMenuSeparatorwith an export alias maintains backwards compatibility while avoiding naming conflicts. The newDropdownMenuShortcutcomponent follows established patterns for displaying keyboard shortcuts in menus.
76-76: Consistent API enhancement: inset prop added.The
insetprop onDropdownMenuItemandDropdownMenuLabelfollows the existing pattern established inDropdownMenuSubTrigger, providing a uniform API for indented menu items.Also applies to: 139-139
| by: ['createdAt'], | ||
| _count: { | ||
| createdAt: true, | ||
| createdAt: true | ||
| }, | ||
| orderBy: { | ||
| createdAt: "asc", | ||
| }, | ||
| createdAt: 'asc' | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Monthly aggregation is incorrect — grouping by createdAt produces per-row buckets.
You need month-level aggregation. Two options:
Option A (Postgres, preferred — true month grouping in DB):
- const projectsByCreationMonth = await prisma.project.groupBy({
- by: ['createdAt'],
- _count: {
- createdAt: true
- },
- orderBy: {
- createdAt: 'asc'
- }
- });
+ const projectsByCreationMonth = await prisma.$queryRaw<
+ { month: Date; count: bigint }[]
+ >`SELECT date_trunc('month', "createdAt") AS month, COUNT(*)::bigint AS count
+ FROM "Project"
+ GROUP BY 1
+ ORDER BY 1 ASC`;- const formattedProjectsByCreationMonth = projectsByCreationMonth.map((item) => ({
- name: new Date(item.createdAt).toLocaleString('default', { month: 'short', year: 'numeric' }),
- value: item._count.createdAt
- }));
+ const formattedProjectsByCreationMonth = projectsByCreationMonth.map((row) => ({
+ name: new Date(row.month).toLocaleString('en', { month: 'short', year: 'numeric' }),
+ value: Number(row.count)
+ }));Option B (DB-agnostic — reduce in JS):
const dates = await prisma.project.findMany({ select: { createdAt: true } });
const monthMap = new Map<string, number>();
for (const { createdAt } of dates) {
const k = new Date(createdAt);
const key = `${k.getUTCFullYear()}-${k.getUTCMonth()}`; // YYYY-M
monthMap.set(key, (monthMap.get(key) ?? 0) + 1);
}
const formattedProjectsByCreationMonth = Array.from(monthMap.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, count]) => {
const [y, m] = key.split('-').map(Number);
const d = new Date(Date.UTC(y, m, 1));
return { name: d.toLocaleString('en', { month: 'short', year: 'numeric' }), value: count };
});Also applies to: 32-35
🤖 Prompt for AI Agents
In src/actions/analytics.ts around lines 18-25 (and also apply the same fix at
lines 32-35), the current grouping by createdAt yields per-row buckets; replace
this with month-level aggregation: for Postgres, change the query to group by a
date_trunc('month', createdAt) selection and order by that truncated month so
the DB returns counts per month (preferred); if DB-agnostic, fetch only
createdAt values and reduce in JS into a month-keyed map, then convert and sort
into the formatted month/count array as shown in the reviewer comment. Ensure
the result uses the month start (UTC) for keys and sorts chronologically before
formatting.
| title: z.string().trim().min(1, { message: 'Title is required' }).max(200, { message: 'Title is too long' }), | ||
| type: z.enum(ProjectType, { | ||
| message: "Invalid project type", | ||
| message: 'Invalid project type' | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify enum type: use z.nativeEnum if ProjectType is a TS enum
If ProjectType is a TS enum (as with Prisma), z.enum(ProjectType) will be incorrect; use z.nativeEnum(ProjectType).
Run to confirm the ProjectType shape:
🏁 Script executed:
#!/bin/bash
# Find where ProjectType is declared to verify if it's a TS enum or a const tuple
rg -nC2 --type=ts '\b(ProjectType)\b' src | sed -n '1,120p'
rg -nC2 'enum\s+ProjectType\b|export\s+const\s+ProjectType\s*=\s*\['Length of output: 2924
Change z.enum(ProjectType) to z.nativeEnum(ProjectType) at line 6
ProjectType is a Prisma-generated enum, not a string tuple. The correct Zod validator is z.nativeEnum(ProjectType), matching the pattern already used in src/lib/validations/projects.ts line 6. Replace:
type: z.enum(ProjectType, {
message: 'Invalid project type'
}),
with:
type: z.nativeEnum(ProjectType, {
message: 'Invalid project type'
}),
🤖 Prompt for AI Agents
In src/actions/projects/createProject/schema.ts around lines 5 to 8, the schema
uses z.enum(ProjectType) but ProjectType is a Prisma-generated enum (not a
string tuple); replace z.enum(ProjectType, { message: 'Invalid project type' })
with z.nativeEnum(ProjectType, { message: 'Invalid project type' }) so Zod
validates the native TypeScript/Prisma enum correctly.
| import { getProjects } from '@/actions/projects/getProjects'; | ||
| import { getProjectAnalytics } from '@/actions/analytics'; | ||
| import { BasicAlert } from '@/components/common/BasicAlert'; | ||
| import { HomePageContainer } from '@/components/pages/home/HomePageContainer'; | ||
| import { ExportDialog } from '@/components/models/projects/ExportDialog'; | ||
| import { ProjectStats } from '@/components/models/analytics/ProjectStats'; | ||
| import { safeAwait } from '@/lib/async'; | ||
| import { getSession } from '@/lib/session'; | ||
| import { redirect } from 'next/navigation'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix getProjects call and undefined variable; remove unused analytics/components. Current code calls getProjects(userId) (expects object) and later uses result.data (undefined). Also unused imports/vars trigger lint.
Apply:
-import { ExportDialog } from '@/components/models/projects/ExportDialog';
-import { ProjectStats } from '@/components/models/analytics/ProjectStats';
-import { getProjectAnalytics } from '@/actions/analytics';
+// removed unused imports
@@
- const [projectsError, projectsResult] = await safeAwait(getProjects(userId));
- const [analyticsError, analyticsResult] = await safeAwait(getProjectAnalytics());
+ const [projectsError, projectsResult] = await safeAwait(getProjects({ userId }));
+ // removed unused analytics call
@@
- if (projectsError) {
+ if (projectsError) {
return <BasicAlert variant='destructive' title='Error loading projects' description={projectsError.message} />;
}
@@
- if (!projectsResult.success) {
+ if (!projectsResult.success) {
return <BasicAlert variant='destructive' title='Error loading projects' description={projectsResult.error} />;
}
@@
- const initialProjects = result.data;
+ const initialProjects = projectsResult.data;Also applies to: 23-28
🧰 Tools
🪛 GitHub Check: lint-and-build
[warning] 6-6:
'ProjectStats' is defined but never used
[warning] 5-5:
'ExportDialog' is defined but never used
🤖 Prompt for AI Agents
In src/app/(general)/page.tsx around lines 1-9 (and also applies to lines
23-28): the code calls getProjects(userId) but the function expects an object
and the code later uses result.data (which is undefined); additionally several
analytics/components and helper imports/vars are unused and causing lints. Fix
by calling getProjects with an object (e.g., getProjects({ userId })), assign
the returned value directly to the projects variable instead of accessing .data,
remove unused imports and any unused variables (getProjectAnalytics,
ProjectStats, ExportDialog, safeAwait, etc.), and update any related variable
names/usages to match the function's actual return shape so ESLint no longer
flags unused/undefined references.
| } | ||
| }); | ||
|
|
||
| const onSubmit = async (data: inviteMemberSchema) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Same type usage issue in function parameter.
Same issue as line 20 - inviteMemberSchema cannot be used as a TypeScript type.
Apply this diff:
- const onSubmit = async (data: inviteMemberSchema) => {
+ const onSubmit = async (data: InviteMemberInput) => {Or:
- const onSubmit = async (data: inviteMemberSchema) => {
+ const onSubmit = async (data: z.infer<typeof inviteMemberSchema>) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const onSubmit = async (data: inviteMemberSchema) => { | |
| const onSubmit = async (data: z.infer<typeof inviteMemberSchema>) => { |
🤖 Prompt for AI Agents
In src/components/models/projects/InviteMemberDialog.tsx around line 29, the
onSubmit parameter is typed as inviteMemberSchema (a runtime Zod schema) which
is invalid as a TypeScript type; replace that usage with the inferred static
type (e.g., create an alias using z.infer<typeof inviteMemberSchema> or import
the existing inferred type) and update the function signature to use that
inferred type for the data parameter; ensure z is imported if needed and remove
the runtime schema from any type position.
| import { toast } from "sonner"; | ||
| import { Role } from "@/generated/prisma/client"; | ||
| import { ColumnDef } from "@tanstack/react-table"; | ||
| import { useState } from 'react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unused import.
The useState import is not used anywhere in this file and should be removed.
Apply this diff:
-import { useState } from 'react';
import { toast } from 'sonner';📝 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 { useState } from 'react'; | |
| import { toast } from 'sonner'; |
🧰 Tools
🪛 GitHub Check: lint-and-build
[warning] 1-1:
'useState' is defined but never used
🤖 Prompt for AI Agents
In src/components/models/projects/ProjectMembersTable.tsx at line 1, the
imported symbol `useState` is unused; remove the `useState` import from the
import list to eliminate the unused import and related linter warnings, leaving
only the imports that are actually referenced in the file.
| email: z.string().email({ | ||
| message: "Please enter a valid email address.", | ||
| }), | ||
| message: 'Please enter a valid email address.' | ||
| }) | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Blocked submit: required email + disabled field + empty default makes form impossible to submit
- Email is required by schema, but the input is disabled and defaults to ''. Disabled fields are omitted from submission; zod then fails on email, and users can’t fix it.
- Fix either by making email non-editable but submitted, or by not validating it here.
Two viable fixes (pick one):
Option A — keep email non-editable but submit value (prefer):
- Use readOnly instead of disabled.
- Ensure defaultValues are populated from the authenticated user before render.
- email: z.string().email({
+ email: z.string().email({
message: 'Please enter a valid email address.'
- })
+ })
...
- <Input id='email' type='email' {...form.register('email')} disabled />
+ <Input id='email' type='email' {...form.register('email')} readOnly />Option B — validate email elsewhere (server/auth context) and allow empty here:
- email: z.string().email({
+ email: z.string().email({
message: 'Please enter a valid email address.'
- })
+ }).optional()At minimum, populate defaultValues from user data before enabling validation.
Also applies to: 28-30, 46-47
I'll verify the concerns raised in the review comment by inspecting the actual ProfileForm.tsx file.
🏁 Script executed:
#!/bin/bash
# Inspect the ProfileForm.tsx file to verify review comment claims
cat src/components/models/user/ProfileForm.tsxLength of output: 1734
Fix required email field to avoid perpetual validation failure
- The email input is disabled with a default of
'', but the Zod schema requires a valid email—validation always fails and blocks submission.
Choose one:
Option A: keep email read-only and populate defaultValues.email from the user
- <Input id="email" type="email" {...form.register('email')} disabled />
+ <Input id="email" type="email" {...form.register('email')} readOnly />Ensure defaultValues.email is initialized with the authenticated user’s email before render.
Option B: make email optional in the schema
- email: z.string().email({ message: 'Please enter a valid email address.' })
+ email: z.string().email({ message: 'Please enter a valid email address.' }).optional()Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/models/user/ProfileForm.tsx around lines 17 to 20, the Zod
schema requires a valid email but the form renders the email input disabled with
default '' causing perpetual validation failure; either populate
defaultValues.email with the authenticated user’s email before render (Option A)
— fetch/derive the user email and set it into the form's defaultValues (or wait
to render the form until defaultValues is set), or make the email schema
optional (Option B) — change the Zod rule to accept
z.string().email(...).optional() (and handle undefined downstream). Implement
one of these fixes so the initial value and schema agree.
| function onSubmit(data: ProfileFormValues) { | ||
| console.log(data); // TODO: Implement actual update logic | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid logging PII (name/email) in production
Console logging form data can leak PII. Remove or guard by environment.
- function onSubmit(data: ProfileFormValues) {
- console.log(data); // TODO: Implement actual update logic
- }
+ function onSubmit(data: ProfileFormValues) {
+ if (process.env.NODE_ENV !== 'production') {
+ // Debug only
+ console.debug('ProfileForm submit', { hasName: !!data.name, hasEmail: !!data.email });
+ }
+ // TODO: Implement actual update logic
+ }📝 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.
| function onSubmit(data: ProfileFormValues) { | |
| console.log(data); // TODO: Implement actual update logic | |
| } | |
| function onSubmit(data: ProfileFormValues) { | |
| if (process.env.NODE_ENV !== 'production') { | |
| // Debug only | |
| console.debug('ProfileForm submit', { hasName: !!data.name, hasEmail: !!data.email }); | |
| } | |
| // TODO: Implement actual update logic | |
| } |
🤖 Prompt for AI Agents
In src/components/models/user/ProfileForm.tsx around lines 33 to 35, the
onSubmit handler currently console.logs the entire form payload which can expose
PII (name/email); remove the console.log in production and either implement the
actual update logic (call the update API/mutation and handle success/error) or
if you must log for debugging, wrap the log in an environment check (e.g., only
when NODE_ENV === "development") or redact PII before logging (mask email/name)
and use a secure logger that avoids printing sensitive fields. Ensure the final
change replaces the temporary console.log with production-safe update/error
handling and no unguarded PII output.
| } | ||
| }, | ||
| user: user, // Add the full user object to the context | ||
| user: user // Add the full user object to the context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not place the full Prisma user object in ctx (risk of leaking sensitive fields).
Select only safe fields (exclude passwordHash, tokens, etc.) and attach that to ctx. Also prefer findUnique when querying by unique id.
Apply this diff locally for the ctx assignment:
- user: user // Add the full user object to the context
+ user: safeUser // Attach a safe projection onlyThen fetch and project the user safely (example):
// Replace the current user fetch:
const user = await prisma.user.findUnique({
where: { id: userId },
select: { id: true, email: true, name: true, role: true } // exclude sensitive fields
});
if (!user) {
console.error('Not Authorised', new Error('Invalid user, not allowed'));
throw new Error('Not Authorised');
}
const safeUser = user; // already projected🤖 Prompt for AI Agents
In src/lib/action.ts around line 62, do not attach the full Prisma user object
to ctx; instead fetch the user with prisma.user.findUnique using a select
projection that only includes safe fields (e.g., id, email, name, role) to
exclude passwordHash/tokens, handle the case where no user is found by logging
and throwing a Not Authorised error, and then assign that projected safeUser to
ctx (replace the existing user: user assignment).
| import { APP_ENV } from './env'; | ||
|
|
||
| export const cookieName = "authormaton-session"; | ||
| export const IS_PRODUCTION = APP_ENV === "production"; | ||
| export const cookieName = 'authormaton-session'; | ||
| export const IS_PRODUCTION = APP_ENV === 'production'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Single-source IS_PRODUCTION; avoid duplicate definitions.
Likely duplicate of IS_PRODUCTION in env.ts. Re-export it here instead of recomputing to prevent drift and import confusion.
Apply this diff:
-import { APP_ENV } from './env';
+export { IS_PRODUCTION } from './env';
export const cookieName = 'authormaton-session';
-export const IS_PRODUCTION = APP_ENV === 'production';Run to verify no other conflicting definitions/import cycles:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Defs of IS_PRODUCTION:"
rg -nP --type=ts '\bexport\s+const\s+IS_PRODUCTION\b' -C2
echo
echo "env.ts importing constants.ts?"
rg -nP --type=ts -C2 $'from\\s+[\'"][.][./\\w-]*constants[\'"]' src/lib/env.ts || true
echo
echo "Usages of cookieName:"
rg -nP --type=ts '\bcookieName\b' -n -C2Length of output: 1343
</script>
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking if env.ts exports APP_ENV:"
rg -nP --type=ts '\bexport\s+(const|let|var)\s+APP_ENV\b' src/lib/env.ts -C3 || echo "Not found as direct export"
echo
echo "All exports from env.ts:"
rg -nP --type=ts '\bexport\b' src/lib/env.ts -nLength of output: 139
</script>
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Full content of src/lib/env.ts:"
cat src/lib/env.tsLength of output: 422
Single-source IS_PRODUCTION; eliminate duplicate definition.
Confirmed: IS_PRODUCTION is defined in both env.ts (line 4) and constants.ts (line 4), creating two independent sources of the same value. The session.ts file imports from constants.ts, not env.ts, which increases the risk of drift if either definition changes. Re-export from env.ts to make it the single source of truth.
Apply the suggested diff to consolidate:
-import { APP_ENV } from './env';
+export { IS_PRODUCTION } from './env';
export const cookieName = 'authormaton-session';
-export const IS_PRODUCTION = APP_ENV === 'production';📝 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 { APP_ENV } from './env'; | |
| export const cookieName = "authormaton-session"; | |
| export const IS_PRODUCTION = APP_ENV === "production"; | |
| export const cookieName = 'authormaton-session'; | |
| export const IS_PRODUCTION = APP_ENV === 'production'; | |
| export { IS_PRODUCTION } from './env'; | |
| export const cookieName = 'authormaton-session'; |
🤖 Prompt for AI Agents
In src/lib/constants.ts around lines 1 to 4, IS_PRODUCTION is duplicated from
env.ts; remove the local IS_PRODUCTION constant and re-export the value from
env.ts instead so there is a single source of truth. Specifically, replace the
local declaration by importing IS_PRODUCTION (or APP_ENV if you prefer) from
./env and then export it (export { IS_PRODUCTION } from './env' or export const
IS_PRODUCTION = APP_ENV === 'production' via env); ensure all modules that
currently import IS_PRODUCTION from constants continue to work by keeping the
exported name unchanged and remove the duplicate definition in this file.
| export const SigninSchema = z.object({ | ||
| email: z.string().email({ message: 'Invalid email address.' }), | ||
| password: z.string().min(6, { message: 'Password must be at least 6 characters.' }), | ||
| password: z.string().min(6, { message: 'Password must be at least 6 characters.' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Password policy mismatch (6 vs 8). UI copy says “at least 8 characters,” schema allows 6. Align schema to 8.
Apply this diff:
- password: z.string().min(6, { message: 'Password must be at least 6 characters.' })
+ password: z.string().min(8, { message: 'Password must be at least 8 characters.' })- password: z.string().min(6, { message: 'Password must be at least 6 characters.' })
+ password: z.string().min(8, { message: 'Password must be at least 8 characters.' })Also applies to: 11-11
🤖 Prompt for AI Agents
In src/lib/validations/auth.ts around lines 5 and 11, the password Zod schema
currently allows a minimum of 6 characters but the UI copy requires at least 8;
update both schema validators to require .min(8) and change the validation
message to 'Password must be at least 8 characters.' so the backend validation
matches the UI.
|
the email field addition is nice and will really help with validation! merging this now. |
Overview: This PR updates the
inviteMemberSchemato include a required email field and exports its inferred input type.Changes
email: z.string().email().nonempty()to theinviteMemberSchemainsrc/actions/collaboration.ts.inviteMemberSchemais exported for broader use.InviteMemberInputtype alias, which will help handlers and UI components import the validated and typed input.Summary by CodeRabbit
Release Notes
New Features
Style
Chores