diff --git a/apps/web/public/gated-features/roles_and_permissions.svg b/apps/web/public/gated-features/roles_and_permissions.svg new file mode 100644 index 0000000000..d3dea0485f --- /dev/null +++ b/apps/web/public/gated-features/roles_and_permissions.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 9d3bb489a1..006965a5d2 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -4093,6 +4093,11 @@ "onboarding_teams_browser_view_no_teams_title": "No teams yet", "onboarding_teams_browser_view_no_teams_description": "Add teams to get started", "book_now": "Book now", + "only_available_on_orgs_plan": "This feature is available on the Organization plan", + "upgrade_team_to_orgs_with_price": "Upgrade your team to get access to this and much more for $37 per month/user.", + "attribute_based_routing": "Attribute-based routing", + "compliance_check": "SOC2, HIPAA, ISO 27001 compliance check", + "and_more_features": "and more features...", "view": "View", "booking_response": "Booking Response", "list_view": "List view", diff --git a/packages/features/shell/DynamicModals.tsx b/packages/features/shell/DynamicModals.tsx index 8ad9aa6e3f..6e066a722d 100644 --- a/packages/features/shell/DynamicModals.tsx +++ b/packages/features/shell/DynamicModals.tsx @@ -3,6 +3,7 @@ import { WelcomeToOrganizationsModal } from "@calcom/features/ee/organizations/components/WelcomeToOrganizationsModal"; import { WelcomeToCalcomModal } from "./components/WelcomeToCalcomModal"; +import { GatedFeaturesModal } from "./components/GatedFeaturesModal"; /** * Container for all query-param driven modals that should appear globally across the app. @@ -20,6 +21,7 @@ export function DynamicModals() { {/* Add more query-param driven modals here */} + ); } diff --git a/packages/features/shell/components/GatedFeaturesModal.tsx b/packages/features/shell/components/GatedFeaturesModal.tsx new file mode 100644 index 0000000000..6d80858d37 --- /dev/null +++ b/packages/features/shell/components/GatedFeaturesModal.tsx @@ -0,0 +1,77 @@ +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Dialog, DialogContent } from "@calcom/ui/components/dialog"; +import { Button } from "@calcom/ui/components/button"; + +import { useGatedFeaturesStore, GatedFeatures } from "../stores/gatedFeaturesStore"; + +type FeatureContent = { + badgeText: string; + title: string; + image: string; + description: string; + features: string[]; + learnMoreUrl: string; + ctaUrl: string; +}; + +const content: Record = { + roles_and_permissions: { + badgeText: "roles_and_permissions", + title: "only_available_on_orgs_plan", + image: "/gated-features/roles_and_permissions.svg", + description: "upgrade_team_to_orgs_with_price", + features: [ + "1_parent_team_unlimited_subteams", + "attribute_based_routing", + "compliance_check", + "and_more_features", + ], + learnMoreUrl: "https://cal.com/enterprise", + ctaUrl: "/settings/organizations/new", + }, +}; + +export function GatedFeaturesModal() { + const { t } = useLocale(); + const isOpen = useGatedFeaturesStore((state) => state.isOpen); + const activeFeature = useGatedFeaturesStore((state) => state.activeFeature); + const close = useGatedFeaturesStore((state) => state.close); + + const data = activeFeature ? content[activeFeature] : null; + + if (!data) { + return null; + } + + const { badgeText, description, image, title, features, learnMoreUrl, ctaUrl } = data; + + return ( + !open && close()}> + +
+ {t(title)} +
{t(badgeText)}
+ +
+
{t(title)}
+
{t(description)}
+
+
+
    + {features.map((feature) => ( +
  • {t(feature)}
  • + ))} +
+
+
+
+ +
+ + +
+
+
+
+ ); +} diff --git a/packages/features/shell/stores/gatedFeaturesStore.ts b/packages/features/shell/stores/gatedFeaturesStore.ts new file mode 100644 index 0000000000..ba67f369bd --- /dev/null +++ b/packages/features/shell/stores/gatedFeaturesStore.ts @@ -0,0 +1,19 @@ +import { create } from "zustand"; + +export enum GatedFeatures { + RolesAndPermissions = "roles_and_permissions", +} + +type GatedFeaturesStore = { + isOpen: boolean; + activeFeature: GatedFeatures | null; + open: (feature: GatedFeatures) => void; + close: () => void; +}; + +export const useGatedFeaturesStore = create((set) => ({ + isOpen: false, + activeFeature: null, + open: (feature) => set({ isOpen: true, activeFeature: feature }), + close: () => set({ isOpen: false, activeFeature: null }), +})); diff --git a/packages/features/tips/Tips.tsx b/packages/features/tips/Tips.tsx index c7a1912a3c..fc899fdf94 100644 --- a/packages/features/tips/Tips.tsx +++ b/packages/features/tips/Tips.tsx @@ -6,6 +6,8 @@ import posthog from "posthog-js"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { localStorage } from "@calcom/lib/webstorage"; import { Card } from "@calcom/ui/components/card"; +import { GatedFeatures } from "@calcom/features/shell/stores/gatedFeaturesStore"; +import { useGatedFeaturesStore } from "@calcom/features/shell/stores/gatedFeaturesStore"; type Tip = { id: number; @@ -13,154 +15,164 @@ type Tip = { mediaLink?: string; title: string; description: string; - href: string; + href?: string; + onClick?: () => void; coverPhoto?: string; variant?: "SidebarCard" | "NewLaunchSidebarCard"; }; -export const tips: Tip[] = [ - { - id: 17, - thumbnailUrl: "https://img.youtube.com/vi/fMHW6jYPIb8/0.jpg", - mediaLink: "https://go.cal.com/embed-video", - title: "Embed", - description: "Embed your booking page on your website", - href: "https://go.cal.com/embed-video", - }, - { - id: 16, - thumbnailUrl: "https://img.youtube.com/vi/xopxmk2H4Ng/0.jpg", - mediaLink: "https://go.cal.com/paid-booking", - title: "Paid Bookings", - description: "Charge for your time with Cal.com's paid bookings", - href: "https://go.cal.com/paid-booking", - }, - { - id: 15, - thumbnailUrl: "https://img.youtube.com/vi/ZjSD1yPgLLQ/0.jpg", - mediaLink: "https://go.cal.com/instant-meetings-video", - title: "Instant Meetings", - description: "Book meetings instantly with a link", - href: "https://go.cal.com/instant-meetings-video", - }, - { - id: 14, - thumbnailUrl: "https://img.youtube.com/vi/IZ4-nUiIpvY/0.jpg", - mediaLink: "https://go.cal.com/custom-attributes", - title: "Custom Attributes", - description: "Define roles and attributes for your teams", - href: "https://go.cal.com/custom-attributes", - }, - { - id: 13, - thumbnailUrl: "https://img.youtube.com/vi/TTAASLLPKk0/0.jpg", - mediaLink: "https://go.cal.com/ooo-video", - title: "ooo.new", - description: "Easily go out-of-office", - href: "https://go.cal.com/ooo-video", - }, - { - id: 12, - thumbnailUrl: "https://cal.com/og-image-cal-ai.jpg", - mediaLink: "https://go.cal.com/cal-ai", - title: "Cal.ai", - description: "Your personal AI scheduling assistant", - href: "https://go.cal.com/cal-ai", - }, - { - id: 11, - thumbnailUrl: "https://img.youtube.com/vi/KTg_qzA9NEc/0.jpg", - mediaLink: "https://go.cal.com/insights", - title: "Insights", - description: "Get a better understanding of your business", - href: "https://go.cal.com/insights", - }, - { - id: 10, - thumbnailUrl: "https://img.youtube.com/vi/jvaBafzVUQc/0.jpg", - mediaLink: "https://go.cal.com/video", - title: "Cal Video", - description: "Free video conferencing with recording", - href: "https://go.cal.com/video", - }, - { - id: 9, - thumbnailUrl: "https://img.youtube.com/vi/93iOmzHieCU/0.jpg", - mediaLink: "https://go.cal.com/round-robin", - title: "Round-Robin", - description: "Create advanced group meetings with round-robin", - href: "https://go.cal.com/round-robin", - }, - { - id: 8, - thumbnailUrl: "https://img.youtube.com/vi/piKlAiibAFo/0.jpg", - mediaLink: "https://go.cal.com/workflows", - title: "Automate Workflows", - description: "Make time work for you and automate tasks", - href: "https://go.cal.com/workflows", - }, - { - id: 7, - thumbnailUrl: "https://img.youtube.com/vi/UVXgo12cY4g/0.jpg", - mediaLink: "https://go.cal.com/routing-forms", - title: "Routing Forms", - description: "Ask questions and route to the correct person", - href: "https://go.cal.com/routing-forms", - }, - { - id: 6, - thumbnailUrl: "https://img.youtube.com/vi/yGiZo1Ry5-8/0.jpg", - mediaLink: "https://go.cal.com/recurring-video", - title: "Recurring Bookings", - description: "Learn how to create a recurring schedule", - href: "https://go.cal.com/recurring-video", - }, - { - id: 5, - thumbnailUrl: "https://img.youtube.com/vi/0v_nQtpxC_4/0.jpg", - mediaLink: "https://go.cal.com/payments-video", - title: "Accept Payments", - description: "Charge for your time with Cal.com's Stripe App", - href: "https://app.cal.com/apps/stripe", - }, - { - id: 4, - thumbnailUrl: "https://img.youtube.com/vi/zGr_s-fG84k/0.jpg", - mediaLink: "https://go.cal.com/confirmation-video", - title: "Requires Confirmation", - description: "Learn how to be in charge of your bookings", - href: "https://cal.com/resources/feature/opt-in", - }, - { - id: 3, - thumbnailUrl: "https://img.youtube.com/vi/-L8MCiT6uhQ/0.jpg", - mediaLink: "https://youtu.be/-L8MCiT6uhQ?si=DRJzRNhN85hqAwBl", - title: "Routing Forms, Workflows", - description: "Ask screening questions of potential bookers to connect them with the right person", - href: "https://youtu.be/-L8MCiT6uhQ?si=DRJzRNhN85hqAwBl", - }, - { - id: 2, - thumbnailUrl: "https://img.youtube.com/vi/EAc46SPL6iA/0.jpg", - mediaLink: "https://go.cal.com/teams-video", - title: "How to set up Teams", - description: "Learn how to use round-robin and collective events.", - href: "https://cal.com/docs/enterprise-features/teams", - }, - { - id: 1, - thumbnailUrl: "https://img.youtube.com/vi/VZ5PfQzzxBw/0.jpg", - mediaLink: "https://youtu.be/VZ5PfQzzxBw?si=oB0LPSxG8dWIwg5a", - title: "Dynamic booking links", - description: "Booking link that allows people to quickly schedule meetings.", - href: "https://youtu.be/VZ5PfQzzxBw?si=oB0LPSxG8dWIwg5a", - }, -]; - -const reversedTips = [...shuffle(tips).slice(0).reverse()]; - function Tips() { const { t } = useLocale(); + const openModal = useGatedFeaturesStore((state) => state.open); + + const tips: Tip[] = [ + { + id: 18, + thumbnailUrl: "https://img.youtube.com/vi/J8HsK-8W39U/0.jpg", + mediaLink: "https://go.cal.com/rbac-video", + title: "Roles & Permissions", + description: "Manage team access with roles & permissions", + onClick: () => openModal(GatedFeatures.RolesAndPermissions), + }, + { + id: 17, + thumbnailUrl: "https://img.youtube.com/vi/fMHW6jYPIb8/0.jpg", + mediaLink: "https://go.cal.com/embed-video", + title: "Embed", + description: "Embed your booking page on your website", + href: "https://go.cal.com/embed-video", + }, + { + id: 16, + thumbnailUrl: "https://img.youtube.com/vi/xopxmk2H4Ng/0.jpg", + mediaLink: "https://go.cal.com/paid-booking", + title: "Paid Bookings", + description: "Charge for your time with Cal.com's paid bookings", + href: "https://go.cal.com/paid-booking", + }, + { + id: 15, + thumbnailUrl: "https://img.youtube.com/vi/ZjSD1yPgLLQ/0.jpg", + mediaLink: "https://go.cal.com/instant-meetings-video", + title: "Instant Meetings", + description: "Book meetings instantly with a link", + href: "https://go.cal.com/instant-meetings-video", + }, + { + id: 14, + thumbnailUrl: "https://img.youtube.com/vi/IZ4-nUiIpvY/0.jpg", + mediaLink: "https://go.cal.com/custom-attributes", + title: "Custom Attributes", + description: "Define roles and attributes for your teams", + href: "https://go.cal.com/custom-attributes", + }, + { + id: 13, + thumbnailUrl: "https://img.youtube.com/vi/TTAASLLPKk0/0.jpg", + mediaLink: "https://go.cal.com/ooo-video", + title: "ooo.new", + description: "Easily go out-of-office", + href: "https://go.cal.com/ooo-video", + }, + { + id: 12, + thumbnailUrl: "https://cal.com/og-image-cal-ai.jpg", + mediaLink: "https://go.cal.com/cal-ai", + title: "Cal.ai", + description: "Your personal AI scheduling assistant", + href: "https://go.cal.com/cal-ai", + }, + { + id: 11, + thumbnailUrl: "https://img.youtube.com/vi/KTg_qzA9NEc/0.jpg", + mediaLink: "https://go.cal.com/insights", + title: "Insights", + description: "Get a better understanding of your business", + href: "https://go.cal.com/insights", + }, + { + id: 10, + thumbnailUrl: "https://img.youtube.com/vi/jvaBafzVUQc/0.jpg", + mediaLink: "https://go.cal.com/video", + title: "Cal Video", + description: "Free video conferencing with recording", + href: "https://go.cal.com/video", + }, + { + id: 9, + thumbnailUrl: "https://img.youtube.com/vi/93iOmzHieCU/0.jpg", + mediaLink: "https://go.cal.com/round-robin", + title: "Round-Robin", + description: "Create advanced group meetings with round-robin", + href: "https://go.cal.com/round-robin", + }, + { + id: 8, + thumbnailUrl: "https://img.youtube.com/vi/piKlAiibAFo/0.jpg", + mediaLink: "https://go.cal.com/workflows", + title: "Automate Workflows", + description: "Make time work for you and automate tasks", + href: "https://go.cal.com/workflows", + }, + { + id: 7, + thumbnailUrl: "https://img.youtube.com/vi/UVXgo12cY4g/0.jpg", + mediaLink: "https://go.cal.com/routing-forms", + title: "Routing Forms", + description: "Ask questions and route to the correct person", + href: "https://go.cal.com/routing-forms", + }, + { + id: 6, + thumbnailUrl: "https://img.youtube.com/vi/yGiZo1Ry5-8/0.jpg", + mediaLink: "https://go.cal.com/recurring-video", + title: "Recurring Bookings", + description: "Learn how to create a recurring schedule", + href: "https://go.cal.com/recurring-video", + }, + { + id: 5, + thumbnailUrl: "https://img.youtube.com/vi/0v_nQtpxC_4/0.jpg", + mediaLink: "https://go.cal.com/payments-video", + title: "Accept Payments", + description: "Charge for your time with Cal.com's Stripe App", + href: "https://app.cal.com/apps/stripe", + }, + { + id: 4, + thumbnailUrl: "https://img.youtube.com/vi/zGr_s-fG84k/0.jpg", + mediaLink: "https://go.cal.com/confirmation-video", + title: "Requires Confirmation", + description: "Learn how to be in charge of your bookings", + href: "https://cal.com/resources/feature/opt-in", + }, + { + id: 3, + thumbnailUrl: "https://img.youtube.com/vi/-L8MCiT6uhQ/0.jpg", + mediaLink: "https://youtu.be/-L8MCiT6uhQ?si=DRJzRNhN85hqAwBl", + title: "Routing Forms, Workflows", + description: "Ask screening questions of potential bookers to connect them with the right person", + href: "https://youtu.be/-L8MCiT6uhQ?si=DRJzRNhN85hqAwBl", + }, + { + id: 2, + thumbnailUrl: "https://img.youtube.com/vi/EAc46SPL6iA/0.jpg", + mediaLink: "https://go.cal.com/teams-video", + title: "How to set up Teams", + description: "Learn how to use round-robin and collective events.", + href: "https://cal.com/teams", + }, + { + id: 1, + thumbnailUrl: "https://img.youtube.com/vi/VZ5PfQzzxBw/0.jpg", + mediaLink: "https://youtu.be/VZ5PfQzzxBw?si=oB0LPSxG8dWIwg5a", + title: "Dynamic booking links", + description: "Booking link that allows people to quickly schedule meetings.", + href: "https://youtu.be/VZ5PfQzzxBw?si=oB0LPSxG8dWIwg5a", + }, + ]; + + const reversedTips = [...shuffle(tips).slice(0).reverse()]; const [list, setList] = useState(() => { if (typeof window === "undefined") { @@ -230,8 +242,9 @@ function Tips() { mediaLinkOnClick={ isTopTip ? () => { - posthog.capture("tip_video_clicked", tip); - } + posthog.capture("tip_video_clicked", tip); + if (tip.onClick) tip.onClick(); + } : undefined } title={t(tip.title)} @@ -239,23 +252,24 @@ function Tips() { learnMore={ isTopTip ? { - href: tip.href, - text: t("learn_more"), - onClick: () => { - posthog.capture("tip_learn_more_clicked", tip); - }, + href: tip.href, + text: t("learn_more"), + onClick: () => { + posthog.capture("tip_learn_more_clicked", tip); + if (tip.onClick) tip.onClick(); } + } : undefined } actionButton={ isTopTip ? { - onClick: () => { - posthog.capture("tip_dismiss_clicked", tip); - handleRemoveItem(tip.id); - }, - child: t("dismiss"), - } + onClick: () => { + posthog.capture("tip_dismiss_clicked", tip); + handleRemoveItem(tip.id); + }, + child: t("dismiss"), + } : undefined } containerProps={{ diff --git a/packages/ui/components/card/Card.tsx b/packages/ui/components/card/Card.tsx index 0493f95cba..ceadfa9046 100644 --- a/packages/ui/components/card/Card.tsx +++ b/packages/ui/components/card/Card.tsx @@ -133,9 +133,9 @@ export interface BaseCardProps extends CVACardType { "data-testid"?: string; }; learnMore?: { - href: string; + href?: string; text: string; - onClick?: (event: React.MouseEvent) => void; + onClick?: (event: React.MouseEvent) => void; }; mediaLink?: string; mediaLinkOnClick?: (event: React.MouseEvent) => void; @@ -161,7 +161,7 @@ export function Card({ buttonClassName, mediaLinkOnClick, }: BaseCardProps) { - const LinkComponent = learnMore && learnMore.href.startsWith("https") ? "a" : Link; + const LinkComponent = learnMore?.href?.startsWith("https") ? "a" : Link; return (
{learnMore && ( - - {learnMore.text} - + <> + {learnMore.href ? ( + + {learnMore.text} + + ) : learnMore.onClick ? ( + + ) : undefined} + )} {actionButton?.child && (