Skip to content

Commit

Permalink
Merge pull request sadmann7#131 from sadmann7/stripe-hook
Browse files Browse the repository at this point in the history
feat: rate limit users based on subscription plan
  • Loading branch information
sadmann7 authored Apr 20, 2024
2 parents 951c851 + 72905ab commit 31530ae
Show file tree
Hide file tree
Showing 27 changed files with 220 additions and 231 deletions.
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 10 additions & 10 deletions src/app/(dashboard)/dashboard/_components/store-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "@radix-ui/react-icons"

import { type getStoresByUserId } from "@/lib/actions/store"
import { type getProgress } from "@/lib/queries/user"
import { type getUserPlanMetrics } from "@/lib/queries/user"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Expand All @@ -28,19 +28,19 @@ import {
PopoverTrigger,
} from "@/components/ui/popover"

import { AddStoreDialog } from "../stores/_components/add-store-dialog"
import { CreateStoreDialog } from "../stores/_components/create-store-dialog"

interface StoreSwitcherProps
extends React.ComponentPropsWithoutRef<typeof PopoverTrigger> {
userId: string
storesPromise: ReturnType<typeof getStoresByUserId>
progressPromise: ReturnType<typeof getProgress>
planMetricsPromise: ReturnType<typeof getUserPlanMetrics>
}

