-
Notifications
You must be signed in to change notification settings - Fork 4k
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
feat(dashboard): Billing settings page in dashboard v2 #7203
Merged
Merged
Changes from 48 commits
Commits
Show all changes
50 commits
Select commit
Hold shift + click to select a range
4a4bf00
feat: settings page
scopsy 062b172
feat: base billing page
scopsy 9a5a375
feat: add billingbase
scopsy 5e76db8
feat: padi
scopsy bb558f5
feat: add hubspot forms
scopsy f1a8e6d
feat: add tests
scopsy 33a606f
feat:ff
scopsy 0d49ca8
feat:cspell
scopsy 4c6a1dc
Update .source
scopsy 7cbb10b
fix: v2 dashboard path
scopsy e462996
feat: align styles
scopsy 0ab6a0a
Update settings.tsx
scopsy 7daa076
fix: style
scopsy aef9c43
Merge branch 'next' into feat-settings-page
scopsy b9f2853
Merge branch 'feat-settings-page' into billing-age
scopsy f1eb4fb
fix: width
scopsy 377a9ab
fix: rows
scopsy abcce8b
Update settings.tsx
scopsy 465fa4e
fix: team invite CTA
scopsy 0a8d6d6
fix: space
scopsy 8f9f452
fix: lint
scopsy 628a784
fix: compare plans
scopsy 1562165
fix: broken import
scopsy 16ee5a5
Merge branch 'feat-settings-page' into billing-age
scopsy bf051dc
Merge branch 'next' into feat-settings-page
scopsy 93d5957
fix: update src
scopsy 0333136
Merge branch 'feat-settings-page' into billing-age
scopsy 1036aa9
Merge branch 'next' into billing-age
scopsy 7b09e3e
feat: pointer latest
scopsy d710cff
Merge branch 'next' into billing-age
scopsy 189a815
Update .source
scopsy 64d6360
fix: boolean pipe
scopsy 70afd1c
fix: small refactor
scopsy 069ca18
Update settings.tsx
scopsy 27372c8
feat: pr review
scopsy 0f6583f
fix: context
scopsy 4e8df6e
fix: states
scopsy 69b4d44
Update get-portal-link.e2e-ee.ts
scopsy 9d808b5
fix: add telemtry
scopsy 02428a2
fix: trial
scopsy ca47b6a
Merge branch 'next' into billing-age
scopsy 3a8a1c6
feat: add dashboard labeler
scopsy d5783a2
Merge branch 'next' into billing-age
scopsy 78eb4d0
fix: refactoring
scopsy c837b97
fix: navigation link and pr comments
scopsy 4d76955
Merge branch 'next' into billing-age
scopsy 372dd89
fix: lint errors
scopsy a19e0cb
Merge branch 'next' into billing-age
scopsy bdf8092
fix: pointer
scopsy fc19f6f
Merge branch 'next' into billing-age
scopsy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 |
---|---|---|
|
@@ -710,6 +710,7 @@ | |
"truncatewords", | ||
"xmlschema", | ||
"jsonify", | ||
"hsforms", | ||
"touchpoint", | ||
"Angularjs", | ||
"navigatable" | ||
|
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
Submodule .source
updated
from e37e9d to e35355
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
131 changes: 131 additions & 0 deletions
131
apps/dashboard/src/components/billing/active-plan-banner.tsx
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,131 @@ | ||
import { Badge } from '@/components/primitives/badge'; | ||
import { Card } from '@/components/primitives/card'; | ||
import { Progress } from '@/components/primitives/progress'; | ||
import { cn } from '../../utils/ui'; | ||
import { CalendarDays } from 'lucide-react'; | ||
import { PlanActionButton } from './plan-action-button'; | ||
import { Skeleton } from '@/components/primitives/skeleton'; | ||
import { useFetchSubscription } from '../../hooks/use-fetch-subscription'; | ||
|
||
interface ActivePlanBannerProps { | ||
selectedBillingInterval: 'month' | 'year'; | ||
} | ||
|
||
export function ActivePlanBanner({ selectedBillingInterval }: ActivePlanBannerProps) { | ||
const { subscription, daysLeft } = useFetchSubscription(); | ||
|
||
const getProgressColor = (current: number, max: number) => { | ||
const percentage = (current / max) * 100; | ||
if (percentage > 90) return 'text-destructive'; | ||
if (percentage > 75) return 'text-warning'; | ||
|
||
return ''; | ||
}; | ||
|
||
const getProgressBarColor = (current: number, max: number) => { | ||
const percentage = (current / max) * 100; | ||
if (percentage > 90) return 'bg-gradient-to-r from-red-500 to-red-400'; | ||
if (percentage > 75) return 'bg-gradient-to-r from-amber-500 to-amber-400'; | ||
|
||
return 'bg-gradient-to-r from-emerald-500 to-emerald-400'; | ||
}; | ||
|
||
const formatDate = (date: string | number) => { | ||
return new Date(date).toLocaleDateString('en-US', { | ||
month: 'short', | ||
day: 'numeric', | ||
year: 'numeric', | ||
}); | ||
}; | ||
|
||
return ( | ||
<div className="mt-6 flex space-y-3"> | ||
<Card className="mx-auto w-full max-w-[500px] overflow-hidden border shadow-none"> | ||
<div className="space-y-5 p-5"> | ||
<div className="flex items-start justify-between gap-4"> | ||
<div className="space-y-1"> | ||
<div className="flex items-center gap-3"> | ||
{!subscription ? ( | ||
<Skeleton className="h-7 w-24" /> | ||
) : ( | ||
<h3 className="text-lg font-semibold capitalize">{subscription.apiServiceLevel?.toLowerCase()}</h3> | ||
)} | ||
{subscription?.trial.isActive && ( | ||
<Badge variant="outline" className="font-medium"> | ||
Trial | ||
</Badge> | ||
)} | ||
</div> | ||
{subscription?.trial.isActive && ( | ||
<div className="text-warning text-sm font-medium">{daysLeft} days left for trial</div> | ||
)} | ||
</div> | ||
|
||
<PlanActionButton | ||
selectedBillingInterval={selectedBillingInterval} | ||
variant="outline" | ||
size="sm" | ||
className="shrink-0" | ||
/> | ||
</div> | ||
|
||
<div className="space-y-4"> | ||
<div className="text-muted-foreground flex items-center gap-2 text-sm"> | ||
<CalendarDays className="h-4 w-4" /> | ||
{!subscription ? ( | ||
<Skeleton className="h-4 w-48" /> | ||
) : ( | ||
<span> | ||
{formatDate(subscription.currentPeriodStart ?? Date.now())} -{' '} | ||
{formatDate(subscription.currentPeriodEnd ?? Date.now())} | ||
</span> | ||
)} | ||
</div> | ||
|
||
<div className="space-y-2"> | ||
<div className="flex items-center justify-between text-sm"> | ||
{subscription ? ( | ||
<> | ||
<div> | ||
<span | ||
className={cn( | ||
'font-medium', | ||
getProgressColor(subscription.events.current ?? 0, subscription.events.included ?? 0) | ||
)} | ||
> | ||
{subscription.events.current?.toLocaleString() ?? 0} | ||
</span>{' '} | ||
<span className="text-muted-foreground"> | ||
of {subscription.events.included?.toLocaleString() ?? 0} events | ||
</span> | ||
</div> | ||
<span className="text-muted-foreground text-xs">Updates hourly</span> | ||
</> | ||
) : ( | ||
<> | ||
<Skeleton className="h-4 w-36" /> | ||
<Skeleton className="h-4 w-24" /> | ||
</> | ||
)} | ||
</div> | ||
{subscription ? ( | ||
<Progress | ||
value={Math.min( | ||
((subscription.events.current ?? 0) / (subscription.events.included ?? 0)) * 100, | ||
100 | ||
)} | ||
className={cn( | ||
'h-1.5 rounded-lg transition-all duration-300', | ||
getProgressBarColor(subscription.events.current ?? 0, subscription.events.included ?? 0) | ||
)} | ||
/> | ||
) : ( | ||
<Skeleton className="h-1.5 w-full" /> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
</Card> | ||
</div> | ||
); | ||
} |
44 changes: 44 additions & 0 deletions
44
apps/dashboard/src/components/billing/contact-sales-button.tsx
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,44 @@ | ||
import { Button } from '@/components/primitives/button'; | ||
import { ApiServiceLevelEnum } from '@novu/shared'; | ||
import { useState } from 'react'; | ||
import { ContactSalesModal } from './contact-sales-modal'; | ||
import { useTelemetry } from '../../hooks/use-telemetry'; | ||
import { TelemetryEvent } from '../../utils/telemetry'; | ||
|
||
interface ContactSalesButtonProps { | ||
className?: string; | ||
variant?: 'default' | 'outline'; | ||
} | ||
|
||
export function ContactSalesButton({ className, variant = 'outline' }: ContactSalesButtonProps) { | ||
const [isContactSalesModalOpen, setIsContactSalesModalOpen] = useState(false); | ||
const track = useTelemetry(); | ||
|
||
const handleContactSales = () => { | ||
track(TelemetryEvent.BILLING_CONTACT_SALES_CLICKED, { | ||
intendedPlan: ApiServiceLevelEnum.ENTERPRISE, | ||
source: 'billing_page', | ||
}); | ||
setIsContactSalesModalOpen(true); | ||
}; | ||
|
||
const handleModalClose = () => { | ||
track(TelemetryEvent.BILLING_CONTACT_SALES_MODAL_CLOSED, { | ||
intendedPlan: ApiServiceLevelEnum.ENTERPRISE, | ||
}); | ||
setIsContactSalesModalOpen(false); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Button variant={variant} className={className} onClick={handleContactSales}> | ||
Contact sales | ||
</Button> | ||
<ContactSalesModal | ||
isOpen={isContactSalesModalOpen} | ||
onClose={handleModalClose} | ||
intendedApiServiceLevel={ApiServiceLevelEnum.ENTERPRISE} | ||
/> | ||
</> | ||
); | ||
} |
47 changes: 47 additions & 0 deletions
47
apps/dashboard/src/components/billing/contact-sales-modal.tsx
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,47 @@ | ||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/primitives/dialog'; | ||
import { ApiServiceLevelEnum } from '@novu/shared'; | ||
import { HubspotForm } from '../hubspot-form'; | ||
import { HUBSPOT_FORM_IDS } from './utils/hubspot.constants'; | ||
import { useAuth } from '@/context/auth/hooks'; | ||
import { toast } from 'sonner'; | ||
|
||
interface ContactSalesModalProps { | ||
isOpen: boolean; | ||
onClose: () => void; | ||
intendedApiServiceLevel: ApiServiceLevelEnum; | ||
} | ||
|
||
export function ContactSalesModal({ isOpen, onClose, intendedApiServiceLevel }: ContactSalesModalProps) { | ||
const { currentUser, currentOrganization } = useAuth(); | ||
|
||
if (!isOpen || !currentUser || !currentOrganization) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Dialog open={isOpen} onOpenChange={onClose}> | ||
<DialogContent className="p-10 sm:max-w-[840px]"> | ||
<DialogHeader> | ||
<DialogTitle>Contact sales</DialogTitle> | ||
</DialogHeader> | ||
<HubspotForm | ||
formId={HUBSPOT_FORM_IDS.UPGRADE_CONTACT_SALES} | ||
properties={{ | ||
firstname: currentUser.firstName || '', | ||
lastname: currentUser.lastName || '', | ||
email: currentUser.email || '', | ||
app_organizationid: currentOrganization._id, | ||
'TICKET.subject': `Contact Sales - ${intendedApiServiceLevel}`, | ||
'TICKET.content': '', | ||
}} | ||
readonlyProperties={['email']} | ||
focussedProperty="TICKET.content" | ||
onFormSubmitted={() => { | ||
toast.success('Thank you for contacting us! We will be in touch soon.'); | ||
onClose(); | ||
}} | ||
/> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
The path was previously wrong