Merge branch 'main' of github.com:calcom/cal.com into refactor-event-types-type-id-10419-cal-2264-cal-2296
This commit is contained in:
commit
4df1064f4c
|
@ -70,6 +70,7 @@ const schemaEventTypeCreateParams = z
|
||||||
recurringEvent: recurringEventInputSchema.optional(),
|
recurringEvent: recurringEventInputSchema.optional(),
|
||||||
seatsPerTimeSlot: z.number().optional(),
|
seatsPerTimeSlot: z.number().optional(),
|
||||||
seatsShowAttendees: z.boolean().optional(),
|
seatsShowAttendees: z.boolean().optional(),
|
||||||
|
seatsShowAvailabilityCount: z.boolean().optional(),
|
||||||
bookingFields: eventTypeBookingFields.optional(),
|
bookingFields: eventTypeBookingFields.optional(),
|
||||||
scheduleId: z.number().optional(),
|
scheduleId: z.number().optional(),
|
||||||
})
|
})
|
||||||
|
@ -89,6 +90,7 @@ const schemaEventTypeEditParams = z
|
||||||
length: z.number().int().optional(),
|
length: z.number().int().optional(),
|
||||||
seatsPerTimeSlot: z.number().optional(),
|
seatsPerTimeSlot: z.number().optional(),
|
||||||
seatsShowAttendees: z.boolean().optional(),
|
seatsShowAttendees: z.boolean().optional(),
|
||||||
|
seatsShowAvailabilityCount: z.boolean().optional(),
|
||||||
bookingFields: eventTypeBookingFields.optional(),
|
bookingFields: eventTypeBookingFields.optional(),
|
||||||
scheduleId: z.number().optional(),
|
scheduleId: z.number().optional(),
|
||||||
})
|
})
|
||||||
|
@ -129,6 +131,7 @@ export const schemaEventTypeReadPublic = EventType.pick({
|
||||||
metadata: true,
|
metadata: true,
|
||||||
seatsPerTimeSlot: true,
|
seatsPerTimeSlot: true,
|
||||||
seatsShowAttendees: true,
|
seatsShowAttendees: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
bookingFields: true,
|
bookingFields: true,
|
||||||
bookingLimits: true,
|
bookingLimits: true,
|
||||||
durationLimits: true,
|
durationLimits: true,
|
||||||
|
|
|
@ -94,6 +94,9 @@ import { defaultResponder } from "@calcom/lib/server";
|
||||||
* seatsShowAttendees:
|
* seatsShowAttendees:
|
||||||
* type: boolean
|
* type: boolean
|
||||||
* description: 'Share Attendee information in seats'
|
* description: 'Share Attendee information in seats'
|
||||||
|
* seatsShowAvailabilityCount:
|
||||||
|
* type: boolean
|
||||||
|
* description: 'Show the number of available seats'
|
||||||
* smsReminderNumber:
|
* smsReminderNumber:
|
||||||
* type: number
|
* type: number
|
||||||
* description: 'SMS reminder number'
|
* description: 'SMS reminder number'
|
||||||
|
|
|
@ -146,6 +146,9 @@ import checkTeamEventEditPermission from "../_utils/checkTeamEventEditPermission
|
||||||
* seatsShowAttendees:
|
* seatsShowAttendees:
|
||||||
* type: boolean
|
* type: boolean
|
||||||
* description: 'Share Attendee information in seats'
|
* description: 'Share Attendee information in seats'
|
||||||
|
* seatsShowAvailabilityCount:
|
||||||
|
* type: boolean
|
||||||
|
* description: 'Show the number of available seats'
|
||||||
* locations:
|
* locations:
|
||||||
* type: array
|
* type: array
|
||||||
* description: A list of all available locations for the event type
|
* description: A list of all available locations for the event type
|
||||||
|
|
|
@ -377,6 +377,14 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||||
defaultChecked={!!eventType.seatsShowAttendees}
|
defaultChecked={!!eventType.seatsShowAttendees}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<CheckboxField
|
||||||
|
description={t("show_available_seats_count")}
|
||||||
|
disabled={seatsLocked.disabled}
|
||||||
|
onChange={(e) => formMethods.setValue("seatsShowAvailabilityCount", e.target.checked)}
|
||||||
|
defaultChecked={!!eventType.seatsShowAvailabilityCount}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -14,7 +14,6 @@ import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/
|
||||||
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
|
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
|
||||||
import { FeatureProvider } from "@calcom/features/flags/context/provider";
|
import { FeatureProvider } from "@calcom/features/flags/context/provider";
|
||||||
import { useFlags } from "@calcom/features/flags/hooks";
|
import { useFlags } from "@calcom/features/flags/hooks";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
|
||||||
import { MetaProvider } from "@calcom/ui";
|
import { MetaProvider } from "@calcom/ui";
|
||||||
|
|
||||||
import useIsBookingPage from "@lib/hooks/useIsBookingPage";
|
import useIsBookingPage from "@lib/hooks/useIsBookingPage";
|
||||||
|
@ -222,19 +221,7 @@ function FeatureFlagsProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
function useOrgBrandingValues() {
|
function useOrgBrandingValues() {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
|
return session?.data?.user.org;
|
||||||
const res = trpc.viewer.organizations.getBrand.useQuery(undefined, {
|
|
||||||
// Only fetch if we have a session to avoid flooding logs with errors
|
|
||||||
enabled: session.status === "authenticated",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.status === "loading") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.status === "error") return null;
|
|
||||||
|
|
||||||
return res.data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function OrgBrandProvider({ children }: { children: React.ReactNode }) {
|
function OrgBrandProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
|
|
@ -917,6 +917,7 @@ const getEventTypesFromDB = async (id: number) => {
|
||||||
metadata: true,
|
metadata: true,
|
||||||
seatsPerTimeSlot: true,
|
seatsPerTimeSlot: true,
|
||||||
seatsShowAttendees: true,
|
seatsShowAttendees: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
periodStartDate: true,
|
periodStartDate: true,
|
||||||
periodEndDate: true,
|
periodEndDate: true,
|
||||||
},
|
},
|
||||||
|
@ -940,6 +941,7 @@ const handleSeatsEventTypeOnBooking = async (
|
||||||
eventType: {
|
eventType: {
|
||||||
seatsPerTimeSlot?: number | null;
|
seatsPerTimeSlot?: number | null;
|
||||||
seatsShowAttendees: boolean | null;
|
seatsShowAttendees: boolean | null;
|
||||||
|
seatsShowAvailabilityCount: boolean | null;
|
||||||
[x: string | number | symbol]: unknown;
|
[x: string | number | symbol]: unknown;
|
||||||
},
|
},
|
||||||
bookingInfo: Partial<
|
bookingInfo: Partial<
|
||||||
|
|
|
@ -113,6 +113,7 @@ export type FormValues = {
|
||||||
periodDates: { startDate: Date; endDate: Date };
|
periodDates: { startDate: Date; endDate: Date };
|
||||||
seatsPerTimeSlot: number | null;
|
seatsPerTimeSlot: number | null;
|
||||||
seatsShowAttendees: boolean | null;
|
seatsShowAttendees: boolean | null;
|
||||||
|
seatsShowAvailabilityCount: boolean | null;
|
||||||
seatsPerTimeSlotEnabled: boolean;
|
seatsPerTimeSlotEnabled: boolean;
|
||||||
minimumBookingNotice: number;
|
minimumBookingNotice: number;
|
||||||
minimumBookingNoticeInDurationType: number;
|
minimumBookingNoticeInDurationType: number;
|
||||||
|
@ -360,6 +361,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||||
afterBufferTime,
|
afterBufferTime,
|
||||||
seatsPerTimeSlot,
|
seatsPerTimeSlot,
|
||||||
seatsShowAttendees,
|
seatsShowAttendees,
|
||||||
|
seatsShowAvailabilityCount,
|
||||||
bookingLimits,
|
bookingLimits,
|
||||||
durationLimits,
|
durationLimits,
|
||||||
recurringEvent,
|
recurringEvent,
|
||||||
|
@ -426,6 +428,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||||
durationLimits,
|
durationLimits,
|
||||||
seatsPerTimeSlot,
|
seatsPerTimeSlot,
|
||||||
seatsShowAttendees,
|
seatsShowAttendees,
|
||||||
|
seatsShowAvailabilityCount,
|
||||||
metadata,
|
metadata,
|
||||||
customInputs,
|
customInputs,
|
||||||
children,
|
children,
|
||||||
|
@ -460,6 +463,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||||
afterBufferTime,
|
afterBufferTime,
|
||||||
seatsPerTimeSlot,
|
seatsPerTimeSlot,
|
||||||
seatsShowAttendees,
|
seatsShowAttendees,
|
||||||
|
seatsShowAvailabilityCount,
|
||||||
bookingLimits,
|
bookingLimits,
|
||||||
durationLimits,
|
durationLimits,
|
||||||
recurringEvent,
|
recurringEvent,
|
||||||
|
@ -516,6 +520,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||||
durationLimits,
|
durationLimits,
|
||||||
seatsPerTimeSlot,
|
seatsPerTimeSlot,
|
||||||
seatsShowAttendees,
|
seatsShowAttendees,
|
||||||
|
seatsShowAvailabilityCount,
|
||||||
metadata,
|
metadata,
|
||||||
customInputs,
|
customInputs,
|
||||||
});
|
});
|
||||||
|
|
|
@ -292,9 +292,9 @@
|
||||||
"add_another_calendar": "Add another calendar",
|
"add_another_calendar": "Add another calendar",
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
"email_sign_in_subject": "Your sign-in link for {{appName}}",
|
"email_sign_in_subject": "Your sign-in link for {{appName}}",
|
||||||
"emailed_you_and_attendees": "We emailed you and the other attendees a calendar invitation with all the details.",
|
"emailed_you_and_attendees": "We sent an email with a calendar invitation with the details to everyone.",
|
||||||
"emailed_you_and_attendees_recurring": "We emailed you and the other attendees a calendar invitation for the first of these recurring events.",
|
"emailed_you_and_attendees_recurring": "We sent an email with a calendar invitation with the details to everyone for the first of these recurring events.",
|
||||||
"emailed_you_and_any_other_attendees": "You and any other attendees have been emailed with this information.",
|
"emailed_you_and_any_other_attendees": "We sent an email to everyone with this information.",
|
||||||
"needs_to_be_confirmed_or_rejected": "Your booking still needs to be confirmed or rejected.",
|
"needs_to_be_confirmed_or_rejected": "Your booking still needs to be confirmed or rejected.",
|
||||||
"needs_to_be_confirmed_or_rejected_recurring": "Your recurring meeting still needs to be confirmed or rejected.",
|
"needs_to_be_confirmed_or_rejected_recurring": "Your recurring meeting still needs to be confirmed or rejected.",
|
||||||
"user_needs_to_confirm_or_reject_booking": "{{user}} still needs to confirm or reject the booking.",
|
"user_needs_to_confirm_or_reject_booking": "{{user}} still needs to confirm or reject the booking.",
|
||||||
|
@ -974,6 +974,8 @@
|
||||||
"offer_seats_description": "Offer seats for booking. This automatically disables guest & opt-in bookings.",
|
"offer_seats_description": "Offer seats for booking. This automatically disables guest & opt-in bookings.",
|
||||||
"seats_available_one": "Seat available",
|
"seats_available_one": "Seat available",
|
||||||
"seats_available_other": "Seats available",
|
"seats_available_other": "Seats available",
|
||||||
|
"seats_nearly_full": "Seats almost full",
|
||||||
|
"seats_half_full": "Seats filling fast",
|
||||||
"number_of_seats": "Number of seats per booking",
|
"number_of_seats": "Number of seats per booking",
|
||||||
"enter_number_of_seats": "Enter number of seats",
|
"enter_number_of_seats": "Enter number of seats",
|
||||||
"you_can_manage_your_schedules": "You can manage your schedules on the Availability page.",
|
"you_can_manage_your_schedules": "You can manage your schedules on the Availability page.",
|
||||||
|
@ -1100,7 +1102,7 @@
|
||||||
"reschedule_optional": "Reason for rescheduling (optional)",
|
"reschedule_optional": "Reason for rescheduling (optional)",
|
||||||
"reschedule_placeholder": "Let others know why you need to reschedule",
|
"reschedule_placeholder": "Let others know why you need to reschedule",
|
||||||
"event_cancelled": "This event is canceled",
|
"event_cancelled": "This event is canceled",
|
||||||
"emailed_information_about_cancelled_event": "We emailed you and the other attendees to let them know.",
|
"emailed_information_about_cancelled_event": "We sent an email to everyone to let them know.",
|
||||||
"this_input_will_shown_booking_this_event": "This input will be shown when booking this event",
|
"this_input_will_shown_booking_this_event": "This input will be shown when booking this event",
|
||||||
"meeting_url_in_confirmation_email": "Meeting url is in the confirmation email",
|
"meeting_url_in_confirmation_email": "Meeting url is in the confirmation email",
|
||||||
"url_start_with_https": "URL needs to start with http:// or https://",
|
"url_start_with_https": "URL needs to start with http:// or https://",
|
||||||
|
@ -1455,6 +1457,7 @@
|
||||||
"add_limit": "Add Limit",
|
"add_limit": "Add Limit",
|
||||||
"team_name_required": "Team name required",
|
"team_name_required": "Team name required",
|
||||||
"show_attendees": "Share attendee information between guests",
|
"show_attendees": "Share attendee information between guests",
|
||||||
|
"show_available_seats_count": "Show the number of available seats",
|
||||||
"how_booking_questions_as_variables": "How to use booking questions as variables?",
|
"how_booking_questions_as_variables": "How to use booking questions as variables?",
|
||||||
"format": "Format",
|
"format": "Format",
|
||||||
"uppercase_for_letters": "Use uppercase for all letters",
|
"uppercase_for_letters": "Use uppercase for all letters",
|
||||||
|
|
|
@ -1888,7 +1888,7 @@
|
||||||
"organization_name": "Nombre de la organización",
|
"organization_name": "Nombre de la organización",
|
||||||
"organization_url": "URL de la organización",
|
"organization_url": "URL de la organización",
|
||||||
"organization_verify_header": "Verifique el correo electrónico de su organización",
|
"organization_verify_header": "Verifique el correo electrónico de su organización",
|
||||||
"organization_verify_email_body": "Utilice el código a continuación para verificar su dirección de correo electrónico para seguir configurando su organización.",
|
"organization_verify_email_body": "Utilice el siguiente código para verificar su dirección de correo electrónico y continuar con la configuración de su organización.",
|
||||||
"additional_url_parameters": "Parámetros adicionales de URL",
|
"additional_url_parameters": "Parámetros adicionales de URL",
|
||||||
"about_your_organization": "Acerca de su organización",
|
"about_your_organization": "Acerca de su organización",
|
||||||
"about_your_organization_description": "Las organizaciones son entornos compartidos donde puede crear varios equipos con miembros, tipos de eventos, aplicaciones, flujos de trabajo compartidos y más.",
|
"about_your_organization_description": "Las organizaciones son entornos compartidos donde puede crear varios equipos con miembros, tipos de eventos, aplicaciones, flujos de trabajo compartidos y más.",
|
||||||
|
|
|
@ -974,6 +974,8 @@
|
||||||
"offer_seats_description": "Proposez des places de réservation. Cela désactive automatiquement les réservations d'invités et d'opt-in.",
|
"offer_seats_description": "Proposez des places de réservation. Cela désactive automatiquement les réservations d'invités et d'opt-in.",
|
||||||
"seats_available_one": "Place disponible",
|
"seats_available_one": "Place disponible",
|
||||||
"seats_available_other": "Places disponibles",
|
"seats_available_other": "Places disponibles",
|
||||||
|
"seats_nearly_full": "Places presque toutes occupées",
|
||||||
|
"seats_half_full": "Les places partent vite",
|
||||||
"number_of_seats": "Nombre de places par réservation",
|
"number_of_seats": "Nombre de places par réservation",
|
||||||
"enter_number_of_seats": "Saisir le nombre de sièges",
|
"enter_number_of_seats": "Saisir le nombre de sièges",
|
||||||
"you_can_manage_your_schedules": "Vous pouvez gérer vos disponibilités sur la page Disponibilité.",
|
"you_can_manage_your_schedules": "Vous pouvez gérer vos disponibilités sur la page Disponibilité.",
|
||||||
|
@ -1455,6 +1457,7 @@
|
||||||
"add_limit": "Ajouter une limite",
|
"add_limit": "Ajouter une limite",
|
||||||
"team_name_required": "Nom d'équipe requis",
|
"team_name_required": "Nom d'équipe requis",
|
||||||
"show_attendees": "Partagez les informations des participants entre les invités",
|
"show_attendees": "Partagez les informations des participants entre les invités",
|
||||||
|
"show_available_seats_count": "Afficher le nombre de places disponibles",
|
||||||
"how_booking_questions_as_variables": "Comment utiliser les questions de réservation comme variables ?",
|
"how_booking_questions_as_variables": "Comment utiliser les questions de réservation comme variables ?",
|
||||||
"format": "Format",
|
"format": "Format",
|
||||||
"uppercase_for_letters": "Utilisez des majuscules pour toutes les lettres.",
|
"uppercase_for_letters": "Utilisez des majuscules pour toutes les lettres.",
|
||||||
|
|
|
@ -210,6 +210,8 @@
|
||||||
"done": "Učinjeno",
|
"done": "Učinjeno",
|
||||||
"all_done": "Završeno!",
|
"all_done": "Završeno!",
|
||||||
"all": "Sve",
|
"all": "Sve",
|
||||||
|
"available_apps": "Dostupne Aplikacije",
|
||||||
|
"available_apps_lower_case": "Dostupne aplikacije",
|
||||||
"check_email_reset_password": "Provjerite svoju e-poštu. Poslali smo vam link za resetiranje lozinke.",
|
"check_email_reset_password": "Provjerite svoju e-poštu. Poslali smo vam link za resetiranje lozinke.",
|
||||||
"finish": "Završi",
|
"finish": "Završi",
|
||||||
"few_sentences_about_yourself": "Nekoliko rečenica o sebi. Ovo će se pojaviti na vašoj osobnoj stranici.",
|
"few_sentences_about_yourself": "Nekoliko rečenica o sebi. Ovo će se pojaviti na vašoj osobnoj stranici.",
|
||||||
|
@ -331,5 +333,6 @@
|
||||||
"dark": "Tamna",
|
"dark": "Tamna",
|
||||||
"automatically_adjust_theme": "Automatski prilagodite temu na temelju preferencija pozvanih osoba",
|
"automatically_adjust_theme": "Automatski prilagodite temu na temelju preferencija pozvanih osoba",
|
||||||
"user_dynamic_booking_disabled": "Neki od korisnika u grupi trenutno su onemogućili dinamičke grupne rezervacije",
|
"user_dynamic_booking_disabled": "Neki od korisnika u grupi trenutno su onemogućili dinamičke grupne rezervacije",
|
||||||
|
"full_name": "Puno ime",
|
||||||
"insights_all_org_filter": "Sve"
|
"insights_all_org_filter": "Sve"
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,7 +308,7 @@
|
||||||
"layout": "Raspored",
|
"layout": "Raspored",
|
||||||
"bookerlayout_default_title": "Podrazumevani prikaz",
|
"bookerlayout_default_title": "Podrazumevani prikaz",
|
||||||
"bookerlayout_description": "Možete da izaberete više njih, a vaši učesnici mogu da menjaju prikaze.",
|
"bookerlayout_description": "Možete da izaberete više njih, a vaši učesnici mogu da menjaju prikaze.",
|
||||||
"bookerlayout_user_settings_title": "Raspored zakazivanja",
|
"bookerlayout_user_settings_title": "Režim prikaza rezervacija",
|
||||||
"bookerlayout_user_settings_description": "Možete da izaberete više njih, a učesnici mogu da menjaju prikaz. Ovo se može zameniti za svaki događaj.",
|
"bookerlayout_user_settings_description": "Možete da izaberete više njih, a učesnici mogu da menjaju prikaz. Ovo se može zameniti za svaki događaj.",
|
||||||
"bookerlayout_month_view": "Mesec",
|
"bookerlayout_month_view": "Mesec",
|
||||||
"bookerlayout_week_view": "Nedeljno",
|
"bookerlayout_week_view": "Nedeljno",
|
||||||
|
@ -404,7 +404,7 @@
|
||||||
"recording_ready": "Link za preuzimanje snimka je spreman",
|
"recording_ready": "Link za preuzimanje snimka je spreman",
|
||||||
"booking_created": "Rezervacija Napravljena",
|
"booking_created": "Rezervacija Napravljena",
|
||||||
"booking_rejected": "Rezevacija je odbijena",
|
"booking_rejected": "Rezevacija je odbijena",
|
||||||
"booking_requested": "Rezervacija je zahtevana",
|
"booking_requested": "Zahtev za rezervaciju je poslat",
|
||||||
"meeting_ended": "Sastanak se završio",
|
"meeting_ended": "Sastanak se završio",
|
||||||
"form_submitted": "Formular poslat",
|
"form_submitted": "Formular poslat",
|
||||||
"event_triggers": "Okidači Dogadjaja",
|
"event_triggers": "Okidači Dogadjaja",
|
||||||
|
@ -552,11 +552,11 @@
|
||||||
"team_description": "Par rečenica o vašem timu. Ovo će se pojaviti na stranici URL adrese vašeg tima.",
|
"team_description": "Par rečenica o vašem timu. Ovo će se pojaviti na stranici URL adrese vašeg tima.",
|
||||||
"org_description": "Nekoliko rečenica o vašoj organizaciji. Ovo će se pojaviti na url stranici vaše organizacije.",
|
"org_description": "Nekoliko rečenica o vašoj organizaciji. Ovo će se pojaviti na url stranici vaše organizacije.",
|
||||||
"members": "Članovi",
|
"members": "Članovi",
|
||||||
"organization_members": "Članovi organizacije",
|
"organization_members": "Korisnici uključeni u tarifni plan Organization",
|
||||||
"member": "Član",
|
"member": "Član",
|
||||||
"number_member_one": "{{count}} član",
|
"number_member_one": "{{count}} član",
|
||||||
"number_member_other": "{{count}} članova",
|
"number_member_other": "{{count}} članova",
|
||||||
"number_selected": "{{count}} izabrano",
|
"number_selected": "Izabrano: {{count}}",
|
||||||
"owner": "Vlasnik",
|
"owner": "Vlasnik",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator_user": "Administrator",
|
"administrator_user": "Administrator",
|
||||||
|
@ -1687,7 +1687,7 @@
|
||||||
"attendee_no_longer_attending": "Polaznik više ne pohađa vaš događaj",
|
"attendee_no_longer_attending": "Polaznik više ne pohađa vaš događaj",
|
||||||
"attendee_no_longer_attending_subtitle": "Korisnik {{name}} je otkazao. To znači da se otvorilo mesto za ovaj vremenski period",
|
"attendee_no_longer_attending_subtitle": "Korisnik {{name}} je otkazao. To znači da se otvorilo mesto za ovaj vremenski period",
|
||||||
"create_event_on": "Kreirajte događaj na",
|
"create_event_on": "Kreirajte događaj na",
|
||||||
"create_routing_form_on": "Kreiraj obrazac za usmeravanje",
|
"create_routing_form_on": "Kreiranje obrasca za usmeravanje za",
|
||||||
"default_app_link_title": "Podesite podrazumevani link aplikacije",
|
"default_app_link_title": "Podesite podrazumevani link aplikacije",
|
||||||
"default_app_link_description": "Podešavanje podrazumevanog linka aplikacije omogućava novokreiranim tipovima događaja da koriste link aplikacije koji ste postavili.",
|
"default_app_link_description": "Podešavanje podrazumevanog linka aplikacije omogućava novokreiranim tipovima događaja da koriste link aplikacije koji ste postavili.",
|
||||||
"organizer_default_conferencing_app": "Podrazumevana aplikacija organizatora",
|
"organizer_default_conferencing_app": "Podrazumevana aplikacija organizatora",
|
||||||
|
@ -1876,23 +1876,23 @@
|
||||||
"connect_google_workspace": "Poveži Google Workspace",
|
"connect_google_workspace": "Poveži Google Workspace",
|
||||||
"google_workspace_admin_tooltip": "Morate da budete Workspace administrator da biste koristili ovu opciju",
|
"google_workspace_admin_tooltip": "Morate da budete Workspace administrator da biste koristili ovu opciju",
|
||||||
"first_event_type_webhook_description": "Napravite svoj prvi webhook za ovaj tip događaja",
|
"first_event_type_webhook_description": "Napravite svoj prvi webhook za ovaj tip događaja",
|
||||||
"install_app_on": "Instaliraj aplikaciju",
|
"install_app_on": "Instaliraj aplikaciju za",
|
||||||
"create_for": "Napravi za",
|
"create_for": "Napravi za",
|
||||||
"organization_banner_description": "Kreirajte okruženje gde vaši timovi mogu da postave deljene aplikacije, radne tokove i vrste događaja sa kružnom dodelom i zajedničko zakazivanje.",
|
"organization_banner_description": "Kreirajte okruženja gde vaši timovi mogu da postave deljene aplikacije, radne tokove i vrste događaja sa kružnom dodelom i zajedničko zakazivanje.",
|
||||||
"organization_banner_title": "Upravljajte organizacijama sa više timova",
|
"organization_banner_title": "Upravljajte organizacijama sa više timova",
|
||||||
"set_up_your_organization": "Postavite svoju organizaciju",
|
"set_up_your_organization": "Konfigurisanje profila organizacije",
|
||||||
"organizations_description": "Organizacije su deljena okruženja gde timovi mogu da kreiraju deljene vrste događaja, aplikacije, radne tokove i još mnogo toga.",
|
"organizations_description": "Organizacije su deljena okruženja gde timovi mogu da kreiraju deljene vrste događaja, aplikacije, radne tokove i još mnogo toga.",
|
||||||
"must_enter_organization_name": "Morate da unesete naziv organizacije",
|
"must_enter_organization_name": "Morate da unesete naziv organizacije",
|
||||||
"must_enter_organization_admin_email": "Morate da unesete adresu e-pošte vaše organizacije",
|
"must_enter_organization_admin_email": "Morate da unesete adresu e-pošte vaše organizacije",
|
||||||
"admin_email": "Imejl adresa vaše organizacije",
|
"admin_email": "Vaša adresa e-pošte u organizaciji",
|
||||||
"admin_username": "Korisničko ime administratora",
|
"admin_username": "Korisničko ime administratora",
|
||||||
"organization_name": "Naziv organizacije",
|
"organization_name": "Naziv organizacije",
|
||||||
"organization_url": "URL organizacije",
|
"organization_url": "URL organizacije",
|
||||||
"organization_verify_header": "Potvrdite imejl vaše organizacije",
|
"organization_verify_header": "Potvrdite svoju adresu e-pošte u organizaciji",
|
||||||
"organization_verify_email_body": "Koristite kôd u nastavku da potvrdite svoju adresu e-pošte kako biste nastavili sa podešavanjem svoje organizacije.",
|
"organization_verify_email_body": "Koristite kôd u nastavku da potvrdite svoju adresu e-pošte kako biste nastavili sa podešavanjem svoje organizacije.",
|
||||||
"additional_url_parameters": "Dodatni URL parametri",
|
"additional_url_parameters": "Dodatni URL parametri",
|
||||||
"about_your_organization": "O vašoj organizaciji",
|
"about_your_organization": "O vašoj organizaciji",
|
||||||
"about_your_organization_description": "Organizacije su deljena okruženja gde možete da kreirate više timova sa deljenim članovima, vrste događaja, aplikacije, radne tokove i drugo.",
|
"about_your_organization_description": "Organizacije su deljena okruženja gde možete da kreirate više timova sa deljenim članovima, vrstama događaja, aplikacijama, radnim tokovima i mnogim drugim stvarima.",
|
||||||
"create_your_teams": "Kreirajte svoje timove",
|
"create_your_teams": "Kreirajte svoje timove",
|
||||||
"create_your_teams_description": "Počnite planiranje zajedno dodavanjem članova tima u svoju organizaciju",
|
"create_your_teams_description": "Počnite planiranje zajedno dodavanjem članova tima u svoju organizaciju",
|
||||||
"invite_organization_admins": "Pozovite administratore vaše organizacije",
|
"invite_organization_admins": "Pozovite administratore vaše organizacije",
|
||||||
|
@ -1917,7 +1917,7 @@
|
||||||
"org_no_teams_yet_description": "Ako ste administrator, obavezno kreirajte timove koji će ovde biti prikazani.",
|
"org_no_teams_yet_description": "Ako ste administrator, obavezno kreirajte timove koji će ovde biti prikazani.",
|
||||||
"set_up": "Podesi",
|
"set_up": "Podesi",
|
||||||
"set_up_your_profile": "Podesite svoj profil",
|
"set_up_your_profile": "Podesite svoj profil",
|
||||||
"set_up_your_profile_description": "Neka ljudi u organizaciji {{orgName}} znaju ko ste i kako da se povežu sa vama putem javnog linka.",
|
"set_up_your_profile_description": "Unesite informacije o svojoj ulozi u organizaciji {{orgName}}. Te informacije će videti osobe koje kliknu na vaš javni link.",
|
||||||
"my_profile": "Moj profil",
|
"my_profile": "Moj profil",
|
||||||
"my_settings": "Moja podešavanja",
|
"my_settings": "Moja podešavanja",
|
||||||
"crm": "CRM",
|
"crm": "CRM",
|
||||||
|
@ -1925,7 +1925,7 @@
|
||||||
"sender_id_info": "Ime ili broj koji će biti prikazani kao pošiljalac SMS-a (neke zemlje ne dozvoljavaju alfanumeričke ID-ove pošiljaoca)",
|
"sender_id_info": "Ime ili broj koji će biti prikazani kao pošiljalac SMS-a (neke zemlje ne dozvoljavaju alfanumeričke ID-ove pošiljaoca)",
|
||||||
"org_admins_can_create_new_teams": "Samo administrator vaše ogranizacije može da kreira nove timove",
|
"org_admins_can_create_new_teams": "Samo administrator vaše ogranizacije može da kreira nove timove",
|
||||||
"google_new_spam_policy": "Google-ova nova politika neželjene pošte može da spreči da primate bilo koje imejlove ili obaveštenja kalendara u vezi sa ovom rezervacijom.",
|
"google_new_spam_policy": "Google-ova nova politika neželjene pošte može da spreči da primate bilo koje imejlove ili obaveštenja kalendara u vezi sa ovom rezervacijom.",
|
||||||
"resolve": "Reši",
|
"resolve": "Kako rešiti taj problem",
|
||||||
"no_organization_slug": "Desila se greška kod kreiranja timova za ovu organizaciju. Nedostaje URL slug.",
|
"no_organization_slug": "Desila se greška kod kreiranja timova za ovu organizaciju. Nedostaje URL slug.",
|
||||||
"org_name": "Naziv organizacije",
|
"org_name": "Naziv organizacije",
|
||||||
"org_url": "URL organizacije",
|
"org_url": "URL organizacije",
|
||||||
|
@ -1933,7 +1933,7 @@
|
||||||
"404_the_org": "Organizacija",
|
"404_the_org": "Organizacija",
|
||||||
"404_the_team": "Tim",
|
"404_the_team": "Tim",
|
||||||
"404_claim_entity_org": "Zatražite poddomen za svoju organizaciju",
|
"404_claim_entity_org": "Zatražite poddomen za svoju organizaciju",
|
||||||
"404_claim_entity_team": "Zahtevajte ovaj tim i počnite da uređujete kolektivni raspored",
|
"404_claim_entity_team": "Postanite deo ovog tima i počnite da uređujete kolektivni raspored",
|
||||||
"insights_all_org_filter": "Sve aplikacije",
|
"insights_all_org_filter": "Sve aplikacije",
|
||||||
"insights_team_filter": "Tim: {{teamName}}",
|
"insights_team_filter": "Tim: {{teamName}}",
|
||||||
"insights_user_filter": "Korisnik: {{userName}}",
|
"insights_user_filter": "Korisnik: {{userName}}",
|
||||||
|
|
|
@ -262,6 +262,7 @@ export default class EventManager {
|
||||||
select: {
|
select: {
|
||||||
seatsPerTimeSlot: true,
|
seatsPerTimeSlot: true,
|
||||||
seatsShowAttendees: true,
|
seatsShowAttendees: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,25 +22,33 @@ export const getAggregatedAvailability = (
|
||||||
return mergeOverlappingDateRanges(availability);
|
return mergeOverlappingDateRanges(availability);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isSameDay(date1: Date, date2: Date) {
|
||||||
|
return (
|
||||||
|
date1.getUTCFullYear() === date2.getUTCFullYear() &&
|
||||||
|
date1.getUTCMonth() === date2.getUTCMonth() &&
|
||||||
|
date1.getUTCDate() === date2.getUTCDate()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function mergeOverlappingDateRanges(dateRanges: DateRange[]) {
|
function mergeOverlappingDateRanges(dateRanges: DateRange[]) {
|
||||||
const sortedDateRanges = dateRanges.sort((a, b) => a.start.diff(b.start)); //is it already sorted before?
|
dateRanges.sort((a, b) => a.start.valueOf() - b.start.valueOf());
|
||||||
|
|
||||||
const mergedDateRanges: DateRange[] = [];
|
const mergedDateRanges: DateRange[] = [];
|
||||||
|
|
||||||
let currentRange = sortedDateRanges[0];
|
let currentRange = dateRanges[0];
|
||||||
if (!currentRange) {
|
if (!currentRange) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 1; i < sortedDateRanges.length; i++) {
|
for (let i = 1; i < dateRanges.length; i++) {
|
||||||
const nextRange = sortedDateRanges[i];
|
const nextRange = dateRanges[i];
|
||||||
if (
|
if (
|
||||||
currentRange.start.utc().format("DD MM YY") === nextRange.start.utc().format("DD MM YY") &&
|
isSameDay(currentRange.start.toDate(), nextRange.start.toDate()) &&
|
||||||
currentRange.end.isAfter(nextRange.start)
|
currentRange.end.valueOf() > nextRange.start.valueOf()
|
||||||
) {
|
) {
|
||||||
currentRange = {
|
currentRange = {
|
||||||
start: currentRange.start,
|
start: currentRange.start,
|
||||||
end: currentRange.end.isAfter(nextRange.end) ? currentRange.end : nextRange.end,
|
end: currentRange.end.valueOf() > nextRange.end.valueOf() ? currentRange.end : nextRange.end,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
mergedDateRanges.push(currentRange);
|
mergedDateRanges.push(currentRange);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type { EventBusyDetails } from "@calcom/types/Calendar";
|
||||||
export async function getBusyTimes(params: {
|
export async function getBusyTimes(params: {
|
||||||
credentials: Credential[];
|
credentials: Credential[];
|
||||||
userId: number;
|
userId: number;
|
||||||
|
userEmail: string;
|
||||||
username: string;
|
username: string;
|
||||||
eventTypeId?: number;
|
eventTypeId?: number;
|
||||||
startTime: string;
|
startTime: string;
|
||||||
|
@ -27,6 +28,7 @@ export async function getBusyTimes(params: {
|
||||||
const {
|
const {
|
||||||
credentials,
|
credentials,
|
||||||
userId,
|
userId,
|
||||||
|
userEmail,
|
||||||
username,
|
username,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
startTime,
|
startTime,
|
||||||
|
@ -45,15 +47,6 @@ export async function getBusyTimes(params: {
|
||||||
status: BookingStatus.ACCEPTED,
|
status: BookingStatus.ACCEPTED,
|
||||||
})}`
|
})}`
|
||||||
);
|
);
|
||||||
// get user email for attendee checking.
|
|
||||||
const user = await prisma.user.findUniqueOrThrow({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A user is considered busy within a given time period if there
|
* A user is considered busy within a given time period if there
|
||||||
|
@ -97,7 +90,7 @@ export async function getBusyTimes(params: {
|
||||||
...sharedQuery,
|
...sharedQuery,
|
||||||
attendees: {
|
attendees: {
|
||||||
some: {
|
some: {
|
||||||
email: user.email,
|
email: userEmail,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -180,6 +180,7 @@ export const getUserAvailability = async function getUsersWorkingHoursLifeTheUni
|
||||||
endTime: getBusyTimesEnd,
|
endTime: getBusyTimesEnd,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
userEmail: user.email,
|
||||||
username: `${user.username}`,
|
username: `${user.username}`,
|
||||||
beforeEventBuffer,
|
beforeEventBuffer,
|
||||||
afterEventBuffer,
|
afterEventBuffer,
|
||||||
|
|
|
@ -72,7 +72,7 @@ export async function getServerSession(options: {
|
||||||
image: `${CAL_URL}/${user.username}/avatar.png`,
|
image: `${CAL_URL}/${user.username}/avatar.png`,
|
||||||
impersonatedByUID: token.impersonatedByUID ?? undefined,
|
impersonatedByUID: token.impersonatedByUID ?? undefined,
|
||||||
belongsToActiveTeam: token.belongsToActiveTeam,
|
belongsToActiveTeam: token.belongsToActiveTeam,
|
||||||
organizationId: token.organizationId,
|
org: token.org,
|
||||||
locale: user.locale ?? undefined,
|
locale: user.locale ?? undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ import GoogleProvider from "next-auth/providers/google";
|
||||||
|
|
||||||
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
|
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
|
||||||
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
|
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
|
||||||
|
import { getOrgFullDomain, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
@ -402,7 +403,14 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||||
username: true,
|
username: true,
|
||||||
name: true,
|
name: true,
|
||||||
email: true,
|
email: true,
|
||||||
organizationId: true,
|
organization: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
slug: true,
|
||||||
|
metadata: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
role: true,
|
role: true,
|
||||||
locale: true,
|
locale: true,
|
||||||
teams: {
|
teams: {
|
||||||
|
@ -419,12 +427,23 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||||
|
|
||||||
// Check if the existingUser has any active teams
|
// Check if the existingUser has any active teams
|
||||||
const belongsToActiveTeam = checkIfUserBelongsToActiveTeam(existingUser);
|
const belongsToActiveTeam = checkIfUserBelongsToActiveTeam(existingUser);
|
||||||
const { teams: _teams, ...existingUserWithoutTeamsField } = existingUser;
|
const { teams: _teams, organization, ...existingUserWithoutTeamsField } = existingUser;
|
||||||
|
|
||||||
|
const parsedOrgMetadata = teamMetadataSchema.parse(organization?.metadata ?? {});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...existingUserWithoutTeamsField,
|
...existingUserWithoutTeamsField,
|
||||||
...token,
|
...token,
|
||||||
belongsToActiveTeam,
|
belongsToActiveTeam,
|
||||||
|
org: organization
|
||||||
|
? {
|
||||||
|
id: organization.id,
|
||||||
|
name: organization.name,
|
||||||
|
slug: organization.slug ?? parsedOrgMetadata?.requestedSlug ?? "",
|
||||||
|
fullDomain: getOrgFullDomain(organization.slug ?? parsedOrgMetadata?.requestedSlug ?? ""),
|
||||||
|
domainSuffix: subdomainSuffix(),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
@ -448,7 +467,7 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||||
role: user.role,
|
role: user.role,
|
||||||
impersonatedByUID: user?.impersonatedByUID,
|
impersonatedByUID: user?.impersonatedByUID,
|
||||||
belongsToActiveTeam: user?.belongsToActiveTeam,
|
belongsToActiveTeam: user?.belongsToActiveTeam,
|
||||||
organizationId: user?.organizationId,
|
org: user?.org,
|
||||||
locale: user?.locale,
|
locale: user?.locale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -487,7 +506,7 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||||
role: existingUser.role,
|
role: existingUser.role,
|
||||||
impersonatedByUID: token.impersonatedByUID as number,
|
impersonatedByUID: token.impersonatedByUID as number,
|
||||||
belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
|
belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
|
||||||
organizationId: token?.organizationId,
|
org: token?.org,
|
||||||
locale: existingUser.locale,
|
locale: existingUser.locale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -507,7 +526,7 @@ export const AUTH_OPTIONS: AuthOptions = {
|
||||||
role: token.role as UserPermissionRole,
|
role: token.role as UserPermissionRole,
|
||||||
impersonatedByUID: token.impersonatedByUID as number,
|
impersonatedByUID: token.impersonatedByUID as number,
|
||||||
belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
|
belongsToActiveTeam: token?.belongsToActiveTeam as boolean,
|
||||||
organizationId: token?.organizationId,
|
org: token?.org,
|
||||||
locale: token.locale,
|
locale: token.locale,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -327,6 +327,7 @@ const BookerComponent = ({
|
||||||
prefetchNextMonth={prefetchNextMonth}
|
prefetchNextMonth={prefetchNextMonth}
|
||||||
monthCount={monthCount}
|
monthCount={monthCount}
|
||||||
seatsPerTimeSlot={event.data?.seatsPerTimeSlot}
|
seatsPerTimeSlot={event.data?.seatsPerTimeSlot}
|
||||||
|
showAvailableSeatsCount={event.data?.seatsShowAvailabilityCount}
|
||||||
/>
|
/>
|
||||||
</BookerSection>
|
</BookerSection>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
|
@ -19,6 +19,7 @@ type AvailableTimeSlotsProps = {
|
||||||
prefetchNextMonth: boolean;
|
prefetchNextMonth: boolean;
|
||||||
monthCount: number | undefined;
|
monthCount: number | undefined;
|
||||||
seatsPerTimeSlot?: number | null;
|
seatsPerTimeSlot?: number | null;
|
||||||
|
showAvailableSeatsCount?: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,6 +33,7 @@ export const AvailableTimeSlots = ({
|
||||||
extraDays,
|
extraDays,
|
||||||
limitHeight,
|
limitHeight,
|
||||||
seatsPerTimeSlot,
|
seatsPerTimeSlot,
|
||||||
|
showAvailableSeatsCount,
|
||||||
prefetchNextMonth,
|
prefetchNextMonth,
|
||||||
monthCount,
|
monthCount,
|
||||||
}: AvailableTimeSlotsProps) => {
|
}: AvailableTimeSlotsProps) => {
|
||||||
|
@ -60,6 +62,7 @@ export const AvailableTimeSlots = ({
|
||||||
seatsPerTimeSlot,
|
seatsPerTimeSlot,
|
||||||
attendees,
|
attendees,
|
||||||
bookingUid,
|
bookingUid,
|
||||||
|
showAvailableSeatsCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (seatsPerTimeSlot && seatsPerTimeSlot - attendees > 1) {
|
if (seatsPerTimeSlot && seatsPerTimeSlot - attendees > 1) {
|
||||||
|
@ -116,6 +119,7 @@ export const AvailableTimeSlots = ({
|
||||||
date={dayjs(slots.date)}
|
date={dayjs(slots.date)}
|
||||||
slots={slots.slots}
|
slots={slots.slots}
|
||||||
seatsPerTimeSlot={seatsPerTimeSlot}
|
seatsPerTimeSlot={seatsPerTimeSlot}
|
||||||
|
showAvailableSeatsCount={showAvailableSeatsCount}
|
||||||
availableMonth={
|
availableMonth={
|
||||||
dayjs(selectedDate).format("MM") !== dayjs(slots.date).format("MM")
|
dayjs(selectedDate).format("MM") !== dayjs(slots.date).format("MM")
|
||||||
? dayjs(slots.date).format("MMM")
|
? dayjs(slots.date).format("MMM")
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { shallow } from "zustand/shallow";
|
||||||
|
|
||||||
import { useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
import { useEmbedUiConfig, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||||
import { EventDetails, EventMembers, EventMetaSkeleton, EventTitle } from "@calcom/features/bookings";
|
import { EventDetails, EventMembers, EventMetaSkeleton, EventTitle } from "@calcom/features/bookings";
|
||||||
|
import { SeatsAvailabilityText } from "@calcom/features/bookings/components/SeatsAvailabilityText";
|
||||||
import { EventMetaBlock } from "@calcom/features/bookings/components/event-meta/Details";
|
import { EventMetaBlock } from "@calcom/features/bookings/components/event-meta/Details";
|
||||||
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
import { useTimePreferences } from "@calcom/features/bookings/lib";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
@ -130,13 +131,12 @@ export const EventMeta = () => {
|
||||||
<EventMetaBlock icon={User} className={`${colorClass}`}>
|
<EventMetaBlock icon={User} className={`${colorClass}`}>
|
||||||
<div className="text-bookinghighlight flex items-start text-sm">
|
<div className="text-bookinghighlight flex items-start text-sm">
|
||||||
<p>
|
<p>
|
||||||
{bookingSeatAttendeesQty ? eventTotalSeats - bookingSeatAttendeesQty : eventTotalSeats} /{" "}
|
<SeatsAvailabilityText
|
||||||
{eventTotalSeats}{" "}
|
showExact={!!seatedEventData.showAvailableSeatsCount}
|
||||||
{t("seats_available", {
|
totalSeats={eventTotalSeats}
|
||||||
count: bookingSeatAttendeesQty
|
bookedSeats={bookingSeatAttendeesQty || 0}
|
||||||
? eventTotalSeats - bookingSeatAttendeesQty
|
variant="fraction"
|
||||||
: eventTotalSeats,
|
/>
|
||||||
})}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</EventMetaBlock>
|
</EventMetaBlock>
|
||||||
|
|
|
@ -33,6 +33,7 @@ type SeatedEventData = {
|
||||||
seatsPerTimeSlot?: number | null;
|
seatsPerTimeSlot?: number | null;
|
||||||
attendees?: number;
|
attendees?: number;
|
||||||
bookingUid?: string;
|
bookingUid?: string;
|
||||||
|
showAvailableSeatsCount?: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BookerStore = {
|
export type BookerStore = {
|
||||||
|
@ -206,6 +207,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||||
seatsPerTimeSlot: undefined,
|
seatsPerTimeSlot: undefined,
|
||||||
attendees: undefined,
|
attendees: undefined,
|
||||||
bookingUid: undefined,
|
bookingUid: undefined,
|
||||||
|
showAvailableSeatsCount: true,
|
||||||
},
|
},
|
||||||
setSeatedEventData: (seatedEventData: SeatedEventData) => {
|
setSeatedEventData: (seatedEventData: SeatedEventData) => {
|
||||||
set({ seatedEventData });
|
set({ seatedEventData });
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { Button, SkeletonText } from "@calcom/ui";
|
||||||
|
|
||||||
import { useBookerStore } from "../Booker/store";
|
import { useBookerStore } from "../Booker/store";
|
||||||
import { useTimePreferences } from "../lib";
|
import { useTimePreferences } from "../lib";
|
||||||
|
import { SeatsAvailabilityText } from "./SeatsAvailabilityText";
|
||||||
import { TimeFormatToggle } from "./TimeFormatToggle";
|
import { TimeFormatToggle } from "./TimeFormatToggle";
|
||||||
|
|
||||||
type AvailableTimesProps = {
|
type AvailableTimesProps = {
|
||||||
|
@ -24,6 +25,7 @@ type AvailableTimesProps = {
|
||||||
bookingUid?: string
|
bookingUid?: string
|
||||||
) => void;
|
) => void;
|
||||||
seatsPerTimeSlot?: number | null;
|
seatsPerTimeSlot?: number | null;
|
||||||
|
showAvailableSeatsCount?: boolean | null;
|
||||||
showTimeFormatToggle?: boolean;
|
showTimeFormatToggle?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
availableMonth?: string | undefined;
|
availableMonth?: string | undefined;
|
||||||
|
@ -35,6 +37,7 @@ export const AvailableTimes = ({
|
||||||
slots,
|
slots,
|
||||||
onTimeSelect,
|
onTimeSelect,
|
||||||
seatsPerTimeSlot,
|
seatsPerTimeSlot,
|
||||||
|
showAvailableSeatsCount,
|
||||||
showTimeFormatToggle = true,
|
showTimeFormatToggle = true,
|
||||||
className,
|
className,
|
||||||
availableMonth,
|
availableMonth,
|
||||||
|
@ -110,15 +113,16 @@ export const AvailableTimes = ({
|
||||||
{dayjs.utc(slot.time).tz(timezone).format(timeFormat)}
|
{dayjs.utc(slot.time).tz(timezone).format(timeFormat)}
|
||||||
{bookingFull && <p className="text-sm">{t("booking_full")}</p>}
|
{bookingFull && <p className="text-sm">{t("booking_full")}</p>}
|
||||||
{hasTimeSlots && !bookingFull && (
|
{hasTimeSlots && !bookingFull && (
|
||||||
<p className="flex items-center text-sm lowercase">
|
<p className="flex items-center text-sm">
|
||||||
<span
|
<span
|
||||||
className={classNames(colorClass, "mr-1 inline-block h-2 w-2 rounded-full")}
|
className={classNames(colorClass, "mr-1 inline-block h-2 w-2 rounded-full")}
|
||||||
aria-hidden
|
aria-hidden
|
||||||
/>
|
/>
|
||||||
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot}{" "}
|
<SeatsAvailabilityText
|
||||||
{t("seats_available", {
|
showExact={!!showAvailableSeatsCount}
|
||||||
count: slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot,
|
totalSeats={seatsPerTimeSlot}
|
||||||
})}
|
bookedSeats={slot.attendees || 0}
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { classNames } from "@calcom/lib";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
/**
|
||||||
|
* Whether to show the exact number of seats available or not
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
showExact: boolean;
|
||||||
|
/**
|
||||||
|
* Shows available seats count as either whole number or fraction.
|
||||||
|
*
|
||||||
|
* Applies only when `showExact` is `true`
|
||||||
|
*
|
||||||
|
* @default "whole"
|
||||||
|
*/
|
||||||
|
variant?: "whole" | "fraction";
|
||||||
|
/** Number of seats booked in the event */
|
||||||
|
bookedSeats: number;
|
||||||
|
/** Total number of seats in the event */
|
||||||
|
totalSeats: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SeatsAvailabilityText = ({
|
||||||
|
showExact = true,
|
||||||
|
bookedSeats,
|
||||||
|
totalSeats,
|
||||||
|
variant = "whole",
|
||||||
|
}: Props) => {
|
||||||
|
const { t } = useLocale();
|
||||||
|
const availableSeats = totalSeats - bookedSeats;
|
||||||
|
const isHalfFull = bookedSeats / totalSeats >= 0.5;
|
||||||
|
const isNearlyFull = bookedSeats / totalSeats >= 0.83;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={classNames(showExact && "lowercase")}>
|
||||||
|
{showExact
|
||||||
|
? `${availableSeats}${variant === "fraction" ? ` / ${totalSeats}` : ""} ${t("seats_available", {
|
||||||
|
count: availableSeats,
|
||||||
|
})}`
|
||||||
|
: isNearlyFull
|
||||||
|
? t("seats_nearly_full")
|
||||||
|
: isHalfFull
|
||||||
|
? t("seats_half_full")
|
||||||
|
: t("seats_available", {
|
||||||
|
count: availableSeats,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
|
@ -21,7 +21,10 @@ function RenderIcon({
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
src={eventLocationType.iconUrl}
|
src={eventLocationType.iconUrl}
|
||||||
className="me-[10px] h-4 w-4 opacity-70 invert-[.65] dark:invert-0"
|
className={classNames(
|
||||||
|
eventLocationType?.iconUrl?.includes("-dark") && "dark:invert",
|
||||||
|
"me-[10px] h-4 w-4"
|
||||||
|
)}
|
||||||
alt={`${eventLocationType.label} icon`}
|
alt={`${eventLocationType.label} icon`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -275,6 +275,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
|
||||||
seatsPerTimeSlot: true,
|
seatsPerTimeSlot: true,
|
||||||
recurringEvent: true,
|
recurringEvent: true,
|
||||||
seatsShowAttendees: true,
|
seatsShowAttendees: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
bookingLimits: true,
|
bookingLimits: true,
|
||||||
durationLimits: true,
|
durationLimits: true,
|
||||||
parentId: true,
|
parentId: true,
|
||||||
|
@ -1071,6 +1072,7 @@ async function handler(
|
||||||
// if seats are not enabled we should default true
|
// if seats are not enabled we should default true
|
||||||
seatsShowAttendees: eventType.seatsPerTimeSlot ? eventType.seatsShowAttendees : true,
|
seatsShowAttendees: eventType.seatsPerTimeSlot ? eventType.seatsShowAttendees : true,
|
||||||
seatsPerTimeSlot: eventType.seatsPerTimeSlot,
|
seatsPerTimeSlot: eventType.seatsPerTimeSlot,
|
||||||
|
seatsShowAvailabilityCount: eventType.seatsPerTimeSlot ? eventType.seatsShowAvailabilityCount : true,
|
||||||
schedulingType: eventType.schedulingType,
|
schedulingType: eventType.schedulingType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
*/
|
*/
|
||||||
export type OrganizationBranding =
|
export type OrganizationBranding =
|
||||||
| ({
|
| ({
|
||||||
logo?: string | null | undefined;
|
id: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
fullDomain: string;
|
fullDomain: string;
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const getServerSideProps = async ({ req, res }: GetServerSidePropsContext
|
||||||
|
|
||||||
// Check if logged in user has an organization assigned
|
// Check if logged in user has an organization assigned
|
||||||
const session = await getServerSession({ req, res });
|
const session = await getServerSession({ req, res });
|
||||||
if (!session?.user.organizationId) {
|
if (!session?.user.org?.id) {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
};
|
};
|
||||||
|
@ -26,7 +26,7 @@ export const getServerSideProps = async ({ req, res }: GetServerSidePropsContext
|
||||||
const membership = await prisma.membership.findFirst({
|
const membership = await prisma.membership.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: session?.user.id,
|
userId: session?.user.id,
|
||||||
teamId: session?.user.organizationId,
|
teamId: session?.user.org.id,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
role: true,
|
role: true,
|
||||||
|
|
|
@ -69,7 +69,7 @@ const MembersView = () => {
|
||||||
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState<boolean>(false);
|
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState<boolean>(false);
|
||||||
const [members, setMembers] = useState<Members>([]);
|
const [members, setMembers] = useState<Members>([]);
|
||||||
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||||
enabled: !!session.data?.user?.organizationId,
|
enabled: !!session.data?.user?.org,
|
||||||
});
|
});
|
||||||
const { data: team, isLoading: isTeamLoading } = trpc.viewer.organizations.getOtherTeam.useQuery(
|
const { data: team, isLoading: isTeamLoading } = trpc.viewer.organizations.getOtherTeam.useQuery(
|
||||||
{ teamId },
|
{ teamId },
|
||||||
|
|
|
@ -215,7 +215,7 @@ const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: n
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const bookerUrl = useBookerUrl();
|
const bookerUrl = useBookerUrl();
|
||||||
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||||
enabled: !!session.data?.user?.organizationId,
|
enabled: !!session.data?.user?.org,
|
||||||
});
|
});
|
||||||
const removeMemberMutation = trpc.viewer.teams.removeMember.useMutation({
|
const removeMemberMutation = trpc.viewer.teams.removeMember.useMutation({
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
|
|
|
@ -80,7 +80,7 @@ const MembersView = () => {
|
||||||
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState(showDialog);
|
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState(showDialog);
|
||||||
const [showInviteLinkSettingsModal, setInviteLinkSettingsModal] = useState(false);
|
const [showInviteLinkSettingsModal, setInviteLinkSettingsModal] = useState(false);
|
||||||
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||||
enabled: !!session.data?.user?.organizationId,
|
enabled: !!session.data?.user?.org,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: orgMembersNotInThisTeam, isLoading: isOrgListLoading } =
|
const { data: orgMembersNotInThisTeam, isLoading: isOrgListLoading } =
|
||||||
|
|
|
@ -263,6 +263,7 @@ const EmailEmbed = ({ eventType, username }: { eventType?: EventType; username:
|
||||||
}
|
}
|
||||||
onTimeSelect={onTimeSelect}
|
onTimeSelect={onTimeSelect}
|
||||||
slots={slots}
|
slots={slots}
|
||||||
|
showAvailableSeatsCount={eventType.seatsShowAvailabilityCount}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -39,6 +39,7 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
||||||
price: true,
|
price: true,
|
||||||
currency: true,
|
currency: true,
|
||||||
seatsPerTimeSlot: true,
|
seatsPerTimeSlot: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
bookingFields: true,
|
bookingFields: true,
|
||||||
team: {
|
team: {
|
||||||
select: {
|
select: {
|
||||||
|
|
|
@ -159,7 +159,7 @@ const useTabs = () => {
|
||||||
|
|
||||||
// check if name is in adminRequiredKeys
|
// check if name is in adminRequiredKeys
|
||||||
return tabs.filter((tab) => {
|
return tabs.filter((tab) => {
|
||||||
if (organizationRequiredKeys.includes(tab.name)) return !!session.data?.user?.organizationId;
|
if (organizationRequiredKeys.includes(tab.name)) return !!session.data?.user?.org;
|
||||||
|
|
||||||
if (isAdmin) return true;
|
if (isAdmin) return true;
|
||||||
return !adminRequiredKeys.includes(tab.name);
|
return !adminRequiredKeys.includes(tab.name);
|
||||||
|
@ -205,7 +205,7 @@ const SettingsSidebarContainer = ({
|
||||||
const { data: teams } = trpc.viewer.teams.list.useQuery();
|
const { data: teams } = trpc.viewer.teams.list.useQuery();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||||
enabled: !!session.data?.user?.organizationId,
|
enabled: !!session.data?.user?.org,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: otherTeams } = trpc.viewer.organizations.listOtherTeams.useQuery();
|
const { data: otherTeams } = trpc.viewer.organizations.listOtherTeams.useQuery();
|
||||||
|
@ -523,6 +523,12 @@ const SettingsSidebarContainer = ({
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent className="space-y-0.5">
|
<CollapsibleContent className="space-y-0.5">
|
||||||
|
<VerticalTabItem
|
||||||
|
name={t("profile")}
|
||||||
|
href={`/settings/organizations/teams/other/${otherTeam.id}/profile`}
|
||||||
|
textClassNames="px-3 text-emphasis font-medium text-sm"
|
||||||
|
disableChevron
|
||||||
|
/>
|
||||||
<VerticalTabItem
|
<VerticalTabItem
|
||||||
name={t("members")}
|
name={t("members")}
|
||||||
href={`/settings/organizations/teams/other/${otherTeam.id}/members`}
|
href={`/settings/organizations/teams/other/${otherTeam.id}/members`}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import AdminPasswordBanner from "@calcom/features/users/components/AdminPassword
|
||||||
import VerifyEmailBanner from "@calcom/features/users/components/VerifyEmailBanner";
|
import VerifyEmailBanner from "@calcom/features/users/components/VerifyEmailBanner";
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
import { APP_NAME, DESKTOP_APP_LINK, JOIN_DISCORD, ROADMAP, WEBAPP_URL } from "@calcom/lib/constants";
|
import { APP_NAME, DESKTOP_APP_LINK, JOIN_DISCORD, ROADMAP, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
|
||||||
import getBrandColours from "@calcom/lib/getBrandColours";
|
import getBrandColours from "@calcom/lib/getBrandColours";
|
||||||
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
|
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
|
||||||
import { useIsomorphicLayoutEffect } from "@calcom/lib/hooks/useIsomorphicLayoutEffect";
|
import { useIsomorphicLayoutEffect } from "@calcom/lib/hooks/useIsomorphicLayoutEffect";
|
||||||
|
@ -792,13 +791,12 @@ function SideBarContainer({ bannersHeight }: SideBarContainerProps) {
|
||||||
function SideBar({ bannersHeight, user }: SideBarProps) {
|
function SideBar({ bannersHeight, user }: SideBarProps) {
|
||||||
const { t, isLocaleReady } = useLocale();
|
const { t, isLocaleReady } = useLocale();
|
||||||
const orgBranding = useOrgBranding();
|
const orgBranding = useOrgBranding();
|
||||||
const isOrgBrandingDataFetched = orgBranding !== undefined;
|
|
||||||
|
|
||||||
const publicPageUrl = useMemo(() => {
|
const publicPageUrl = useMemo(() => {
|
||||||
if (!user?.organizationId) return `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user?.username}`;
|
if (!user?.org?.id) return `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user?.username}`;
|
||||||
const publicPageUrl = orgBranding?.slug ? getOrgFullDomain(orgBranding.slug) : "";
|
const publicPageUrl = orgBranding?.slug ? getOrgFullDomain(orgBranding.slug) : "";
|
||||||
return publicPageUrl;
|
return publicPageUrl;
|
||||||
}, [orgBranding?.slug, user?.organizationId, user?.username]);
|
}, [orgBranding?.slug, user?.username, user?.org?.id]);
|
||||||
|
|
||||||
const bottomNavItems: NavigationItemType[] = [
|
const bottomNavItems: NavigationItemType[] = [
|
||||||
{
|
{
|
||||||
|
@ -819,7 +817,7 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "settings",
|
name: "settings",
|
||||||
href: user?.organizationId ? `/settings/organizations/profile` : "/settings/my-account/profile",
|
href: user?.org ? `/settings/organizations/profile` : "/settings/my-account/profile",
|
||||||
icon: Settings,
|
icon: Settings,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -830,12 +828,12 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
|
||||||
className="desktop-transparent bg-muted border-muted fixed left-0 hidden h-full max-h-screen w-14 flex-col overflow-y-auto overflow-x-hidden border-r dark:bg-gradient-to-tr dark:from-[#2a2a2a] dark:to-[#1c1c1c] md:sticky md:flex lg:w-56 lg:px-3">
|
className="desktop-transparent bg-muted border-muted fixed left-0 hidden h-full max-h-screen w-14 flex-col overflow-y-auto overflow-x-hidden border-r dark:bg-gradient-to-tr dark:from-[#2a2a2a] dark:to-[#1c1c1c] md:sticky md:flex lg:w-56 lg:px-3">
|
||||||
<div className="flex h-full flex-col justify-between py-3 lg:pt-4">
|
<div className="flex h-full flex-col justify-between py-3 lg:pt-4">
|
||||||
<header className="items-center justify-between md:hidden lg:flex">
|
<header className="items-center justify-between md:hidden lg:flex">
|
||||||
{!isOrgBrandingDataFetched ? null : orgBranding ? (
|
{orgBranding ? (
|
||||||
<Link href="/settings/organizations/profile" className="px-1.5">
|
<Link href="/settings/organizations/profile" className="px-1.5">
|
||||||
<div className="flex items-center gap-2 font-medium">
|
<div className="flex items-center gap-2 font-medium">
|
||||||
<Avatar
|
<Avatar
|
||||||
alt={`${orgBranding.name} logo`}
|
alt={`${orgBranding.name} logo`}
|
||||||
imageSrc={getPlaceholderAvatar(orgBranding.logo, orgBranding.name)}
|
imageSrc={`${orgBranding.fullDomain}/avatar.png`}
|
||||||
size="xsm"
|
size="xsm"
|
||||||
/>
|
/>
|
||||||
<p className="text line-clamp-1 text-sm">
|
<p className="text line-clamp-1 text-sm">
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type { Action, State } from "./UserListTable";
|
||||||
|
|
||||||
export function ChangeUserRoleModal(props: { state: State; dispatch: Dispatch<Action> }) {
|
export function ChangeUserRoleModal(props: { state: State; dispatch: Dispatch<Action> }) {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const orgId = session?.user.organizationId;
|
const orgId = session?.user.org?.id;
|
||||||
if (!orgId || !props.state.changeMemberRole.user) return null;
|
if (!orgId || !props.state.changeMemberRole.user) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -40,10 +40,10 @@ export function DeleteMemberModal({ state, dispatch }: { state: State; dispatch:
|
||||||
confirmBtnText={t("confirm_remove_member")}
|
confirmBtnText={t("confirm_remove_member")}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
// Shouldnt ever happen just for type safety
|
// Shouldnt ever happen just for type safety
|
||||||
if (!session?.user.organizationId || !state?.deleteMember?.user?.id) return;
|
if (!session?.user.org?.id || !state?.deleteMember?.user?.id) return;
|
||||||
|
|
||||||
removeMemberMutation.mutate({
|
removeMemberMutation.mutate({
|
||||||
teamId: session?.user.organizationId,
|
teamId: session?.user.org.id,
|
||||||
memberId: state?.deleteMember?.user.id,
|
memberId: state?.deleteMember?.user.id,
|
||||||
isOrg: true,
|
isOrg: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ import type { Action, State } from "./UserListTable";
|
||||||
export function ImpersonationMemberModal(props: { state: State; dispatch: Dispatch<Action> }) {
|
export function ImpersonationMemberModal(props: { state: State; dispatch: Dispatch<Action> }) {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const teamId = session?.user.organizationId;
|
const teamId = session?.user.org?.id;
|
||||||
const user = props.state.impersonateMember.user;
|
const user = props.state.impersonateMember.user;
|
||||||
|
|
||||||
if (!user || !teamId) return null;
|
if (!user || !teamId) return null;
|
||||||
|
|
|
@ -46,9 +46,9 @@ export function InviteMemberModal(props: Props) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!session?.user.organizationId) return null;
|
if (!session?.user.org?.id) return null;
|
||||||
|
|
||||||
const orgId = session.user.organizationId;
|
const orgId = session.user.org.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MemberInvitationModal
|
<MemberInvitationModal
|
||||||
|
|
|
@ -79,6 +79,7 @@ const commons = {
|
||||||
schedulingType: SchedulingType.COLLECTIVE,
|
schedulingType: SchedulingType.COLLECTIVE,
|
||||||
seatsPerTimeSlot: null,
|
seatsPerTimeSlot: null,
|
||||||
seatsShowAttendees: null,
|
seatsShowAttendees: null,
|
||||||
|
seatsShowAvailabilityCount: null,
|
||||||
id: 0,
|
id: 0,
|
||||||
hideCalendarNotes: false,
|
hideCalendarNotes: false,
|
||||||
recurringEvent: null,
|
recurringEvent: null,
|
||||||
|
|
|
@ -174,6 +174,7 @@ export default async function getEventTypeById({
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
seatsPerTimeSlot: true,
|
seatsPerTimeSlot: true,
|
||||||
seatsShowAttendees: true,
|
seatsShowAttendees: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
webhooks: {
|
webhooks: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
|
|
@ -93,6 +93,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
|
||||||
afterEventBuffer: 0,
|
afterEventBuffer: 0,
|
||||||
seatsPerTimeSlot: null,
|
seatsPerTimeSlot: null,
|
||||||
seatsShowAttendees: null,
|
seatsShowAttendees: null,
|
||||||
|
seatsShowAvailabilityCount: null,
|
||||||
schedulingType: null,
|
schedulingType: null,
|
||||||
scheduleId: null,
|
scheduleId: null,
|
||||||
bookingLimits: null,
|
bookingLimits: null,
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "EventType" ADD COLUMN "seatsShowAvailabilityCount" BOOLEAN DEFAULT true;
|
|
@ -98,6 +98,7 @@ model EventType {
|
||||||
afterEventBuffer Int @default(0)
|
afterEventBuffer Int @default(0)
|
||||||
seatsPerTimeSlot Int?
|
seatsPerTimeSlot Int?
|
||||||
seatsShowAttendees Boolean? @default(false)
|
seatsShowAttendees Boolean? @default(false)
|
||||||
|
seatsShowAvailabilityCount Boolean? @default(true)
|
||||||
schedulingType SchedulingType?
|
schedulingType SchedulingType?
|
||||||
schedule Schedule? @relation(fields: [scheduleId], references: [id])
|
schedule Schedule? @relation(fields: [scheduleId], references: [id])
|
||||||
scheduleId Int?
|
scheduleId Int?
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Prisma } from "@prisma/client";
|
||||||
export const availabilityUserSelect = Prisma.validator<Prisma.UserSelect>()({
|
export const availabilityUserSelect = Prisma.validator<Prisma.UserSelect>()({
|
||||||
id: true,
|
id: true,
|
||||||
timeZone: true,
|
timeZone: true,
|
||||||
|
email: true,
|
||||||
bufferTime: true,
|
bufferTime: true,
|
||||||
startTime: true,
|
startTime: true,
|
||||||
username: true,
|
username: true,
|
||||||
|
@ -22,7 +23,6 @@ export const availabilityUserSelect = Prisma.validator<Prisma.UserSelect>()({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const baseUserSelect = Prisma.validator<Prisma.UserSelect>()({
|
export const baseUserSelect = Prisma.validator<Prisma.UserSelect>()({
|
||||||
email: true,
|
|
||||||
name: true,
|
name: true,
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
locale: true,
|
locale: true,
|
||||||
|
@ -35,7 +35,6 @@ export const baseUserSelect = Prisma.validator<Prisma.UserSelect>()({
|
||||||
|
|
||||||
export const userSelect = Prisma.validator<Prisma.UserArgs>()({
|
export const userSelect = Prisma.validator<Prisma.UserArgs>()({
|
||||||
select: {
|
select: {
|
||||||
email: true,
|
|
||||||
name: true,
|
name: true,
|
||||||
allowDynamicBooking: true,
|
allowDynamicBooking: true,
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
|
|
|
@ -575,6 +575,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
|
||||||
successRedirectUrl: true,
|
successRedirectUrl: true,
|
||||||
seatsPerTimeSlot: true,
|
seatsPerTimeSlot: true,
|
||||||
seatsShowAttendees: true,
|
seatsShowAttendees: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
periodType: true,
|
periodType: true,
|
||||||
hashedLink: true,
|
hashedLink: true,
|
||||||
webhooks: true,
|
webhooks: true,
|
||||||
|
|
|
@ -206,6 +206,7 @@ export const deleteCredentialHandler = async ({ ctx, input }: DeleteCredentialOp
|
||||||
bookingFields: true,
|
bookingFields: true,
|
||||||
seatsPerTimeSlot: true,
|
seatsPerTimeSlot: true,
|
||||||
seatsShowAttendees: true,
|
seatsShowAttendees: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
eventName: true,
|
eventName: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -295,6 +296,7 @@ export const deleteCredentialHandler = async ({ ctx, input }: DeleteCredentialOp
|
||||||
cancellationReason: "Payment method removed by organizer",
|
cancellationReason: "Payment method removed by organizer",
|
||||||
seatsPerTimeSlot: booking.eventType?.seatsPerTimeSlot,
|
seatsPerTimeSlot: booking.eventType?.seatsPerTimeSlot,
|
||||||
seatsShowAttendees: booking.eventType?.seatsShowAttendees,
|
seatsShowAttendees: booking.eventType?.seatsShowAttendees,
|
||||||
|
seatsShowAvailabilityCount: booking.eventType?.seatsShowAvailabilityCount,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
eventName: booking?.eventType?.eventName,
|
eventName: booking?.eventType?.eventName,
|
||||||
|
|
|
@ -191,6 +191,7 @@ async function getBookings({
|
||||||
currency: true,
|
currency: true,
|
||||||
metadata: true,
|
metadata: true,
|
||||||
seatsShowAttendees: true,
|
seatsShowAttendees: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
team: {
|
team: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
|
|
@ -239,7 +239,6 @@ export async function sendVerificationEmail({
|
||||||
}) {
|
}) {
|
||||||
const token: string = randomBytes(32).toString("hex");
|
const token: string = randomBytes(32).toString("hex");
|
||||||
|
|
||||||
if (!connectionInfo.autoAccept) {
|
|
||||||
await prisma.verificationToken.create({
|
await prisma.verificationToken.create({
|
||||||
data: {
|
data: {
|
||||||
identifier: usernameOrEmail,
|
identifier: usernameOrEmail,
|
||||||
|
@ -252,6 +251,7 @@ export async function sendVerificationEmail({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (!connectionInfo.autoAccept) {
|
||||||
await sendTeamInviteEmail({
|
await sendTeamInviteEmail({
|
||||||
language: translation,
|
language: translation,
|
||||||
from: ctx.user.name || `${team.name}'s admin`,
|
from: ctx.user.name || `${team.name}'s admin`,
|
||||||
|
@ -262,15 +262,6 @@ export async function sendVerificationEmail({
|
||||||
isOrg: input.isOrg,
|
isOrg: input.isOrg,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// we have already joined the team in createNewUserConnectToOrgIfExists so we dont need to connect via token
|
|
||||||
await prisma.verificationToken.create({
|
|
||||||
data: {
|
|
||||||
identifier: usernameOrEmail,
|
|
||||||
token,
|
|
||||||
expires: new Date(new Date().setHours(168)), // +1 week
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await sendOrganizationAutoJoinEmail({
|
await sendOrganizationAutoJoinEmail({
|
||||||
language: translation,
|
language: translation,
|
||||||
from: ctx.user.name || `${team.name}'s admin`,
|
from: ctx.user.name || `${team.name}'s admin`,
|
||||||
|
|
|
@ -177,6 +177,7 @@ export interface CalendarEvent {
|
||||||
eventTypeId?: number | null;
|
eventTypeId?: number | null;
|
||||||
appsStatus?: AppsStatus[];
|
appsStatus?: AppsStatus[];
|
||||||
seatsShowAttendees?: boolean | null;
|
seatsShowAttendees?: boolean | null;
|
||||||
|
seatsShowAvailabilityCount?: boolean | null;
|
||||||
attendeeSeatId?: string;
|
attendeeSeatId?: string;
|
||||||
seatsPerTimeSlot?: number | null;
|
seatsPerTimeSlot?: number | null;
|
||||||
schedulingType?: SchedulingType | null;
|
schedulingType?: SchedulingType | null;
|
||||||
|
|
|
@ -16,7 +16,13 @@ declare module "next-auth" {
|
||||||
email_verified?: boolean;
|
email_verified?: boolean;
|
||||||
impersonatedByUID?: number;
|
impersonatedByUID?: number;
|
||||||
belongsToActiveTeam?: boolean;
|
belongsToActiveTeam?: boolean;
|
||||||
organizationId?: number | null;
|
org?: {
|
||||||
|
id: number;
|
||||||
|
name?: string;
|
||||||
|
slug: string;
|
||||||
|
fullDomain: string;
|
||||||
|
domainSuffix: string;
|
||||||
|
};
|
||||||
username?: PrismaUser["username"];
|
username?: PrismaUser["username"];
|
||||||
role?: PrismaUser["role"] | "INACTIVE_ADMIN";
|
role?: PrismaUser["role"] | "INACTIVE_ADMIN";
|
||||||
locale?: string | null;
|
locale?: string | null;
|
||||||
|
@ -32,6 +38,13 @@ declare module "next-auth/jwt" {
|
||||||
role?: UserPermissionRole | "INACTIVE_ADMIN" | null;
|
role?: UserPermissionRole | "INACTIVE_ADMIN" | null;
|
||||||
impersonatedByUID?: number | null;
|
impersonatedByUID?: number | null;
|
||||||
belongsToActiveTeam?: boolean;
|
belongsToActiveTeam?: boolean;
|
||||||
|
org?: {
|
||||||
|
id: number;
|
||||||
|
name?: string;
|
||||||
|
slug: string;
|
||||||
|
fullDomain: string;
|
||||||
|
domainSuffix: string;
|
||||||
|
};
|
||||||
organizationId?: number | null;
|
organizationId?: number | null;
|
||||||
locale?: string;
|
locale?: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user