export function StoreSwitcher({
userId,
storesPromise,
progressPromise,
planMetricsPromise,
className,
...props
}: StoreSwitcherProps) {
Expand All @@ -51,15 +51,15 @@ export function StoreSwitcher({
const [showNewStoreDialog, setShowNewStoreDialog] = React.useState(false)

const stores = React.use(storesPromise)
const progress = React.use(progressPromise)
const planMetrics = React.use(planMetricsPromise)

const selectedStore = stores.find((store) => store.id === storeId)

return (
<>
<AddStoreDialog
<CreateStoreDialog
userId={userId}
progressPromise={progressPromise}
planMetricsPromise={planMetricsPromise}
open={showNewStoreDialog}
onOpenChange={setShowNewStoreDialog}
showTrigger={false}
Expand All @@ -70,13 +70,13 @@ export function StoreSwitcher({
variant="outline"
role="combobox"
aria-expanded={open}
aria-label="Select a team"
aria-label="Select a store"
className={cn("w-full justify-between", className)}
{...props}
>
{selectedStore?.name ?? "Select a store"}
<CaretSortIcon
className="ml-auto size-4 shrink-0 opacity-50"
className="ml-auto size-3.5 shrink-0 opacity-50"
aria-hidden="true"
/>
</Button>
Expand Down Expand Up @@ -121,7 +121,7 @@ export function StoreSwitcher({
setOpen(false)
setShowNewStoreDialog(true)
}}
disabled={progress.storeCount >= progress.storeLimit}
disabled={planMetrics.storeLimitExceeded}
>
<PlusCircledIcon className="mr-2 size-5" aria-hidden="true" />
Create store
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,28 @@

import { UserProfile as ClerkUserProfile } from "@clerk/nextjs"
import { dark } from "@clerk/themes"
import { type Theme } from "@clerk/types"
import type { Theme, UserProfileProps } from "@clerk/types"
import { useTheme } from "next-themes"

const appearance: Theme = {
baseTheme: undefined,
variables: {
borderRadius: "0.25rem",
},
elements: {
card: "shadow-none",
navbar: "hidden",
navbarMobileMenuButton: "hidden",
headerTitle: "hidden",
headerSubtitle: "hidden",
},
}

export function UserProfile() {
export function UserProfile({ ...props }: UserProfileProps) {
const { theme } = useTheme()

return (
<ClerkUserProfile
appearance={{
...appearance,
baseTheme: theme === "dark" ? dark : appearance.baseTheme,
baseTheme: theme === "light" ? appearance.baseTheme : dark,
variables: {
...appearance.variables,
colorBackground: theme === "light" ? "#fafafa" : undefined,
},
}}
{...props}
/>
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next"
import { env } from "@/env.js"

import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
import {
PageHeader,
PageHeaderDescription,
Expand All @@ -18,16 +19,17 @@ export const metadata: Metadata = {

export default function AccountPage() {
return (
<Shell variant="sidebar">
<Shell variant="sidebar" className="overflow-hidden">
<PageHeader>
<PageHeaderHeading size="sm">Account</PageHeaderHeading>
<PageHeaderDescription size="sm">
Manage your account settings
</PageHeaderDescription>
</PageHeader>
<section className="w-full overflow-hidden">
<ScrollArea className="w-full pb-3.5">
<UserProfile />
</section>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</Shell>
)
}
20 changes: 8 additions & 12 deletions src/app/(dashboard)/dashboard/billing/_components/billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Link from "next/link"
import type { SubscriptionPlanWithPrice, UserSubscriptionPlan } from "@/types"
import { CheckIcon } from "@radix-ui/react-icons"

import { type getUserUsageMetrics } from "@/lib/queries/user"
import { getPlanLimits } from "@/lib/subscription"
import { cn, formatDate } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
Expand All @@ -20,22 +21,17 @@ import { ManageSubscriptionForm } from "@/components/manage-subscription-form"
interface BillingProps {
subscriptionPlanPromise: Promise<UserSubscriptionPlan | null>
subscriptionPlansPromise: Promise<SubscriptionPlanWithPrice[]>
usagePromise: Promise<{
storeCount: number
productCount: number
}>
usageMetricsPromise: ReturnType<typeof getUserUsageMetrics>
}

export async function Billing({
subscriptionPlanPromise,
subscriptionPlansPromise,
usagePromise,
usageMetricsPromise,
}: BillingProps) {
const [subscriptionPlan, subscriptionPlans, usage] = await Promise.all([
subscriptionPlanPromise,
subscriptionPlansPromise,
usagePromise,
])
const [subscriptionPlan, subscriptionPlans, usageMetrics] = await Promise.all(
[subscriptionPlanPromise, subscriptionPlansPromise, usageMetricsPromise]
)

const { storeLimit, productLimit } = getPlanLimits({
planTitle: subscriptionPlan?.title,
Expand Down Expand Up @@ -68,13 +64,13 @@ export async function Billing({
<CardContent className="grid gap-6 sm:grid-cols-2">
<UsageCard
title="Stores"
count={usage.storeCount}
count={usageMetrics.storeCount}
limit={storeLimit}
moreInfo="The number of stores you can create on the current plan."
/>
<UsageCard
title="Products"
count={usage.productCount}
count={usageMetrics.productCount}
limit={productLimit}
moreInfo="The number of products you can create on the current plan."
/>
Expand Down
6 changes: 3 additions & 3 deletions src/app/(dashboard)/dashboard/billing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { env } from "@/env.js"
import { RocketIcon } from "@radix-ui/react-icons"

import { getSubscriptionPlan, getSubscriptionPlans } from "@/lib/actions/stripe"
import { getCachedUser, getUsage } from "@/lib/queries/user"
import { getCachedUser, getUserUsageMetrics } from "@/lib/queries/user"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import {
PageHeader,
Expand All @@ -32,7 +32,7 @@ export default async function BillingPage() {

const subscriptionPlanPromise = getSubscriptionPlan({ userId: user.id })
const subscriptionPlansPromise = getSubscriptionPlans()
const usagePromise = getUsage({ userId: user.id })
const usageMetricsPromise = getUserUsageMetrics({ userId: user.id })

return (
<Shell variant="sidebar">
Expand Down Expand Up @@ -63,7 +63,7 @@ export default async function BillingPage() {
<Billing
subscriptionPlanPromise={subscriptionPlanPromise}
subscriptionPlansPromise={subscriptionPlansPromise}
usagePromise={usagePromise}
usageMetricsPromise={usageMetricsPromise}
/>
</React.Suspense>
</Shell>
Expand Down
8 changes: 4 additions & 4 deletions src/app/(dashboard)/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { redirect } from "next/navigation"

import { getStoresByUserId } from "@/lib/actions/store"
import { getCachedUser, getProgress } from "@/lib/queries/user"
import { getCachedUser, getUserPlanMetrics } from "@/lib/queries/user"
import { SiteFooter } from "@/components/layouts/site-footer"

import { DashboardHeader } from "./_components/dashboard-header"
Expand All @@ -20,7 +20,7 @@ export default async function DashboardLayout({
}

const storesPromise = getStoresByUserId({ userId: user.id })
const progressPromise = getProgress({ userId: user.id })
const planMetricsPromise = getUserPlanMetrics({ userId: user.id })

return (
<SidebarProvider>
Expand All @@ -31,7 +31,7 @@ export default async function DashboardLayout({
<StoreSwitcher
userId={user.id}
storesPromise={storesPromise}
progressPromise={progressPromise}
planMetricsPromise={planMetricsPromise}
/>
</DashboardSidebar>
</DashboardSidebarSheet>
Expand All @@ -44,7 +44,7 @@ export default async function DashboardLayout({
<StoreSwitcher
userId={user.id}
storesPromise={storesPromise}
progressPromise={progressPromise}
planMetricsPromise={planMetricsPromise}
/>
</DashboardSidebar>
<main className="flex w-full flex-col overflow-hidden">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
} from "@/lib/actions/product"
import { getErrorMessage } from "@/lib/handle-error"
import {
addProductSchema,
type AddProductSchema,
createProductSchema,
type CreateProductSchema,
} from "@/lib/validations/product"
import { useUploadFile } from "@/hooks/use-upload-file"
import { Button } from "@/components/ui/button"
Expand Down Expand Up @@ -48,23 +48,26 @@ import { FilesCard } from "@/components/cards/FilesCard"
import { FileUploader } from "@/components/file-uploader"
import { Icons } from "@/components/icons"

interface AddProductFormProps {
interface CreateProductFormProps {
storeId: string
promises: Promise<{
categories: Awaited<ReturnType<typeof getCategories>>
subcategories: Awaited<ReturnType<typeof getSubcategories>>
}>
}

export function AddProductForm({ storeId, promises }: AddProductFormProps) {
export function CreateProductForm({
storeId,
promises,
}: CreateProductFormProps) {
const { categories, subcategories } = React.use(promises)

const [loading, setLoading] = React.useState(false)
const { uploadFiles, progresses, uploadedFiles, isUploading } =
useUploadFile("productImage")

const form = useForm<AddProductSchema>({
resolver: zodResolver(addProductSchema),
const form = useForm<CreateProductSchema>({
resolver: zodResolver(createProductSchema),
defaultValues: {
name: "",
description: "",
Expand All @@ -76,7 +79,7 @@ export function AddProductForm({ storeId, promises }: AddProductFormProps) {
},
})

function onSubmit(input: AddProductSchema) {
function onSubmit(input: CreateProductSchema) {
setLoading(true)

toast.promise(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
CardTitle,
} from "@/components/ui/card"

import { AddProductForm } from "./_components/add-product-form"
import { CreateProductForm } from "./_components/create-product-form"

export const metadata: Metadata = {
metadataBase: new URL(env.NEXT_PUBLIC_APP_URL),
Expand Down Expand Up @@ -46,7 +46,7 @@ export default async function NewProductPage({ params }: NewProductPageProps) {
<CardDescription>Add a new product to your store</CardDescription>
</CardHeader>
<CardContent>
<AddProductForm storeId={storeId} promises={promises} />
<CreateProductForm storeId={storeId} promises={promises} />
</CardContent>
</Card>
)
Expand Down
Loading

0 comments on commit 31530ae

Please sign in to comment.