fix: UI nits in QA verison of onboarding v3 (#25532)
* fix create button * fix subdomain prefix in orgs details view * remove redundant text * fix prefix on link fields * brand mode colours * Fix banner positioning * remove app descrtipion plus add mr to account for button * fix gaps * improve free plan welcome modal
This commit is contained in:
@@ -34,7 +34,7 @@ const ServerPage = async () => {
|
||||
return <TeamInviteEmailView userEmail={userEmail} />;
|
||||
}
|
||||
|
||||
return <TeamInviteView userEmail={userEmail} />;
|
||||
return <TeamInviteEmailView userEmail={userEmail} />;
|
||||
};
|
||||
|
||||
export default ServerPage;
|
||||
|
||||
@@ -50,7 +50,7 @@ export function EmailInviteForm({
|
||||
<div className="flex h-full w-full flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
{showTeamSelect ? (
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="mr-7 grid grid-cols-2">
|
||||
<Label className="text-emphasis mb-0 text-sm font-medium" htmlFor="invites.0.email">
|
||||
{t("email")}
|
||||
</Label>
|
||||
|
||||
@@ -32,7 +32,7 @@ export const OnboardingLayout = ({ userEmail, currentStep, totalSteps, children
|
||||
{/* Logo and container - centered */}
|
||||
<div className="flex w-full flex-1 flex-col items-center justify-center gap-6">
|
||||
<Logo className="mt-4 h-5 w-auto shrink-0" />
|
||||
<div className="border-subtle bg-default grid max-h-[690px] min-h-0 w-full max-w-[532px] flex-1 grid-cols-1 gap-6 overflow-hidden rounded-2xl border px-4 py-2 sm:px-12 sm:py-10 xl:h-[690px] xl:max-w-[1130px] xl:grid-cols-[40%_1fr] xl:pl-10 xl:pr-0">
|
||||
<div className="border-subtle bg-default grid max-h-[690px] min-h-0 w-full max-w-[532px] flex-1 grid-cols-1 gap-12 overflow-hidden rounded-2xl border px-4 py-2 sm:px-12 sm:py-10 xl:h-[690px] xl:max-w-[1130px] xl:grid-cols-[40%_1fr] xl:pl-10 xl:pr-0">
|
||||
{/* Column 1 - Always visible, 40% on xl+ */}
|
||||
<div className="flex h-full min-h-0 flex-col">{column1}</div>
|
||||
{/* Column 2 - Hidden on mobile, visible on xl+, 60% on xl+ */}
|
||||
|
||||
@@ -4,6 +4,8 @@ import classNames from "classnames";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
||||
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
@@ -18,6 +20,21 @@ type OnboardingBrowserViewProps = {
|
||||
teamSlug?: string;
|
||||
};
|
||||
|
||||
const getDisplayUrl = (
|
||||
orgSlug: string | null | undefined,
|
||||
username: string | null | undefined,
|
||||
teamSlug: string | undefined
|
||||
): string => {
|
||||
if (orgSlug) {
|
||||
return teamSlug !== undefined
|
||||
? `${orgSlug}.${subdomainSuffix()}/team/${teamSlug || ""}`
|
||||
: `${orgSlug}.${subdomainSuffix()}/${username || ""}`;
|
||||
}
|
||||
|
||||
const webappUrl = WEBAPP_URL.replace(/^https?:\/\//, "");
|
||||
return teamSlug !== undefined ? `${webappUrl}/team/${teamSlug || ""}` : `${webappUrl}/${username || ""}`;
|
||||
};
|
||||
|
||||
export const OnboardingBrowserView = ({
|
||||
avatar,
|
||||
name,
|
||||
@@ -27,9 +44,9 @@ export const OnboardingBrowserView = ({
|
||||
}: OnboardingBrowserViewProps) => {
|
||||
const { t } = useLocale();
|
||||
const pathname = usePathname();
|
||||
const webappUrl = WEBAPP_URL.replace(/^https?:\/\//, "");
|
||||
const displayUrl =
|
||||
teamSlug !== undefined ? `${webappUrl}/team/${teamSlug || ""}` : `${webappUrl}/${username || ""}`;
|
||||
const orgBranding = useOrgBranding();
|
||||
|
||||
const displayUrl = getDisplayUrl(orgBranding?.slug, username, teamSlug);
|
||||
|
||||
// Animation variants for entry and exit
|
||||
const containerVariants = {
|
||||
|
||||
@@ -133,22 +133,44 @@ export const OnboardingInviteBrowserView = ({
|
||||
duration: 0.5,
|
||||
ease: "backOut",
|
||||
}}>
|
||||
<div className="bg-default border-subtle flex flex-col gap-4 rounded-2xl border p-8">
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<Avatar
|
||||
size="lg"
|
||||
imageSrc={avatar || undefined}
|
||||
alt={displayName}
|
||||
className="border-default h-12 w-12 border-2"
|
||||
/>
|
||||
<div className="flex w-full flex-col items-start gap-1">
|
||||
<h2 className="text-emphasis font-cal w-full text-left text-xl font-semibold leading-tight">
|
||||
{displayName}
|
||||
</h2>
|
||||
<p className="text-subtle text-left text-sm font-normal leading-tight">
|
||||
{displayBio || "We're emailing you all the details"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-default border-subtle flex flex-col rounded-2xl border">
|
||||
<div className="relative p-1">
|
||||
{/* Banner Image */}
|
||||
{organizationBrand.banner && (
|
||||
<div className="border-subtle relative h-36 w-full overflow-hidden rounded-xl border">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={organizationBrand.banner}
|
||||
alt={displayName}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Organization Avatar - Overlaying the banner */}
|
||||
{organizationBrand.banner && avatar && (
|
||||
<div className="absolute -bottom-6 left-4">
|
||||
<Avatar size="lg" imageSrc={avatar} alt={displayName} className="h-12 w-12 border" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Organization Info */}
|
||||
<div className={`flex flex-col items-start gap-1 px-4 pb-4 pt-8`}>
|
||||
{!organizationBrand.banner && avatar && (
|
||||
<Avatar
|
||||
size="lg"
|
||||
imageSrc={avatar}
|
||||
alt={displayName}
|
||||
className="border-default mb-4 h-12 w-12 border-2"
|
||||
/>
|
||||
)}
|
||||
<h2 className="text-emphasis font-cal w-full text-left text-xl font-semibold leading-tight">
|
||||
{displayName}
|
||||
</h2>
|
||||
<p className="text-subtle text-left text-sm font-normal leading-tight">
|
||||
{displayBio || "We're emailing you all the details"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,19 +3,30 @@
|
||||
import classNames from "classnames";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Icon, type IconName } from "@calcom/ui/components/icon";
|
||||
|
||||
// Helper function to darken a hex color
|
||||
const darkenColor = (hex: string, amount: number): string => {
|
||||
const num = parseInt(hex.replace("#", ""), 16);
|
||||
const r = Math.max(0, Math.min(255, ((num >> 16) & 0xff) * (1 - amount)));
|
||||
const g = Math.max(0, Math.min(255, ((num >> 8) & 0xff) * (1 - amount)));
|
||||
const b = Math.max(0, Math.min(255, (num & 0xff) * (1 - amount)));
|
||||
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, "0")}`;
|
||||
};
|
||||
|
||||
type OnboardingOrganizationBrowserViewProps = {
|
||||
avatar?: string | null;
|
||||
name?: string;
|
||||
bio?: string;
|
||||
slug?: string;
|
||||
bannerUrl?: string | null;
|
||||
brandColor?: string;
|
||||
};
|
||||
|
||||
export const OnboardingOrganizationBrowserView = ({
|
||||
@@ -24,11 +35,22 @@ export const OnboardingOrganizationBrowserView = ({
|
||||
bio,
|
||||
slug,
|
||||
bannerUrl,
|
||||
brandColor,
|
||||
}: OnboardingOrganizationBrowserViewProps) => {
|
||||
const { t } = useLocale();
|
||||
const pathname = usePathname();
|
||||
const webappUrl = WEBAPP_URL.replace(/^https?:\/\//, "");
|
||||
const displayUrl = `${webappUrl}/${slug || ""}`;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const displayUrl = slug ? `${slug}.${subdomainSuffix()}` : subdomainSuffix();
|
||||
|
||||
// Update CSS variables when brandColor changes
|
||||
useEffect(() => {
|
||||
if (brandColor && containerRef.current) {
|
||||
containerRef.current.style.setProperty("--cal-brand", brandColor);
|
||||
// Set brand-emphasis as a slightly darker version for hover states
|
||||
const darkerColor = darkenColor(brandColor, 0.1);
|
||||
containerRef.current.style.setProperty("--cal-brand-emphasis", darkerColor);
|
||||
}
|
||||
}, [brandColor]);
|
||||
|
||||
// Animation variants for entry and exit
|
||||
const containerVariants = {
|
||||
@@ -79,7 +101,9 @@ export const OnboardingOrganizationBrowserView = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="bg-default border-subtle hidden h-full w-full flex-col overflow-hidden rounded-l-2xl border-y border-s xl:flex">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="bg-default border-subtle hidden h-full w-full flex-col overflow-hidden rounded-l-2xl border-y border-s xl:flex">
|
||||
{/* Browser header */}
|
||||
<div className="border-subtle bg-default flex min-w-0 shrink-0 items-center gap-3 rounded-t-2xl border-b p-3">
|
||||
{/* Navigation buttons */}
|
||||
@@ -164,7 +188,7 @@ export const OnboardingOrganizationBrowserView = ({
|
||||
</div>
|
||||
<p className="text-subtle text-sm font-medium leading-tight">{event.description}</p>
|
||||
</div>
|
||||
<Button color="secondary" size="sm" EndIcon="arrow-right" tabIndex={-1}>
|
||||
<Button color="primary" size="sm" EndIcon="arrow-right" tabIndex={-1}>
|
||||
{t("book_now")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -108,11 +108,6 @@ export const OnboardingTeamsBrowserView = ({
|
||||
{/* Teams Info */}
|
||||
<div className="flex flex-col gap-2 px-4 pb-4 pt-12">
|
||||
<h2 className="text-emphasis text-xl font-semibold leading-tight">{t("teams")}</h2>
|
||||
<p className="text-subtle text-sm leading-normal">
|
||||
{hasValidTeams
|
||||
? t("onboarding_teams_browser_view_subtitle")
|
||||
: t("onboarding_teams_browser_view_placeholder_subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,11 +18,13 @@ export function useCreateTeam() {
|
||||
const createTeamMutation = trpc.viewer.teams.create.useMutation();
|
||||
const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation();
|
||||
|
||||
const createTeam = async (store: OnboardingState) => {
|
||||
const createTeam = async (store?: OnboardingState) => {
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const { teamDetails, teamBrand } = store;
|
||||
// Get the latest state from the store to ensure we have the most up-to-date values
|
||||
const currentStore = store || useOnboardingStore.getState();
|
||||
const { teamDetails, teamBrand } = currentStore;
|
||||
|
||||
// Validate team details - if empty, redirect back to team details step
|
||||
if (!teamDetails.name || !teamDetails.name.trim() || !teamDetails.slug || !teamDetails.slug.trim()) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
@@ -11,6 +12,7 @@ import type { OnboardingState } from "../store/onboarding-store";
|
||||
|
||||
export const useSubmitOnboarding = () => {
|
||||
const router = useRouter();
|
||||
const { update: updateSession } = useSession();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const flags = useFlagMap();
|
||||
@@ -78,6 +80,8 @@ export const useSubmitOnboarding = () => {
|
||||
// No checkout URL means billing is disabled (self-hosted flow)
|
||||
// Organization has already been created by the backend
|
||||
showToast("Organization created successfully!", "success");
|
||||
// Refresh session to get updated organization data
|
||||
await updateSession();
|
||||
// Set flag to show welcome modal after personal onboarding redirect
|
||||
setShowNewOrgModalFlag();
|
||||
skipToPersonal(resetOnboarding);
|
||||
|
||||
@@ -189,6 +189,7 @@ export const OrganizationBrandView = ({ userEmail }: OrganizationBrandViewProps)
|
||||
bio={organizationDetails.bio}
|
||||
slug={organizationDetails.link}
|
||||
bannerUrl={bannerPreview}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
</OnboardingLayout>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,6 @@ export const InstallableAppCard = ({
|
||||
title={app.name}>
|
||||
{app.name}
|
||||
</p>
|
||||
<p className="text-subtle hidden text-left text-xs leading-tight xl:line-clamp-2">{app.description}</p>
|
||||
<InstallAppButtonWithoutPlanCheck
|
||||
type={app.type}
|
||||
options={installOptions}
|
||||
|
||||
@@ -54,7 +54,7 @@ export const TeamInviteEmailView = ({ userEmail }: TeamInviteEmailViewProps) =>
|
||||
const formSchema = z.object({
|
||||
invites: z.array(
|
||||
z.object({
|
||||
email: z.string().email(t("invalid_email_address")),
|
||||
email: z.union([z.literal(""), z.string().email(t("invalid_email_address"))]),
|
||||
role: z.enum(["MEMBER", "ADMIN"]),
|
||||
})
|
||||
),
|
||||
@@ -151,7 +151,7 @@ export const TeamInviteEmailView = ({ userEmail }: TeamInviteEmailViewProps) =>
|
||||
{t("onboarding_skip_for_now")}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
type="button"
|
||||
color="primary"
|
||||
className="rounded-[10px]"
|
||||
disabled={!hasValidInvites || isSubmitting}
|
||||
|
||||
@@ -401,8 +401,14 @@
|
||||
"welcome_back": "Welcome back",
|
||||
"welcome_to_calcom": "Welcome to {{appName}}",
|
||||
"personal_welcome_description": "You're all set! Start scheduling meetings and managing your calendar.",
|
||||
"unlimited_calendars": "Unlimited calendars",
|
||||
"unlimited_event_types": "Unlimited event types",
|
||||
"workflows_feature": "Workflows",
|
||||
"integrate_with_favorite_apps": "Integrate with your favorite apps",
|
||||
"accept_payments_via_stripe": "Accept payments via Stripe",
|
||||
"html_react_embed": "HTML & React Embed",
|
||||
"cal_ai_phone_agent": "Cal.ai phone agent",
|
||||
"cal_video": "Cal Video",
|
||||
"and_more": "And more…",
|
||||
"welcome_instructions": "Tell us what to call you and let us know what timezone you're in. You'll be able to edit this later.",
|
||||
"connect_caldav": "Connect to CalDav (Beta)",
|
||||
|
||||
@@ -9,7 +9,16 @@ import { Logo } from "@calcom/ui/components/logo";
|
||||
|
||||
import { useWelcomeToCalcomModal } from "../hooks/useWelcomeToCalcomModal";
|
||||
|
||||
const features = ["unlimited_event_types", "workflows_feature", "and_more"];
|
||||
const features = [
|
||||
"unlimited_calendars",
|
||||
"unlimited_event_types",
|
||||
"workflows_feature",
|
||||
"integrate_with_favorite_apps",
|
||||
"accept_payments_via_stripe",
|
||||
"html_react_embed",
|
||||
"cal_ai_phone_agent",
|
||||
"cal_video",
|
||||
];
|
||||
|
||||
export function WelcomeToCalcomModal() {
|
||||
const { t } = useLocale();
|
||||
|
||||
Reference in New Issue
Block a user