diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index b956608523..95cbc704fc 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -108,6 +108,8 @@ "link_expires": "p.s. It expires in {{expiresIn}} hours.", "upgrade_to_per_seat": "Upgrade to Per-Seat", "team_upgrade_seats_details": "Of the {{memberCount}} members in your team, {{unpaidCount}} seat(s) are unpaid. At ${{seatPrice}}/month per seat the estimated total cost of your membership is ${{totalCost}}/month.", + "team_upgrade_banner_description": "Thank you for trialing our new team plan. We noticed your team \"{{teamName}}\" needs to be upgraded.", + "team_upgrade_banner_action": "Upgrade here", "team_upgraded_successfully": "Your team was upgraded successfully!", "use_link_to_reset_password": "Use the link below to reset your password", "hey_there": "Hey there,", @@ -1371,4 +1373,3 @@ "test_preview_description": "Test your routing form without submitting any data", "test_routing": "Test Routing" } - diff --git a/packages/features/ee/teams/api/upgrade.ts b/packages/features/ee/teams/api/upgrade.ts index 1b9f23d128..6e3b77c13d 100644 --- a/packages/features/ee/teams/api/upgrade.ts +++ b/packages/features/ee/teams/api/upgrade.ts @@ -39,13 +39,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { if (!team) { const prevTeam = await prisma.team.findFirstOrThrow({ where: { id } }); const metadata = teamMetadataSchema.parse(prevTeam.metadata); - if (!metadata?.requestedSlug) throw new HttpError({ statusCode: 400, message: "Missing requestedSlug" }); - + /** Legacy teams already have a slug, this will allow them to upgrade as well */ + const slug = prevTeam.slug || metadata?.requestedSlug; + if (!slug) throw new HttpError({ statusCode: 400, message: "Missing team slug" }); try { - team = await prisma.team.update({ + /** We save the metadata first to prevent duplicate payments */ + await prisma.team.update({ where: { id }, data: { - slug: metadata.requestedSlug, metadata: { paymentId: checkoutSession.id, subscriptionId: subscription.id || null, @@ -53,8 +54,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { }, }, }); + /** Then we try to upgrade the slug, which may fail if a conflict came up since team creation */ + team = await prisma.team.update({ where: { id }, data: { slug } }); } catch (error) { - const { message, statusCode } = getRequestedSlugError(error, metadata.requestedSlug); + const { message, statusCode } = getRequestedSlugError(error, slug); return res.status(statusCode).json({ message }); } diff --git a/packages/features/ee/teams/components/TeamListItem.tsx b/packages/features/ee/teams/components/TeamListItem.tsx index 9f6b2d3829..f026ced0f9 100644 --- a/packages/features/ee/teams/components/TeamListItem.tsx +++ b/packages/features/ee/teams/components/TeamListItem.tsx @@ -7,6 +7,7 @@ import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { Icon } from "@calcom/ui/Icon"; +import { Avatar } from "@calcom/ui/components"; import { Button } from "@calcom/ui/components/button"; import { ButtonGroup } from "@calcom/ui/components/buttonGroup"; import ConfirmationDialogContent from "@calcom/ui/v2/core/ConfirmationDialogContent"; @@ -21,8 +22,6 @@ import Dropdown, { import { Tooltip } from "@calcom/ui/v2/core/Tooltip"; import showToast from "@calcom/ui/v2/core/notifications"; -import Avatar from "@components/ui/Avatar"; - import { TeamRole } from "./TeamPill"; interface Props { @@ -65,7 +64,7 @@ export default function TeamListItem(props: Props) { const teamInfo = (
{["warning", "error"].includes(variant) && ( diff --git a/packages/ui/v2/core/Shell.tsx b/packages/ui/v2/core/Shell.tsx index 32b1ce5427..1544cd8d4d 100644 --- a/packages/ui/v2/core/Shell.tsx +++ b/packages/ui/v2/core/Shell.tsx @@ -11,9 +11,10 @@ import { useIsEmbed } from "@calcom/embed-core/embed-iframe"; import UnconfirmedBookingBadge from "@calcom/features/bookings/UnconfirmedBookingBadge"; import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner"; import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem"; +import { TeamsUpgradeBanner } from "@calcom/features/ee/teams/components"; import CustomBranding from "@calcom/lib/CustomBranding"; import classNames from "@calcom/lib/classNames"; -import { JOIN_SLACK, ROADMAP, DESKTOP_APP_LINK, WEBAPP_URL } from "@calcom/lib/constants"; +import { DESKTOP_APP_LINK, JOIN_SLACK, ROADMAP, WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useTheme from "@calcom/lib/hooks/useTheme"; import isCalcom from "@calcom/lib/isCalcom"; @@ -23,9 +24,9 @@ import { SVGComponent } from "@calcom/types/SVGComponent"; import Dropdown, { DropdownMenuContent, DropdownMenuItem, + DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuTrigger, - DropdownMenuPortal, } from "@calcom/ui/Dropdown"; import { Icon } from "@calcom/ui/Icon"; import TimezoneChangeDialog from "@calcom/ui/TimezoneChangeDialog"; @@ -147,6 +148,7 @@ const Layout = (props: LayoutProps) => {