Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis update introduces a new pricing page with interactive plan selection and upgrade logic, adds a responsive Navbar component for navigation, and dynamically adjusts onboarding steps and sidebar UI based on user subscription status. It also includes new and updated icon components, refines UI styling, updates localization strings, removes a UI label, and modifies server utility logic for connection handling. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant PricingPage
participant useBilling
participant useCustomer
User->>PricingPage: Loads /pricing
PricingPage->>useCustomer: Fetch customer data
PricingPage->>useBilling: Prepare upgrade handler
User->>PricingPage: Clicks "Upgrade" on Pro plan
PricingPage->>useBilling: attach({ productId, redirectUrl })
useBilling-->>PricingPage: Returns result or error
PricingPage-->>User: Navigates or shows error
sequenceDiagram
participant User
participant OnboardingDialog
participant useBilling
participant useCustomer
User->>OnboardingDialog: Opens onboarding
OnboardingDialog->>useCustomer: Fetch customer data
OnboardingDialog->>getSteps: Generate steps (isPro)
User->>OnboardingDialog: Interacts with steps
alt Step requires upgrade
User->>OnboardingDialog: Clicks upgrade
OnboardingDialog->>useBilling: attach({ productId, redirectUrl })
useBilling-->>OnboardingDialog: Returns result or error
end
OnboardingDialog-->>User: Shows next step or completion
Suggested reviewers
Poem
Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (8)
apps/mail/components/ui/nav-user.tsx (1)
179-180: Minor formatting improvementAdded spacing for better code readability.
apps/mail/components/ui/ai-sidebar.tsx (1)
82-93: Pro subscription detection logic implemented.The code now checks if the user has a Pro subscription by examining the customer's products array, looking for products with IDs or names containing "pro-example".
Consider using exact matching instead of
includes()for more precise product identification:- product.id.includes('pro-example') || product.name.includes('pro-example') + product.id === 'pro-example' || product.name === 'pro-example'Also, it would be better to type
productproperly rather than usingany:- (product: any) => + (product: { id: string; name: string }) =>apps/mail/app/(full-width)/pricing/page.tsx (2)
25-69: Navigation link definitions for resources and company information.The code defines arrays of navigation links for resources (GitHub, Twitter, etc.) and company information (About, Privacy, Terms).
These link arrays are duplicated in both
pricing/page.tsxandnavbar.tsx. Consider extracting them to a shared configuration file:// New file: apps/mail/config/navigation-links.ts export const resources = [ { title: 'GitHub', href: 'https://github.com/Mail-0/Zero', description: 'Check out our open-source projects and contributions.', platform: 'github' as const, }, // ...other resources ]; export const aboutLinks = [ { title: 'About', href: '/about', description: 'Learn more about Zero and our mission.', }, // ...other about links ];Then import these in both files:
- const resources = [...] - const aboutLinks = [...] + import { resources, aboutLinks } from '@/config/navigation-links';
102-105: Commented out background component.There's a commented out
PixelatedBackgroundcomponent.Either remove the commented code or add a comment explaining why it's being kept for future reference:
- {/* <PixelatedBackground - className="z-1 absolute -top-32 left-1/2 h-auto w-screen min-w-[1920px] -translate-x-1/2 object-cover opacity-5" - style={{ mixBlendMode: 'screen' }} - /> */} + {/* TODO: Pixelated background temporarily disabled - will be re-enabled after design review */} + {/* <PixelatedBackground + className="z-1 absolute -top-32 left-1/2 h-auto w-screen min-w-[1920px] -translate-x-1/2 object-cover opacity-5" + style={{ mixBlendMode: 'screen' }} + /> */}apps/mail/components/navbar.tsx (2)
19-62: Navigation link definitions for resources and company information.The code defines arrays of navigation links for resources (GitHub, Twitter, etc.) and company information (About, Privacy, Terms).
As mentioned in my review of the pricing page, these link arrays are duplicated and should be extracted to a shared configuration file.
124-168: Mobile navigation with slide-in sheet.For mobile devices, the component renders a hamburger menu button that triggers a slide-in sheet with navigation links.
The resource icons in the mobile view lack alternative text for screen readers. Consider adding more descriptive alt text:
- {resource.platform && <Image src={`/${resource.platform}.svg`} alt={resource.platform} width={20} height={20} />} + {resource.platform && <Image src={`/${resource.platform}.svg`} alt={`${resource.title} logo`} width={20} height={20} />}apps/mail/components/onboarding.tsx (2)
126-127: Confetti may never fire after a step-count changeThe effect’s dependency on
steps.lengthis great, but ifisProtoggles while the user is on the last visible step,currentStepcould now exceed the newsteps.length – 1, preventing both navigation and the confetti trigger.Consider resetting
currentSteptoMath.min(currentStep, steps.length - 1)inside auseEffect([steps.length]).
136-147: Missing early return & feedback inhandleUpgrade
attachcan beundefined; the current guard is good, but an early return saves nesting:- if (attach) { - try { + if (!attach) return; + try {
- The user gets no visual feedback on failure. Surfacing a toast/snackbar would greatly improve UX.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/mail/public/onboarding/coming-soon.pngis excluded by!**/*.png
📒 Files selected for processing (9)
apps/mail/app/(full-width)/pricing/page.tsx(1 hunks)apps/mail/components/icons/icons.tsx(2 hunks)apps/mail/components/navbar.tsx(1 hunks)apps/mail/components/onboarding.tsx(7 hunks)apps/mail/components/ui/ai-sidebar.tsx(4 hunks)apps/mail/components/ui/nav-main.tsx(2 hunks)apps/mail/components/ui/nav-user.tsx(1 hunks)apps/mail/locales/en.json(1 hunks)apps/server/src/lib/server-utils.ts(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/mail/components/ui/ai-sidebar.tsx (1)
apps/mail/components/ui/gauge.tsx (1)
Gauge(1-84)
apps/mail/components/onboarding.tsx (1)
apps/mail/hooks/use-billing.ts (1)
useBilling(36-84)
🪛 Biome (1.9.4)
apps/mail/components/onboarding.tsx
[error] 178-178: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (15)
apps/server/src/lib/server-utils.ts (3)
9-9: Improved session validation with structured object checkThe change from checking
session.connectionIdtosession.activeConnection?.idprovides better structure and optional chaining for safer access to nested properties.
12-15: Enhanced database query with explicit conditionsUsing
and()with explicit equality checks on both user ID and connection ID improves the query clarity and ensures proper record filtering based on the updated session structure.
31-38: Simplified driver creation implementationThe refactored code simplifies the driver creation by passing only the essential parameters (providerId and auth object). This makes the code more readable and maintains a cleaner API interface.
apps/mail/components/icons/icons.tsx (2)
162-177: Good addition of the CircleX icon for UI actionsThe new CircleX icon follows the established pattern and will be useful for close/cancel actions in the UI. The SVG implementation with fill-rule is clean and efficient.
1059-1066: LinkedIn icon visibility improvementChanging the fill from "none" to "white" improves the visibility of the LinkedIn icon, especially on darker backgrounds. This enhances the overall UI consistency.
apps/mail/locales/en.json (1)
440-441: Localization added for compose close actionAddition of the "closeCompose" localization string properly supports the new UI action for closing the composition interface. Follows existing naming conventions.
apps/mail/components/ui/nav-user.tsx (1)
150-159: Consistent subscription status check maintainedThe code maintains the isPro logic for subscription status detection, which is still used in other parts of the component (line 562). This ensures consistent subscription status determination throughout the application.
apps/mail/components/ui/ai-sidebar.tsx (2)
13-13: New import added for subscription status management.The
useState,useEffect,useContext,createContext,useCallback,useMemoimports have been reorganized, and a new import foruseCustomerfromautumn-js/nexthas been added to support subscription status checking.Also applies to: 29-29
164-187: Conditionally render usage gauge and upgrade tooltip only for non-Pro users.The UI now intelligently hides the message usage gauge and upgrade tooltip for Pro subscribers, providing a cleaner experience for paid users while maintaining the upgrade path for free users.
This is a good UX improvement that reduces visual clutter for Pro users who don't need to see usage limits.
apps/mail/components/ui/nav-main.tsx (2)
345-345: Reordered CSS classes for styling consistency.Background styling classes were moved before layout classes.
Though functionally equivalent, this change likely aligns with a team convention of ordering background styles before layout styles.
498-498: Enhanced dropdown menu styling with modern visual effects.The dropdown menu now includes translucent backgrounds, proper spacing with flex layout, and a backdrop blur effect.
These styling improvements create a more modern, visually appealing dropdown that's consistent with contemporary UI trends.
apps/mail/app/(full-width)/pricing/page.tsx (2)
1-24: New pricing page implemented with necessary imports.A client-side pricing page has been created with imports for UI components, hooks for billing and customer data, and navigation elements.
The imports are well-organized and include all necessary components for the pricing functionality.
214-300: Pricing plans UI implementation.The pricing section displays two plans (Free and Pro) with their respective features and prices, highlighting the differences and providing appropriate upgrade/downgrade buttons based on the user's current subscription status.
The pricing UI is well-structured and clearly communicates the value proposition of each plan. The conditional button states based on subscription status are appropriately implemented.
apps/mail/components/navbar.tsx (2)
1-18: New responsive navbar component with required imports.A new
Navbarcomponent is added with imports for navigation elements, sheet components for mobile views, and basic UI utilities.The component is well-structured with all necessary imports for both desktop and mobile navigation patterns.
64-123: Desktop navigation implementation in Navbar component.The component renders a fixed header for desktop with the company logo, dropdown menus, and action buttons.
The desktop navigation is well-implemented with appropriate responsive classes and interactive elements. The dropdown menus provide a clean way to access additional links without cluttering the main navigation.
| <div className="md:hidden"> | ||
| <Sheet open={open} onOpenChange={setOpen}> | ||
| <SheetTrigger asChild> | ||
| <Button variant="ghost" size="icon" className="fixed left-4 top-6 z-50"> | ||
| <Menu className="h-6 w-6" /> | ||
| </Button> | ||
| </SheetTrigger> | ||
| <SheetContent side="left" className="w-[300px] bg-[#111111] sm:w-[400px]"> | ||
| <SheetHeader className="flex flex-row items-center justify-between"> | ||
| <SheetTitle> | ||
| <Image src="white-icon.svg" alt="Zero Email" width={22} height={22} /> | ||
| </SheetTitle> | ||
| <Link href="/login"> | ||
| <Button className="w-full">Sign in</Button> | ||
| </Link> | ||
| </SheetHeader> | ||
| <div className="mt-8 flex flex-col space-y-3"> | ||
| <div className="space-y-3"> | ||
| <h4 className="text-muted-foreground text-sm font-medium">Company</h4> | ||
| {aboutLinks.map((link) => ( | ||
| <Link key={link.title} href={link.href} className="block font-medium"> | ||
| {link.title} | ||
| </Link> | ||
| ))} | ||
| </div> | ||
| <Link target="_blank" href="https://cal.com/team/0" className="font-medium"> | ||
| Contact Us | ||
| </Link> | ||
| </div> | ||
| <Separator className="mt-8" /> | ||
| <div className="mt-8 flex flex-row items-center justify-center gap-4"> | ||
| {resources.map((resource) => ( | ||
| <Link | ||
| key={resource.title} | ||
| href={resource.href} | ||
| className="flex items-center gap-2 font-medium" | ||
| > | ||
| {resource.platform && <Image src={`/${resource.platform}.svg`} alt={resource.platform} width={20} height={20} />} | ||
| </Link> | ||
| ))} | ||
| </div> | ||
| </SheetContent> | ||
| </Sheet> | ||
| </div> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Mobile navigation with slide-in sheet.
A mobile-friendly navigation is implemented using a slide-in sheet triggered by a hamburger menu button.
Similar to the desktop navigation, this is duplicated with the Navbar component and should be refactored to use it.
| {/* Desktop Navigation - Hidden on mobile */} | ||
| <header className="fixed z-50 hidden w-full items-center justify-center px-4 pt-6 md:flex"> | ||
| <nav className="border-input/50 bg-popover flex w-full max-w-3xl items-center justify-between gap-2 rounded-xl border-t p-2 px-4"> | ||
| <div className="flex items-center gap-6"> | ||
| <Link href="/" className="relative cursor-pointer"> | ||
| <Image src="white-icon.svg" alt="Zero Email" width={22} height={22} /> | ||
| </Link> | ||
| <NavigationMenu> | ||
| <NavigationMenuList className="gap-1"> | ||
| <NavigationMenuItem> | ||
| <NavigationMenuTrigger>Company</NavigationMenuTrigger> | ||
| <NavigationMenuContent> | ||
| <ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-1 lg:w-[600px]"> | ||
| {aboutLinks.map((link) => ( | ||
| <ListItem key={link.title} title={link.title} href={link.href}> | ||
| {link.description} | ||
| </ListItem> | ||
| ))} | ||
| </ul> | ||
| </NavigationMenuContent> | ||
| </NavigationMenuItem> | ||
| <NavigationMenuItem> | ||
| <NavigationMenuTrigger>Resources</NavigationMenuTrigger> | ||
| <NavigationMenuContent> | ||
| <ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]"> | ||
| {resources.map((resource) => ( | ||
| <ListItem | ||
| key={resource.title} | ||
| title={resource.title} | ||
| href={resource.href} | ||
| platform={resource.platform} | ||
| > | ||
| {resource.description} | ||
| </ListItem> | ||
| ))} | ||
| </ul> | ||
| </NavigationMenuContent> | ||
| </NavigationMenuItem> | ||
| <NavigationMenuItem> | ||
| <Link href="/pricing"> | ||
| <Button variant="ghost" className="h-9"> | ||
| Pricing | ||
| </Button> | ||
| </Link> | ||
| </NavigationMenuItem> | ||
| </NavigationMenuList> | ||
| </NavigationMenu> | ||
| </div> | ||
| <div className="flex gap-2"> | ||
| <Link href="/login"> | ||
| <Button variant="ghost" className="h-8"> | ||
| Sign in | ||
| </Button> | ||
| </Link> | ||
| <Link target="_blank" href="https://cal.com/team/0"> | ||
| <Button className="h-8 font-medium">Contact Us</Button> | ||
| </Link> | ||
| </div> | ||
| </nav> | ||
| </header> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Desktop navigation implementation.
The desktop navigation includes the company logo, dropdown menus for company info and resources, and action buttons for sign-in and contact.
The navigation code is duplicated between this file and the Navbar component. Consider reusing the Navbar component instead:
- {/* Desktop Navigation - Hidden on mobile */}
- <header className="fixed z-50 hidden w-full items-center justify-center px-4 pt-6 md:flex">
- {/* ... all the navigation code ... */}
- </header>
+ <Navbar />Committable suggestion skipped: line range outside the PR's diff.
| export default function PricingPage() { | ||
| const [open, setOpen] = useState(false); | ||
| const { attach } = useBilling(); | ||
| const { customer } = useCustomer(); | ||
|
|
||
| const isPro = useMemo(() => { | ||
| return ( | ||
| customer && | ||
| Array.isArray(customer.products) && | ||
| customer.products.some( | ||
| (product: any) => | ||
| product.id.includes('pro-example') || product.name.includes('pro-example'), | ||
| ) | ||
| ); | ||
| }, [customer]); | ||
|
|
||
| const handleUpgrade = async () => { | ||
| if (attach) { | ||
| try { | ||
| await attach({ | ||
| productId: 'pro-example', | ||
| successUrl: `${window.location.origin}/mail/inbox?success=true`, | ||
| authUrl: `${window.location.origin}/login?redirect=/pricing`, | ||
| }); | ||
| } catch (error) { | ||
| console.error('Failed to upgrade:', error); | ||
| } | ||
| } | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Pricing page component with subscription status checking and upgrade handling.
The component uses hooks to access billing functionality and customer data, checks if the user has a Pro subscription, and provides an upgrade function.
The error handling in handleUpgrade only logs to the console without providing user feedback. Consider adding toast notifications:
const handleUpgrade = async () => {
if (attach) {
try {
await attach({
productId: 'pro-example',
successUrl: `${window.location.origin}/mail/inbox?success=true`,
authUrl: `${window.location.origin}/login?redirect=/pricing`,
});
+ // Success is handled by the redirect
} catch (error) {
console.error('Failed to upgrade:', error);
+ toast.error('Failed to process your upgrade. Please try again later.');
}
}
};Also, consider adding loading state feedback during the upgrade process:
const [isUpgrading, setIsUpgrading] = useState(false);
const handleUpgrade = async () => {
if (attach) {
+ setIsUpgrading(true);
try {
await attach({
// ...
});
} catch (error) {
// ...
+ } finally {
+ setIsUpgrading(false);
}
}
};
// Then in the button:
<Button
// ...
+ disabled={isPro || isUpgrading}
+ className="h-10 w-full"
>
+ {isUpgrading ? "Processing..." : isPro ? "Current Plan" : "Upgrade"}
</Button>Committable suggestion skipped: line range outside the PR's diff.
| <div className="mt-8 flex flex-row items-center justify-center gap-4"> | ||
| {resources.map((resource) => ( | ||
| <Link | ||
| key={resource.title} | ||
| href={resource.href} | ||
| className="flex items-center gap-2 font-medium" | ||
| > | ||
| {resource.platform && <Image src={`/${resource.platform}.svg`} alt={resource.platform} width={20} height={20} />} | ||
| </Link> | ||
| ))} | ||
| </div> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Social media links in mobile navigation.
The code renders social media platform icons as links in the mobile sheet menu.
Verify that all the platform SVG files referenced in the Image src attributes exist in the public directory:
🏁 Script executed:
#!/bin/bash
# Check if all required social media icon files exist
missing_files=0
for platform in github twitter linkedin discord
do
if [ ! -f "public/${platform}.svg" ]; then
echo "Missing file: public/${platform}.svg"
missing_files=$((missing_files+1))
fi
done
if [ $missing_files -eq 0 ]; then
echo "All platform icon files exist."
else
echo "Missing ${missing_files} platform icon files."
fiLength of output: 822
Missing social media icon files in public directory
The mobile navigation renders social media icons using <Image src="/{platform}.svg" />, but none of the expected SVGs are present:
• public/github.svg
• public/twitter.svg
• public/linkedin.svg
• public/discord.svg
Please add the corresponding SVG files (or update the platform values) so these links render correctly.
| {typeof steps[currentStep]?.description === 'function' | ||
| ? steps[currentStep]?.description(handleUpgrade) | ||
| : steps[currentStep]?.description} | ||
| </p> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
description typing is too permissive for the <p> wrapper
description currently accepts string | ReactNode | ((handleUpgrade)=>ReactNode), but only strings are guaranteed to be valid inside a <p>. When it’s a JSX element that renders a <div> the HTML becomes invalid.
Two possible fixes:
- <p className="text-muted-foreground mx-auto max-w-md text-sm">
- {typeof steps[currentStep]?.description === 'function'
- ? steps[currentStep]?.description(handleUpgrade)
- : steps[currentStep]?.description}
- </p>
+ const Description = steps[currentStep]?.description;
+ <div className="text-muted-foreground mx-auto max-w-md text-sm">
+ {typeof Description === 'function' ? Description(handleUpgrade) : Description}
+ </div>This removes the semantic trap and lets description be any valid ReactNode.
Committable suggestion skipped: line range outside the PR's diff.
| const isPro = useMemo(() => { | ||
| return ( | ||
| customer && | ||
| Array.isArray(customer.products) && | ||
| customer.products.some( | ||
| (product: any) => | ||
| product.id.includes('pro-example') || product.name.includes('pro-example'), | ||
| ) | ||
| ); | ||
| }, [customer]); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Guard against unexpected product shapes in isPro detection
isPro assumes every entry in customer.products is an object with both id and name properties.
If the API ever sends a string identifier (or any non-object) the optional chain breaks and the component crashes while rendering.
- customer.products.some(
- (product: any) =>
- product.id.includes('pro-example') || product.name.includes('pro-example'),
- )
+ customer.products.some((product: any) => {
+ if (typeof product !== 'object' || product === null) return false;
+ return (
+ (typeof product.id === 'string' && product.id.includes('pro-example')) ||
+ (typeof product.name === 'string' && product.name.includes('pro-example'))
+ );
+ })Adding the type-checks costs virtually nothing but prevents a fatal runtime error if the backend shape changes.
📝 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 isPro = useMemo(() => { | |
| return ( | |
| customer && | |
| Array.isArray(customer.products) && | |
| customer.products.some( | |
| (product: any) => | |
| product.id.includes('pro-example') || product.name.includes('pro-example'), | |
| ) | |
| ); | |
| }, [customer]); | |
| const isPro = useMemo(() => { | |
| return ( | |
| customer && | |
| Array.isArray(customer.products) && | |
| customer.products.some((product: any) => { | |
| if (typeof product !== 'object' || product === null) return false; | |
| return ( | |
| (typeof product.id === 'string' && product.id.includes('pro-example')) || | |
| (typeof product.name === 'string' && product.name.includes('pro-example')) | |
| ); | |
| }) | |
| ); | |
| }, [customer]); |
| {steps[currentStep] && steps[currentStep].video && ( | ||
| <div className="relative flex items-center justify-center"> | ||
| <div className="bg-muted aspect-video w-full max-w-4xl overflow-hidden rounded-lg"> | ||
| {steps.map( | ||
| (step, index) => | ||
| step.video && ( | ||
| <div | ||
| key={index} | ||
| className={`absolute inset-0 transition-opacity duration-300 ${ | ||
| index === currentStep ? 'opacity-100' : 'opacity-0' | ||
| }`} | ||
| > | ||
| <Image | ||
| priority | ||
| width={500} | ||
| height={500} | ||
| src={step.video} | ||
| alt={step.title} | ||
| className="h-full w-full object-cover rounded-lg border" | ||
| /> | ||
| </div> | ||
| ), | ||
| )} | ||
| </div> |
There was a problem hiding this comment.
Rendering every video at once harms performance
Each step’s image is mounted (and pre-loaded, due to priority) even when hidden. This can easily add several megabytes to the initial payload.
Render only the active frame:
- {steps.map(
- (step, index) =>
- step.video && (
- <div
- key={index}
- className={`absolute inset-0 transition-opacity duration-300 ${
- index === currentStep ? 'opacity-100' : 'opacity-0'
- }`}
- >
- <Image ... src={step.video} />
- </div>
- ),
- )}
+ {steps[currentStep]?.video && (
+ <Image
+ priority
+ width={500}
+ height={500}
+ src={steps[currentStep].video}
+ alt={steps[currentStep].title}
+ className="h-full w-full object-cover rounded-lg border"
+ />
+ )}This keeps memory and network usage minimal while preserving the fade effect (you can wrap the Image in a single animated container if the transition is required).
📝 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.
| {steps[currentStep] && steps[currentStep].video && ( | |
| <div className="relative flex items-center justify-center"> | |
| <div className="bg-muted aspect-video w-full max-w-4xl overflow-hidden rounded-lg"> | |
| {steps.map( | |
| (step, index) => | |
| step.video && ( | |
| <div | |
| key={index} | |
| className={`absolute inset-0 transition-opacity duration-300 ${ | |
| index === currentStep ? 'opacity-100' : 'opacity-0' | |
| }`} | |
| > | |
| <Image | |
| priority | |
| width={500} | |
| height={500} | |
| src={step.video} | |
| alt={step.title} | |
| className="h-full w-full object-cover rounded-lg border" | |
| /> | |
| </div> | |
| ), | |
| )} | |
| </div> | |
| {steps[currentStep] && steps[currentStep].video && ( | |
| <div className="relative flex items-center justify-center"> | |
| <div className="bg-muted aspect-video w-full max-w-4xl overflow-hidden rounded-lg"> | |
| {steps[currentStep]?.video && ( | |
| <Image | |
| priority | |
| width={500} | |
| height={500} | |
| src={steps[currentStep].video} | |
| alt={steps[currentStep].title} | |
| className="h-full w-full object-cover rounded-lg border" | |
| /> | |
| )} | |
| </div> | |
| </div> | |
| )} |
🧰 Tools
🪛 Biome (1.9.4)
[error] 178-178: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
small pushed to prod
Summary by CodeRabbit
New Features
Improvements
Style