Desktop first banner, mobile pending

This commit is contained in:
Leo Giovanetti 2023-05-25 12:36:19 -03:00
parent 582079d32c
commit 68e03945d0
9 changed files with 257 additions and 1 deletions

View File

@ -11,6 +11,7 @@ import useIntercom from "@calcom/features/ee/support/lib/intercom/useIntercom";
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
import CreateEventTypeDialog from "@calcom/features/eventtypes/components/CreateEventTypeDialog";
import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog";
import { useFlagMap } from "@calcom/features/flags/context/provider";
import Shell from "@calcom/features/shell/Shell";
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -57,6 +58,7 @@ import {
Trash,
Upload,
Users,
X,
} from "@calcom/ui/components/icon";
import { withQuery } from "@lib/QueryCell";
@ -731,6 +733,40 @@ const CreateFirstEventTypeView = () => {
);
};
const SetupOrganizationBanner = () => {
const { t } = useLocale();
return (
<div className="bg-inverted text-inverted relative mx-4 mt-4 h-56 max-w-full rounded-md bg-[url('/noise.svg')] md:mt-8 lg:mx-12">
<div className="h-full w-full rounded-md bg-[url('/grid.png')] bg-[length:60%_100%] bg-[100%_0rem] bg-no-repeat">
<div className="h-full gap-4 rounded-md bg-[url('/orgs_banner.png')] bg-[length:50%] bg-[120%_4.4rem] bg-no-repeat">
<Button
variant="icon"
StartIcon={X}
color="minimal"
className="hover:text-muted absolute top-0 right-0 text-white hover:bg-transparent"
/>
<div className="flex flex-col gap-2 px-8 pt-8 pb-8">
<h1 className="text-2xl font-bold">{t("organisation_banner_title")}</h1>
<p className="max-w-2xl">{t("organisation_banner_description")}</p>
</div>
<div className="flex flex-row gap-2 px-8">
<Button variant="button" color="secondary">
{t("setup_organisation")}
</Button>
<Button
variant="button"
color="minimal"
className="text-inverted hover:text-muted hover:bg-transparent">
{t("learn_more")}
</Button>
</div>
</div>
</div>
</div>
);
};
const CTA = () => {
const { t } = useLocale();
@ -774,6 +810,8 @@ const EventTypesPage = () => {
}
}, []);
const flags = useFlagMap();
return (
<div>
<HeadSeo
@ -784,6 +822,7 @@ const EventTypesPage = () => {
withoutSeo
heading={t("event_types_page_title")}
hideHeadingOnMobile
TopNavContainer={flags.organizations && <SetupOrganizationBanner />}
subtitle={t("event_types_page_subtitle")}
CTA={<CTA />}>
<WithQuery

View File

@ -0,0 +1,32 @@
import Head from "next/head";
import { CreateANewOrganizationForm } from "@calcom/features/ee/organizations/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import PageWrapper from "@components/PageWrapper";
import WizardLayout from "@components/layouts/WizardLayout";
const CreateNewTeamPage = () => {
const { t } = useLocale();
return (
<>
<Head>
<title>{t("create_new_team")}</title>
<meta name="description" content={t("create_new_team_description")} />
</Head>
<CreateANewOrganizationForm />
</>
);
};
const LayoutWrapper = (page: React.ReactElement) => {
return (
<WizardLayout currentStep={1} maxSteps={2}>
{page}
</WizardLayout>
);
};
CreateNewTeamPage.getLayout = LayoutWrapper;
CreateNewTeamPage.PageWrapper = PageWrapper;
export default CreateNewTeamPage;

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
apps/web/public/grid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -1836,5 +1836,8 @@
"connect_google_workspace":"Connect Google Workspace",
"google_workspace_admin_tooltip":"You must be a Workspace Admin to use this feature",
"first_event_type_webhook_description": "Create your first webhook for this event type",
"create_for": "Create for"
"create_for": "Create for",
"setup_organisation": "Setup an Organization",
"organisation_banner_description": "Create an environments where your teams can create shared apps, workflows and event types with round-robin and collective scheduling.",
"organisation_banner_title": "Manage organizations with multiple teams"
}

View File

@ -0,0 +1,163 @@
import { useRouter } from "next/router";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import slugify from "@calcom/lib/slugify";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react";
import { Avatar, Button, Form, ImageUploader, TextField, Alert } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon";
import type { NewOrganizationFormValues } from "../lib/types";
const querySchema = z.object({
returnTo: z.string(),
});
export const CreateANewOrganizationForm = () => {
const { t } = useLocale();
const router = useRouter();
const telemetry = useTelemetry();
const returnToParsed = querySchema.safeParse(router.query);
const [serverErrorMessage, setServerErrorMessage] = useState<string | null>(null);
const returnToParam =
(returnToParsed.success ? getSafeRedirectUrl(returnToParsed.data.returnTo) : "/settings/organizations") ||
"/organizations/teams";
const newTeamFormMethods = useForm<NewOrganizationFormValues>();
const createTeamMutation = trpc.viewer.teams.create.useMutation({
onSuccess: (data) => {
telemetry.event(telemetryEventTypes.team_created);
router.push(`/settings/organizations/${data.id}/onboard-members`);
},
onError: (err) => {
if (err.message === "team_url_taken") {
newTeamFormMethods.setError("slug", { type: "custom", message: t("team_url_taken") });
} else {
setServerErrorMessage(err.message);
}
},
});
return (
<>
<Form
form={newTeamFormMethods}
handleSubmit={(v) => {
if (!createTeamMutation.isLoading) {
setServerErrorMessage(null);
createTeamMutation.mutate(v);
}
}}>
<div className="mb-8">
{serverErrorMessage && (
<div className="mb-4">
<Alert severity="error" message={serverErrorMessage} />
</div>
)}
<Controller
name="name"
control={newTeamFormMethods.control}
defaultValue=""
rules={{
required: t("must_enter_team_name"),
}}
render={({ field: { value } }) => (
<>
<TextField
className="mt-2"
placeholder="Acme Inc."
name="name"
label={t("team_name")}
defaultValue={value}
onChange={(e) => {
newTeamFormMethods.setValue("name", e?.target.value);
if (newTeamFormMethods.formState.touchedFields["slug"] === undefined) {
newTeamFormMethods.setValue("slug", slugify(e?.target.value));
}
}}
autoComplete="off"
/>
</>
)}
/>
</div>
<div className="mb-8">
<Controller
name="slug"
control={newTeamFormMethods.control}
rules={{ required: t("team_url_required") }}
render={({ field: { value } }) => (
<TextField
className="mt-2"
name="slug"
placeholder="acme"
label={t("team_url")}
addOnLeading={`${process.env.NEXT_PUBLIC_WEBSITE_URL?.replace("https://", "")?.replace(
"http://",
""
)}/team/`}
defaultValue={value}
onChange={(e) => {
newTeamFormMethods.setValue("slug", slugify(e?.target.value), {
shouldTouch: true,
});
newTeamFormMethods.clearErrors("slug");
}}
/>
)}
/>
</div>
<div className="mb-8">
<Controller
control={newTeamFormMethods.control}
name="logo"
render={({ field: { value } }) => (
<div className="flex items-center">
<Avatar alt="" imageSrc={value || null} gravatarFallbackMd5="newTeam" size="lg" />
<div className="ms-4">
<ImageUploader
target="avatar"
id="avatar-upload"
buttonMsg={t("update")}
handleAvatarChange={(newAvatar: string) => {
newTeamFormMethods.setValue("logo", newAvatar);
createTeamMutation.reset();
}}
imageSrc={value}
/>
</div>
</div>
)}
/>
</div>
<div className="flex space-x-2 rtl:space-x-reverse">
<Button
disabled={createTeamMutation.isLoading}
color="secondary"
href={returnToParam}
className="w-full justify-center">
{t("cancel")}
</Button>
<Button
disabled={newTeamFormMethods.formState.isSubmitting || createTeamMutation.isLoading}
color="primary"
EndIcon={ArrowRight}
type="submit"
className="w-full justify-center">
{t("continue")}
</Button>
</div>
</Form>
</>
);
};

View File

@ -0,0 +1 @@
export { CreateANewOrganizationForm } from "./CreateANewOrganizationForm";

View File

@ -0,0 +1,18 @@
import type { MembershipRole } from "@calcom/prisma/enums";
export interface NewOrganizationFormValues {
name: string;
slug: string;
temporarySlug: string;
logo: string;
}
export interface PendingMember {
name: string | null;
email: string;
id?: number;
username: string | null;
role: MembershipRole;
avatar: string | null;
sendInviteEmail?: boolean;
}