Desktop first banner, mobile pending
This commit is contained in:
parent
582079d32c
commit
68e03945d0
|
@ -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
|
||||
|
|
|
@ -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 |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export { CreateANewOrganizationForm } from "./CreateANewOrganizationForm";
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user