forked from mickasmt/next-saas-stripe-starter
-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #41 from and-voila/srizvi/issue24
chore: migrate components and hooks
- Loading branch information
Showing
65 changed files
with
4,779 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { cva, VariantProps } from 'class-variance-authority'; | ||
|
||
import { Icons } from '@/app/components/shared/icons'; | ||
|
||
import { cn } from '../lib/utils'; | ||
|
||
const bannerVariants = cva( | ||
'border-2 text-center p-4 text-base flex items-center w-full', | ||
{ | ||
variants: { | ||
variant: { | ||
warning: 'bg-yellow-400 border-yellow-600 text-black', | ||
success: 'bg-alternate border-green-600 text-white', | ||
}, | ||
}, | ||
defaultVariants: { | ||
variant: 'warning', | ||
}, | ||
}, | ||
); | ||
|
||
interface BannerProps extends VariantProps<typeof bannerVariants> { | ||
label: string; | ||
} | ||
|
||
const iconMap = { | ||
warning: Icons.warning, | ||
success: Icons.circleChecked, | ||
}; | ||
|
||
export const Banner = ({ label, variant }: BannerProps) => { | ||
const Icon = iconMap[variant || 'warning']; | ||
|
||
return ( | ||
<div className={cn(bannerVariants({ variant }))}> | ||
<Icon className="mr-2 h-6 w-6" /> | ||
{label} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { ElementType, ReactNode } from 'react'; | ||
import clsx from 'clsx'; | ||
|
||
interface ContainerProps { | ||
as?: ElementType; | ||
className?: string; | ||
children?: ReactNode; | ||
} | ||
|
||
export function Container({ | ||
as: Component = 'div', | ||
className, | ||
children, | ||
}: ContainerProps) { | ||
return ( | ||
<Component | ||
className={clsx('mx-auto max-w-5xl px-6 lg:px-8 xl:px-10', className)} | ||
> | ||
<div className="mx-auto max-w-2xl lg:max-w-none">{children}</div> | ||
</Component> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
'use client'; | ||
|
||
import { useEffect, useState } from 'react'; | ||
import { MAX_FREE_TOKENS } from '@/constants'; | ||
|
||
import { Icons } from '@/app/components/shared/icons'; | ||
import { Button } from '@/app/components/ui/button'; | ||
import { Card, CardContent } from '@/app/components/ui/card'; | ||
import { useProModal } from '@/app/hooks/use-pro-modal'; | ||
import { isTeacher } from '@/app/lib/teacher'; | ||
|
||
interface FreeCounterProps { | ||
apiLimitCount: number; | ||
isPaidMember: boolean; | ||
userId: string; | ||
} | ||
|
||
export const FreeCounter = ({ | ||
apiLimitCount = 0, | ||
isPaidMember = false, | ||
userId, | ||
}: FreeCounterProps) => { | ||
const proModal = useProModal(); | ||
const [mounted, setMounted] = useState(false); | ||
|
||
useEffect(() => { | ||
setMounted(true); | ||
}, []); | ||
|
||
if (!mounted) { | ||
return null; | ||
} | ||
|
||
if (isPaidMember) { | ||
return null; | ||
} | ||
|
||
if (isTeacher(userId)) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="px-2"> | ||
<Card className="border bg-primary-foreground"> | ||
<CardContent className="py-4"> | ||
<div className="mb-4 space-y-2 text-center text-xs text-foreground"> | ||
<h2 className="font-display text-lg uppercase text-foreground"> | ||
Get Early Access | ||
</h2> | ||
<p className="text-muted-foreground"> | ||
You're on the free Good plan. Upgrade to the Best plan for | ||
some real magic. | ||
</p> | ||
{/*<p> | ||
You've used {apiLimitCount} / {MAX_FREE_TOKENS} AI tokens. | ||
</p> | ||
<Progress | ||
value={(apiLimitCount / MAX_FREE_TOKENS) * 100} | ||
className="h3" | ||
/>*/} | ||
</div> | ||
<Button onClick={proModal.onOpen} className="w-full" variant="custom"> | ||
Upgrade | ||
<Icons.magic className="ml-2 h-4 w-4" /> | ||
</Button> | ||
</CardContent> | ||
</Card> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { cva, VariantProps } from 'class-variance-authority'; | ||
|
||
import { IconName, Icons } from '@/app/components/shared/icons'; | ||
import { cn } from '@/app/lib/utils'; | ||
|
||
const backgroundVariants = cva('rounded-xl flex items-center justify-center', { | ||
variants: { | ||
variant: { | ||
default: 'bg-transparent', | ||
success: 'bg-emerald-100', | ||
}, | ||
size: { | ||
default: 'p-2', | ||
sm: 'p-1', | ||
}, | ||
}, | ||
defaultVariants: { | ||
variant: 'default', | ||
size: 'default', | ||
}, | ||
}); | ||
|
||
const iconVariants = cva('', { | ||
variants: { | ||
variant: { | ||
default: 'text-brand', | ||
success: 'text-emerald-700', | ||
}, | ||
size: { | ||
default: 'h-6 w-6', | ||
sm: 'h-4 w-4', | ||
}, | ||
}, | ||
defaultVariants: { | ||
variant: 'default', | ||
size: 'default', | ||
}, | ||
}); | ||
|
||
type BackgroundVariantsProps = VariantProps<typeof backgroundVariants>; | ||
type IconVariantsProps = VariantProps<typeof iconVariants>; | ||
|
||
interface IconBadgeProps extends BackgroundVariantsProps, IconVariantsProps { | ||
icon: IconName; | ||
} | ||
|
||
export const IconBadge = ({ icon, variant, size }: IconBadgeProps) => { | ||
const Icon = Icons[icon]; | ||
return ( | ||
<div className={cn(backgroundVariants({ variant, size }))}> | ||
<Icon className={cn(iconVariants({ variant, size }))} /> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { Suspense } from 'react'; | ||
import Image from 'next/image'; | ||
import Link from 'next/link'; | ||
|
||
import { Skeleton } from '@/app/components/ui/skeleton'; | ||
import { getCoursePrice } from '@/app/lib/course-pricing'; | ||
|
||
interface CourseCardProps { | ||
id: string; | ||
title: string; | ||
preview: string; | ||
imageUrl: string; | ||
displayImage?: boolean; | ||
price: number; | ||
progress: number | null; | ||
category: string; | ||
isPaidMember: boolean; | ||
purchased: boolean; | ||
} | ||
export const CourseCard = ({ | ||
id, | ||
title, | ||
preview, | ||
imageUrl, | ||
displayImage = true, | ||
price, | ||
progress, | ||
category, | ||
isPaidMember, | ||
purchased, | ||
}: CourseCardProps) => { | ||
const displayPrice = getCoursePrice(price, isPaidMember, purchased); | ||
|
||
return ( | ||
<Link href={`/learn/courses/${id}`}> | ||
<div className="group h-full overflow-hidden rounded-xl border bg-white transition hover:shadow-sm dark:bg-background"> | ||
{displayImage && ( | ||
<div className="relative aspect-video w-full overflow-hidden md:grayscale md:group-hover:grayscale-0"> | ||
<Suspense | ||
fallback={ | ||
<Skeleton className="relative aspect-video h-32 w-full" /> | ||
} | ||
> | ||
<Image fill className="object-cover" alt={title} src={imageUrl} /> | ||
</Suspense> | ||
</div> | ||
)} | ||
<div className="mt-1 flex flex-col p-4"> | ||
<div className="mb-2 flex items-center justify-between"> | ||
<Suspense fallback={<Skeleton className="h-4 w-12" />}> | ||
<p className="font-mono text-sm text-muted-foreground"> | ||
{category} | ||
</p> | ||
</Suspense> | ||
<Suspense fallback={<Skeleton className="h-4 w-12" />}> | ||
{progress !== null ? ( | ||
progress === 0 ? ( | ||
<p className="font-mono text-sm text-brand">Not Started</p> | ||
) : progress === 100 ? ( | ||
<p className="font-mono text-sm text-alternate">Complete</p> | ||
) : ( | ||
<p className="font-mono text-sm text-brand">In Progress</p> | ||
) | ||
) : ( | ||
<p className="font-mono text-sm text-brand">{displayPrice}</p> | ||
)} | ||
</Suspense> | ||
</div> | ||
<Suspense fallback={<Skeleton className="h-8 w-3/4" />}> | ||
<div className="line-clamp-2 text-lg font-semibold leading-tight transition group-hover:text-brand"> | ||
{title} | ||
</div> | ||
</Suspense> | ||
<Suspense fallback={<Skeleton className="h-24 w-3/5" />}> | ||
<p className="my-2 line-clamp-2 text-sm text-muted-foreground"> | ||
{preview} | ||
</p> | ||
</Suspense> | ||
</div> | ||
</div> | ||
</Link> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
'use client'; | ||
|
||
import { useState } from 'react'; | ||
import axios from 'axios'; | ||
|
||
import { Button } from '@/app/components/ui/button'; | ||
import { toast } from '@/app/components/ui/use-toast'; | ||
import { formatPrice } from '@/app/lib/format'; | ||
|
||
interface CourseEnrollButtonProps { | ||
price: number; | ||
courseId: string; | ||
} | ||
|
||
export const CourseEnrollButton = ({ | ||
price, | ||
courseId, | ||
}: CourseEnrollButtonProps) => { | ||
const [isLoading, setIsLoading] = useState(false); | ||
|
||
const onClick = async () => { | ||
try { | ||
setIsLoading(true); | ||
|
||
const response = await axios.post(`/api/courses/${courseId}/checkout`); | ||
|
||
window.location.assign(response.data.url); | ||
} catch { | ||
toast({ | ||
title: 'Uh oh! An error occurred.', | ||
description: | ||
'Honestly, we have no idea what happened. Please try again.', | ||
variant: 'destructive', | ||
}); | ||
} finally { | ||
setIsLoading(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<Button | ||
variant="secondary" | ||
onClick={onClick} | ||
disabled={isLoading} | ||
size="sm" | ||
className="w-full flex-shrink-0 lg:w-auto" | ||
> | ||
Buy for {formatPrice(price)} | ||
</Button> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Icons } from '@/app/components/shared/icons'; | ||
import { Sheet, SheetContent, SheetTrigger } from '@/app/components/ui/sheet'; | ||
import { CourseMobileSidebarProps } from '@/app/lib/types'; | ||
|
||
import { CourseSidebar } from './course-sidebar'; | ||
|
||
export const CourseMobileSidebar = ({ | ||
course, | ||
progressCount, | ||
isPaidMember, | ||
apiLimitCount, | ||
}: CourseMobileSidebarProps) => { | ||
return ( | ||
<Sheet> | ||
<SheetTrigger className="pr-4 transition hover:opacity-75 md:hidden"> | ||
<Icons.hamburgerMenu /> | ||
</SheetTrigger> | ||
<SheetContent side="left" className="w-72 bg-white p-0"> | ||
<CourseSidebar | ||
course={course} | ||
progressCount={progressCount} | ||
isPaidMember={isPaidMember} | ||
apiLimitCount={apiLimitCount} | ||
/> | ||
</SheetContent> | ||
</Sheet> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { redirect } from 'next/navigation'; | ||
|
||
import { UserAccountNav } from '@/app/components/layout/user-account-nav'; | ||
import { CourseMobileSidebar } from '@/app/components/learn/courses/course-mobile-sidebar'; | ||
import { NavbarRoutes } from '@/app/config/navbar-routes'; | ||
import { getCurrentUser } from '@/app/lib/session'; | ||
import { CourseNavbarProps } from '@/app/lib/types'; | ||
|
||
export const CourseNavbar = async ({ | ||
course, | ||
progressCount, | ||
isPaidMember, | ||
apiLimitCount, | ||
}: CourseNavbarProps) => { | ||
const user = await getCurrentUser(); | ||
const userId = user?.id; | ||
if (!userId) { | ||
return redirect('/login'); | ||
} | ||
return ( | ||
<div className="flex h-full items-center border-b bg-[#dcdfe5] p-4 shadow-sm dark:bg-[#16161a]"> | ||
<CourseMobileSidebar | ||
course={course} | ||
progressCount={progressCount} | ||
isPaidMember={isPaidMember} | ||
apiLimitCount={apiLimitCount} | ||
/> | ||
<NavbarRoutes userId={userId} /> | ||
<UserAccountNav | ||
user={{ | ||
name: user.name, | ||
image: user.image, | ||
email: user.email, | ||
}} | ||
/> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.