From a638708871e0c4f1a18b9b3802234d1dda3b2339 Mon Sep 17 00:00:00 2001 From: Leo Giovanetti Date: Thu, 15 Dec 2022 10:45:54 -0300 Subject: [PATCH 1/8] Baikal CalDAV error hint (#6021) --- apps/web/public/static/locales/en/common.json | 2 +- packages/app-store/caldavcalendar/api/add.ts | 18 +++++++++++++-- .../caldavcalendar/pages/setup/index.tsx | 23 ++++++++++++++++++- .../components/ImpersonatingBanner.tsx | 2 +- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index c445ac1ff2..4a09c31c4e 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -920,7 +920,7 @@ "allow_booker_to_select_duration": "Allow booker to select duration", "impersonate_user_tip": "All uses of this feature is audited.", "impersonating_user_warning": "Impersonating username \"{{user}}\".", - "impersonating_stop_instructions": "<0>Click Here to stop.", + "impersonating_stop_instructions": "Click here to stop", "event_location_changed": "Updated - Your event changed the location", "location_changed_event_type_subject": "Location Changed: {{eventType}} with {{name}} at {{date}}", "current_location": "Current Location", diff --git a/packages/app-store/caldavcalendar/api/add.ts b/packages/app-store/caldavcalendar/api/add.ts index 98fa3fe297..600f0accac 100644 --- a/packages/app-store/caldavcalendar/api/add.ts +++ b/packages/app-store/caldavcalendar/api/add.ts @@ -40,8 +40,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) await prisma.credential.create({ data, }); - } catch (reason) { - logger.error("Could not add this caldav account", reason); + } catch (e) { + logger.error("Could not add this caldav account", e); + if (e instanceof Error) { + let message = e.message; + if (e.message.indexOf("Invalid credentials") > -1 && url.indexOf("dav.php") > -1) { + const parsedUrl = new URL(url); + const adminUrl = + parsedUrl.protocol + + "//" + + parsedUrl.hostname + + (parsedUrl.port ? ":" + parsedUrl.port : "") + + "/admin/?/settings/standard/"; + message = `Couldn\'t connect to caldav account, please verify WebDAV authentication type is set to "Basic"`; + return res.status(500).json({ message, actionUrl: adminUrl }); + } + } return res.status(500).json({ message: "Could not add this caldav account" }); } diff --git a/packages/app-store/caldavcalendar/pages/setup/index.tsx b/packages/app-store/caldavcalendar/pages/setup/index.tsx index bfcc1fe61d..424b7b5733 100644 --- a/packages/app-store/caldavcalendar/pages/setup/index.tsx +++ b/packages/app-store/caldavcalendar/pages/setup/index.tsx @@ -18,6 +18,7 @@ export default function CalDavCalendarSetup() { }); const [errorMessage, setErrorMessage] = useState(""); + const [errorActionUrl, setErrorActionUrl] = useState(""); return (
@@ -49,6 +50,9 @@ export default function CalDavCalendarSetup() { const json = await res.json(); if (!res.ok) { setErrorMessage(json?.message || t("something_went_wrong")); + if (json.actionUrl) { + setErrorActionUrl(json.actionUrl); + } } else { router.push(json.url); } @@ -78,7 +82,24 @@ export default function CalDavCalendarSetup() { /> - {errorMessage && } + {errorMessage && ( + + Go to Admin + + ) : undefined + } + className="my-4" + /> + )}
)} @@ -65,7 +67,7 @@ function WizardForm(props: { onClick={() => { setStep(step + 1); }}> - {step < steps.length ? "Next" : "Finish"} + {step < steps.length ? t("next_step_text") : t("finish")}
)} From 987756c4d40c1d93d577ddbc78ef3c8967d147f7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Dec 2022 21:18:33 +0100 Subject: [PATCH 3/8] New Crowdin translations by Github Action (#6022) Co-authored-by: Crowdin Bot --- apps/web/public/static/locales/ar/common.json | 1 + apps/web/public/static/locales/de/common.json | 3 +- apps/web/public/static/locales/el/common.json | 100 +++++++++++++++++- apps/web/public/static/locales/fr/common.json | 24 ++++- apps/web/public/static/locales/it/common.json | 1 + apps/web/public/static/locales/ko/common.json | 1 + apps/web/public/static/locales/pt/common.json | 1 + apps/web/public/static/locales/sr/common.json | 1 + apps/web/public/static/locales/sv/common.json | 1 + apps/web/public/static/locales/tr/common.json | 15 +++ apps/web/public/static/locales/uk/common.json | 60 ++++++++++- apps/web/public/static/locales/vi/common.json | 28 ++++- .../public/static/locales/zh-CN/common.json | 1 + 13 files changed, 232 insertions(+), 5 deletions(-) diff --git a/apps/web/public/static/locales/ar/common.json b/apps/web/public/static/locales/ar/common.json index c2608537f5..dbf692ec3a 100644 --- a/apps/web/public/static/locales/ar/common.json +++ b/apps/web/public/static/locales/ar/common.json @@ -1426,6 +1426,7 @@ "disable_app": "تعطيل التطبيق", "disable_app_description": "يمكن لتعطيل هذا التطبيق أن يسبب مشاكل مع كيفية تفاعل المستخدمين مع Cal", "edit_keys": "تحرير المفاتيح", + "admin_apps_description": "تمكين تطبيقات مثيل Cal الخاص بك", "no_available_apps": "لا توجد تطبيقات متاحة", "no_available_apps_description": "الرجاء التأكد من وجود تطبيقات في نشرك تحت 'حزم/متجر تطبيقات'", "no_apps": "لا توجد تطبيقات مفعلة في هذا المظهر من Cal", diff --git a/apps/web/public/static/locales/de/common.json b/apps/web/public/static/locales/de/common.json index 48d41bbe3e..d9778ce433 100644 --- a/apps/web/public/static/locales/de/common.json +++ b/apps/web/public/static/locales/de/common.json @@ -1337,5 +1337,6 @@ "test_preview": "Vorschau testen", "route_to": "Weiterleiten zu", "test_preview_description": "Testen Sie Ihr Weiterleitungsformular, ohne Daten zu senden", - "test_routing": "Testweiterleitung" + "test_routing": "Testweiterleitung", + "admin_apps_description": "Hier findest du eine Auflistung deiner Apps" } diff --git a/apps/web/public/static/locales/el/common.json b/apps/web/public/static/locales/el/common.json index d44e1c4b35..85d64e50b4 100644 --- a/apps/web/public/static/locales/el/common.json +++ b/apps/web/public/static/locales/el/common.json @@ -9,5 +9,103 @@ "reset_password_subject": "{{appName}}: Οδηγίες επαναφοράς κωδικού πρόσβασης", "event_declined_subject": "Απορρίφθηκε: {{eventType}} με {{name}} στις {{date}}", "event_cancelled_subject": "Ακύρωση: {{eventType}} με {{name}} στις {{date}}", - "cancellation_reason": "Λόγος ακύρωσης (προαιρετικό)" + "need_to_reschedule_or_cancel": "Χρειάζεται να επαναπρογραμματίσετε ή να ακυρώσετε;", + "cancellation_reason": "Λόγος ακύρωσης (προαιρετικό)", + "rejection_reason": "Λόγος απόρριψης", + "rejection_reason_title": "Απόρριψη του αιτήματος κράτησης;", + "rejection_confirmation": "Απόρριψη κράτησης", + "error_message": "Το μήνυμα σφάλματος ήταν: '{{errorMessage}}'", + "refund_failed_subject": "Η επιστροφή χρημάτων απέτυχε: {{name}} - {{date}} - {{eventType}}", + "refund_failed": "Η επιστροφή χρημάτων για την εκδήλωση {{eventType}} με {{userName}} στις {{date}} απέτυχε.", + "a_refund_failed": "Αποτυχία επιστροφής χρημάτων", + "awaiting_payment_subject": "Σε αναμονή Πληρωμής: {{eventType}} με {{name}} στις {{date}}", + "meeting_awaiting_payment": "Η πληρωμή της συνάντησής σας εκκρεμεί", + "help": "Βοήθεια", + "price": "Τιμή", + "payment": "Πληρωμή", + "missing_card_fields": "Λείπουν τα πεδία της κάρτας", + "pay_now": "Πληρωμή τώρα", + "terms_summary": "Περίληψη όρων", + "open_env": "Ανοίξτε το .env και συμφωνήστε με την Άδεια χρήσης μας", + "env_changed": "Έχω αλλάξει το .env μου", + "accept_license": "Αποδοχή Άδειας", + "no_more_results": "Δεν υπάρχουν άλλα αποτελέσματα", + "no_results": "Δεν υπάρχουν αποτελέσματα", + "load_more_results": "Φόρτωση περισσότερων αποτελεσμάτων", + "integration_meeting_id": "ID συνάντησης {{integrationName}} : {{meetingId}}", + "confirmed_event_type_subject": "Επιβεβαίωση: {{eventType}} με {{name}} στις {{date}}", + "new_event_request": "Νέο αίτημα εκδήλωσης: {{attendeeName}} - {{date}} - {{eventType}}", + "confirm_or_reject_request": "Επιβεβαιώστε ή απορρίψτε το αίτημα", + "check_bookings_page_to_confirm_or_reject": "Ελέγξτε τη σελίδα κρατήσεων για να επιβεβαιώσετε ή να απορρίψετε την κράτηση.", + "event_awaiting_approval": "Ένα γεγονός περιμένει την έγκρισή σας", + "event_type": "Τύπος Συμβάντος", + "meeting_password": "Συνθηματικό Συνάντησης", + "meeting_url": "URL Συνάντησης", + "meeting_request_rejected": "Το αίτημα συνάντησής σας απορρίφθηκε", + "hi": "Γεια", + "manage_this_team": "Διαχείριση αυτής της ομάδας", + "team_info": "Πληροφορίες Ομάδας", + "hidden_team_member_title": "Είστε κρυμμένοι σε αυτήν την ομάδα", + "edit_webhook": "Επεξεργασία Webhook", + "delete_webhook": "Διαγραφή Webhook", + "webhook_enabled": "Ενεργοποιημένο Webhook", + "webhook_disabled": "Απενεργοποιημένο Webhook", + "webhook_response": "Απάντηση Webhook", + "webhook_test": "Έλεγχος Webhook", + "webhook_created_successfully": "Το Webhook δημιουργήθηκε επιτυχώς!", + "webhook_updated_successfully": "Το Webhook ενημερώθηκε επιτυχώς!", + "webhook_removed_successfully": "Το Webhook αφαιρέθηκε επιτυχώς!", + "dismiss": "Παράβλεψη", + "no_data_yet": "Δεν υπάρχουν δεδομένα ακόμη", + "upcoming": "Επερχόμενα", + "recurring": "Επαναλαμβανόμενα", + "past": "Παρελθοντικά", + "choose_a_file": "Επιλογή αρχείου...", + "upload_image": "Μεταφόρτωση εικόνας", + "username": "Όνομα χρήστη", + "is_still_available": "είναι ακόμα διαθέσιμο.", + "documentation": "Τεκμηρίωση", + "blog": "Ιστολόγιο", + "blog_description": "Διαβάστε τα τελευταία μας νέα και άρθρα", + "popular_pages": "Δημοφιλείς σελίδες", + "register_now": "Εγγραφή τώρα", + "register": "Εγγραφή", + "page_doesnt_exist": "Η σελίδα δεν υπάρχει.", + "check_spelling_mistakes_or_go_back": "Έλεγχος για ορθογραφικά λάθη ή επιστροφή στην προηγούμενη σελίδα.", + "404_page_not_found": "404: Η σελίδα δεν βρέθηκε.", + "getting_started": "Ξεκινήστε", + "already_have_an_account": "Έχετε ήδη λογαριασμό;", + "create_account": "Δημιουργία Λογαριασμού", + "confirm_password": "Επιβεβαίωση κωδικού πρόσβασης", + "create_your_account": "Δημιουργία λογαριασμού", + "sign_up": "Εγγραφή", + "youve_been_logged_out": "Έχετε αποσυνδεθεί", + "hope_to_see_you_soon": "Ελπίζουμε να σας ξαναδούμε σύντομα!", + "please_try_again_and_contact_us": "Παρακαλούμε δοκιμάστε ξανά και επικοινωνήστε μαζί μας αν το πρόβλημα παραμένει.", + "no_account_exists": "Δεν υπάρχει λογαριασμός που να ταιριάζει με τη διεύθυνση email.", + "2fa_enter_six_digit_code": "Εισάγετε τον εξαψήφιο κωδικό από την εφαρμογή ελέγχου ταυτότητας παρακάτω.", + "create_an_account": "Δημιουργία λογαριασμού", + "dont_have_an_account": "Δεν έχετε λογαριασμό;", + "sign_in_account": "Συνδεθείτε στο λογαριασμό σας", + "sign_in": "Είσοδος", + "connect": "Σύνδεση", + "try_for_free": "Δοκιμάστε δωρεάν", + "add_to_calendar": "Προσθήκη στο ημερολόγιο", + "add_another_calendar": "Προσθήκη άλλου ημερολογίου", + "meeting_is_scheduled": "Η συνάντηση έχει προγραμματιστεί", + "submitted": "Η κράτησή σας έχει υποβληθεί", + "reset_password": "Επαναφορά Κωδικού Πρόσβασης", + "change_your_password": "Αλλαγή κωδικού πρόσβασης", + "show_password": "Εμφάνιση κωδικού πρόσβασης", + "hide_password": "Απόκρυψη κωδικού πρόσβασης", + "try_again": "Δοκιμάστε ξανά", + "sunday_time_error": "Μη έγκυρη ώρα την Κυριακή", + "monday_time_error": "Μη έγκυρη ώρα τη Δευτέρα", + "tuesday_time_error": "Μη έγκυρη ώρα την Τρίτη", + "wednesday_time_error": "Μη έγκυρη ώρα την Τετάρτη", + "thursday_time_error": "Μη έγκυρη ώρα την Πέμπτη", + "friday_time_error": "Μη έγκυρη ώρα την Παρασκευή", + "saturday_time_error": "Μη έγκυρη ώρα το Σάββατο", + "no_meeting_found": "Δε βρέθηκε συνάντηση", + "bookings": "Κρατήσεις" } diff --git a/apps/web/public/static/locales/fr/common.json b/apps/web/public/static/locales/fr/common.json index ab1960f5d4..1041fd4ced 100644 --- a/apps/web/public/static/locales/fr/common.json +++ b/apps/web/public/static/locales/fr/common.json @@ -562,6 +562,7 @@ "available_durations": "Durées disponibles", "default_duration": "Durée par défaut", "default_duration_no_options": "Veuillez d'abord choisir les durées disponibles", + "multiple_duration_mins": "{{count}} $t(minute_timeUnit)", "minutes": "Minutes", "round_robin": "Round Robin", "round_robin_description": "Faites tourner les réunions entre plusieurs membres de l'équipe.", @@ -627,6 +628,7 @@ "teams": "Équipes", "team": "Équipe", "team_billing": "Facturation d'équipe", + "team_billing_description": "Gérer la facturation pour votre équipe", "upgrade_to_flexible_pro_title": "Nous avons modifié la facturation pour les équipes", "upgrade_to_flexible_pro_message": "Des membres dans votre équipe n'ont pas de place. Mettez à niveau votre offre pro pour couvrir les places manquantes.", "changed_team_billing_info": "Depuis janvier 2022, nous facturons chaque place aux membres de l'équipe. Les membres de votre équipe qui ont eu la version Pro gratuitement disposent maintenant d'un essai de 14 jours. Une fois leur période d'essai expirée, ces membres seront cachés pour votre équipe, sauf si vous mettez à niveau maintenant.", @@ -702,6 +704,7 @@ "hide_event_type": "Masquer le type d'événement", "edit_location": "Modifier le lieu", "into_the_future": "dans le futur", + "when_booked_with_less_than_notice": "Si réservé avec moins de de préavis", "within_date_range": "Dans une plage de dates", "indefinitely_into_future": "Sans doute dans le futur", "add_new_custom_input_field": "Ajouter un nouveau champ de saisie personnalisé", @@ -721,6 +724,7 @@ "delete_account_confirmation_message": "Êtes-vous sûr de vouloir supprimer votre compte {{appName}} ? Toute personne avec qui vous avez partagé le lien de votre compte ne pourra plus réserver en utilisant ce lien et toutes les préférences que vous avez enregistrées seront perdues.", "integrations": "Intégrations", "apps": "Applications", + "apps_listing": "Liste des applications", "category_apps": "Applications {{category}}", "app_store": "App Store", "app_store_description": "Connecter les personnes, la technologie et l'espace de travail.", @@ -744,6 +748,7 @@ "toggle_calendars_conflict": "Activer/désactiver les calendriers pour lesquels vous souhaiter vérifier les conflits afin d'éviter les doubles réservations.", "select_destination_calendar": "Créer des événements le", "connect_additional_calendar": "Connecter un calendrier supplémentaire", + "calendar_updated_successfully": "Calendrier mis à jour avec succès", "conferencing": "Conférence", "calendar": "Calendrier", "payments": "Paiements", @@ -776,6 +781,7 @@ "trending_apps": "Applications populaires", "explore_apps": "{{category}} applications", "installed_apps": "Applications installées", + "free_to_use_apps": "Gratuit", "no_category_apps": "Aucune application {{category}}", "no_category_apps_description_calendar": "Ajouter une application de calendrier pour vérifier les conflits et éviter les doubles réservations", "no_category_apps_description_conferencing": "Essayez d'ajouter une application de conférence pour interconnecter les appels vidéo avec vos clients", @@ -814,6 +820,8 @@ "verify_wallet": "Vérifier le portefeuille", "connect_metamask": "Connecter Metamask", "create_events_on": "Créer des événements le :", + "enterprise_license": "Il s'agit d'une fonctionnalité d'entreprise", + "enterprise_license_description": "Pour activer cette fonctionnalité, obtenez une clé de déploiement sur la console {{consoleUrl}} et ajoutez-la à votre .env en tant que CALCOM_LICENSE_KEY. Si votre équipe a déjà une licence, veuillez contacter {{supportMail}} pour obtenir de l'aide.", "missing_license": "Licence manquante", "signup_requires": "Licence commerciale requise", "signup_requires_description": "{{companyName}} ne propose pas actuellement de version open source gratuite de la page d'inscription. Pour obtenir un accès complet aux composants d'inscription, vous devez acquérir une licence commerciale. Pour une utilisation personnelle, nous recommandons la Plateforme de Données Prisma ou toute autre interface Postgres pour créer des comptes.", @@ -905,6 +913,7 @@ "user_impersonation_heading": "Connexion en tant qu'autre utilisateur", "user_impersonation_description": "Permet à notre équipe d'assistance de se connecter temporairement lorsque vous nous aidez à résoudre rapidement tous les problèmes que vous nous signalez.", "team_impersonation_description": "Permet aux administrateurs de votre équipe de se connecter temporairement en tant que vous-même.", + "allow_booker_to_select_duration": "Autoriser l'organisateur à sélectionner la durée", "impersonate_user_tip": "Toutes les utilisations de cette fonctionnalité sont vérifiées.", "impersonating_user_warning": "Identification du nom d'utilisateur \"{{user}}\".", "impersonating_stop_instructions": "<0>Cliquez ici pour arrêter.", @@ -1022,6 +1031,9 @@ "error_removing_app": "Erreur lors de la suppression de l'application", "web_conference": "Conférence en ligne", "requires_confirmation": "Nécessite une confirmation", + "always_requires_confirmation": "Toujours", + "requires_confirmation_threshold": "Nécessite une confirmation si réservé avec un préavis de < {{time}} $t({{unit}}_timeUnit)", + "may_require_confirmation": "Peut nécessiter une confirmation", "nr_event_type_one": "{{count}} type d'événement", "nr_event_type_other": "{{count}} types d'événements", "add_action": "Ajouter une action", @@ -1109,6 +1121,9 @@ "event_limit_tab_description": "Fréquence de réservation", "event_advanced_tab_description": "Paramètres du calendrier & plus...", "event_advanced_tab_title": "Avancé", + "event_setup_multiple_duration_error": "Configuration de l'événement : plusieurs durées requièrent au moins une option.", + "event_setup_multiple_duration_default_error": "Configuration de l'événement: veuillez sélectionner une durée par défaut valide.", + "event_setup_booking_limits_error": "Les limites de réservation doivent être en ordre croissant. [jour,semaine,mois,année]", "select_which_cal": "Sélectionnez le calendrier auquel ajouter des réservations", "custom_event_name": "Nom de l'événement personnalisé", "custom_event_name_description": "Créer des noms d'événements personnalisés à afficher sur l'événement du calendrier", @@ -1162,8 +1177,11 @@ "invoices": "Factures", "embeds": "Intègre", "impersonation": "Identification", + "impersonation_description": "Paramètres de gestion de l'identité de l'utilisateur", "users": "Utilisateurs", "profile_description": "Gérer les paramètres de votre profil {{appName}}", + "users_description": "Vous trouverez ici une liste de tous les utilisateurs", + "users_listing": "Liste des utilisateurs", "general_description": "Gérer les paramètres pour votre langue et votre fuseau horaire", "calendars_description": "Configurez la manière dont vos types d'événements interagissent avec vos calendriers", "appearance_description": "Gérer les paramètres pour votre apparence de réservation", @@ -1368,6 +1386,7 @@ "number_sms_notifications": "Numéro de téléphone (notifications SMS)", "attendee_email_workflow": "E-mail du participant", "attendee_email_info": "E-mail de la personne ayant réservé", + "kbar_search_placeholder": "Saisissez une commande ou une recherche...", "invalid_credential": "Oh non ! L'autorisation semble avoir expiré ou avoir été révoquée. Veuillez la réinstaller.", "choose_common_schedule_team_event": "Choisissez un horaire commun", "choose_common_schedule_team_event_description": "Activez cette option si vous souhaitez utiliser un horaire commun entre les hôtes. Si désactivée, chaque hôte sera réservé en fonction de son planning par défaut.", @@ -1378,5 +1397,8 @@ "test_preview": "Tester l'aperçu", "route_to": "Router vers", "test_preview_description": "Tester votre formulaire de routage sans envoyer de données", - "test_routing": "Tester le routage" + "test_routing": "Tester le routage", + "payment_app_disabled": "Un administrateur a désactivé une application de paiement", + "edit_event_type": "Modifier le type d'événement", + "admin_apps_description": "Activer les applications pour votre instance de Cal" } diff --git a/apps/web/public/static/locales/it/common.json b/apps/web/public/static/locales/it/common.json index ec0196bf45..90c3c58134 100644 --- a/apps/web/public/static/locales/it/common.json +++ b/apps/web/public/static/locales/it/common.json @@ -1426,6 +1426,7 @@ "disable_app": "Disabilita app", "disable_app_description": "La disattivazione di questa app potrebbe causare problemi con il modo in cui i tuoi utenti interagiscono con Cal", "edit_keys": "Modifica chiavi", + "admin_apps_description": "Abilita le app per la tua istanza di Cal", "no_available_apps": "Nessuna app disponibile", "no_available_apps_description": "Assicurarsi che ci siano delle applicazioni nella propria distribuzione in 'packages/app-store'", "no_apps": "Non ci sono applicazioni abilitate in questa istanza di Cal", diff --git a/apps/web/public/static/locales/ko/common.json b/apps/web/public/static/locales/ko/common.json index 774fff6bce..003d39bcd2 100644 --- a/apps/web/public/static/locales/ko/common.json +++ b/apps/web/public/static/locales/ko/common.json @@ -1426,6 +1426,7 @@ "disable_app": "앱 비활성화", "disable_app_description": "이 앱을 비활성화하면 사용자가 Cal과 상호 작용하는 방식에 문제가 발생할 수 있습니다", "edit_keys": "키 편집", + "admin_apps_description": "Cal 인스턴스용 앱 활성화", "no_available_apps": "사용 가능한 앱이 없습니다", "no_available_apps_description": "'packages/app-store' 아래 배포에 앱이 있는지 확인하세요", "no_apps": "Cal의 이 인스턴스에서 활성화된 앱이 없습니다", diff --git a/apps/web/public/static/locales/pt/common.json b/apps/web/public/static/locales/pt/common.json index b55830c399..eca3444c29 100644 --- a/apps/web/public/static/locales/pt/common.json +++ b/apps/web/public/static/locales/pt/common.json @@ -1426,6 +1426,7 @@ "disable_app": "Desativar a aplicação", "disable_app_description": "A desativação desta aplicação pode causar problemas na forma como os seus utilizadores interagem com o Cal", "edit_keys": "Editar chaves", + "admin_apps_description": "Ativar aplicações para a sua instância do Cal", "no_available_apps": "Não existem aplicações disponíveis", "no_available_apps_description": "Por favor, certifique-se que existem aplicações na sua instalação em 'packages/app-store'", "no_apps": "Não existem aplicações ativas nesta instância do Cal", diff --git a/apps/web/public/static/locales/sr/common.json b/apps/web/public/static/locales/sr/common.json index 4c0c11906f..78515708b2 100644 --- a/apps/web/public/static/locales/sr/common.json +++ b/apps/web/public/static/locales/sr/common.json @@ -1426,6 +1426,7 @@ "disable_app": "Onemogući aplikaciju", "disable_app_description": "Onemogućavanje ove aplikacije može da stvori probleme u komunikaciji vaših korisnika sa Cal-om", "edit_keys": "Izmeni ključeve", + "admin_apps_description": "Omogućite aplikacije za vašu instancu Cal-a", "no_available_apps": "Nema dostupnih aplikacija", "no_available_apps_description": "Uverite se da ima aplikacija u vašoj fascikli za instalaciju pod „packages/app-store“", "no_apps": "Nema omogućenih aplikacija u ovoj instanci Cal-a", diff --git a/apps/web/public/static/locales/sv/common.json b/apps/web/public/static/locales/sv/common.json index 626a131907..ba463b83db 100644 --- a/apps/web/public/static/locales/sv/common.json +++ b/apps/web/public/static/locales/sv/common.json @@ -1426,6 +1426,7 @@ "disable_app": "Inaktivera app", "disable_app_description": "Inaktivering av den här appen kan orsaka problem med hur dina användare interagerar med Cal", "edit_keys": "Redigera nycklar", + "admin_apps_description": "Aktivera appar för din Cal-version", "no_available_apps": "Det finns inga tillgängliga appar", "no_available_apps_description": "Se till att det finns appar i din distribution under \"paket/app-store\"", "no_apps": "Det finns inga appar aktiverade i denna instans av Cal", diff --git a/apps/web/public/static/locales/tr/common.json b/apps/web/public/static/locales/tr/common.json index cebbf05fb3..a6c31ec2c5 100644 --- a/apps/web/public/static/locales/tr/common.json +++ b/apps/web/public/static/locales/tr/common.json @@ -704,6 +704,7 @@ "hide_event_type": "Etkinlik türünü gizle", "edit_location": "Konumu düzenle", "into_the_future": "gelecekte", + "when_booked_with_less_than_notice": " bildiriminden daha az süre ile rezervasyon yapıldığında", "within_date_range": "Bir tarih aralığında", "indefinitely_into_future": "Süresiz olarak gelecekte", "add_new_custom_input_field": "Yeni özel veri girdi alanı ekle", @@ -1032,6 +1033,7 @@ "web_conference": "Web konferansı", "requires_confirmation": "Onay gerekli", "always_requires_confirmation": "Her zaman", + "requires_confirmation_threshold": "{{time}} $t({{unit}}_timeUnit) bildiriminden daha az süre ile rezervasyon yapıldığında onay gerekir", "may_require_confirmation": "Onay gerekebilir", "nr_event_type_one": "{{count}} etkinlik türü", "nr_event_type_other": "{{count}} etkinlik türü", @@ -1400,11 +1402,19 @@ "payment_app_disabled": "Bir yönetici, ödeme uygulamasını devre dışı bıraktı", "edit_event_type": "Etkinlik türünü düzenle", "collective_scheduling": "Toplu Planlama", + "make_it_easy_to_book": "Herkes müsait olduğunda ekibinizin rezervasyon yapmasını kolaylaştırın.", + "find_the_best_person": "Mevcut en iyi kişiyi bulun ve ekip üyeleriniz arasında rotasyon yapın.", + "fixed_round_robin": "Sabit döngü", + "add_one_fixed_attendee": "Tek bir sabit katılımcı ve birden fazla katılımcının olduğu bir döngü ekleyin.", "calcom_is_better_with_team": "Cal.com ekiplerle daha iyidir", "add_your_team_members": "Ekip üyelerinizi etkinlik türlerinize ekleyin. Herkesi eklemek için toplu planlamayı kullanın veya döngüsel planlama ile en uygun kişiyi bulun.", "booking_limit_reached": "Bu etkinlik türü için Rezervasyon Sınırına ulaşıldı", "admin_has_disabled": "Bir yönetici {{appName}} uygulamasını devre dışı bıraktı", + "disabled_app_affects_event_type": "Bir yönetici {{eventType}} etkinlik türünüzü etkileyen {{appName}} uygulamasını devre dışı bıraktı", + "disable_payment_app": "Yönetici, {{title}} etkinlik türünüzü etkileyebilecek {{appName}} uygulamasını devre dışı bıraktı. Katılımcılar yine de bu tür bir etkinlik için rezervasyon yaptırabilirler ancak herhangi bir ödeme yapmalarına gerek yoktur. Bu durumu önlemek için yöneticiniz ödeme yönteminizi yeniden etkinleştirene kadar etkinlik türünü gizleyebilirsiniz.", + "payment_disabled_still_able_to_book": "Katılımcılar yine de bu tür bir etkinlik için rezervasyon yaptırabilirler ancak herhangi bir ödeme yapmalarına gerek yoktur. Bu durumu önlemek için yöneticiniz ödeme yönteminizi yeniden etkinleştirene kadar etkinlik türünü gizleyebilirsiniz.", "app_disabled_with_event_type": "Yönetici, etkinlik {{title}} türünüzü etkileyen {{appName}} uygulamasını devre dışı bıraktı.", + "app_disabled_video": "Yönetici, etkinlik türlerinizi etkileyebilecek {{appName}} uygulamasını devre dışı bıraktı. Konum olarak {{appName}} olan etkinlik türleriniz varsa varsayılan uygulama Cal Video olacaktır.", "app_disabled_subject": "{{appName}} devre dışı bırakıldı", "navigate_installed_apps": "Yüklü uygulamalara git", "disabled_calendar": "Yüklü başka bir takviminiz varsa yeni randevular bu takvime eklenecektir. Aksi takdirde yeni bir takvim bağlamazsanız yeni rezervasyonları kaçırabilirsiniz.", @@ -1414,13 +1424,18 @@ "app_is_disabled": "{{appName}} devre dışı bırakıldı", "keys_have_been_saved": "Anahtarlar kaydedildi", "disable_app": "Uygulamaları Devre Dışı Bırak", + "disable_app_description": "Bu uygulamanın devre dışı bırakılması kullanıcılarınızın Cal ile etkileşimde bulunmaları konusunda sorunlara neden olabilir", "edit_keys": "Anahtarları Düzenle", + "admin_apps_description": "Cal örneğiniz için uygulamayı etkinleştirin", "no_available_apps": "Kullanılabilir uygulama yok", + "no_available_apps_description": "'Paketler/uygulama mağazası' altındaki dağıtım klasörünüzde uygulamaların olduğundan emin olun", + "no_apps": "Bu Cal örneğindeki hiçbir uygulama etkin değil", "apps_settings": "Uygulama ayarları", "fill_this_field": "Lütfen bu alanı doldurun", "options": "Seçenekler", "enter_option": "{{index}} Seçeneğini Girin", "add_an_option": "Bir seçenek ekle", "radio": "Radio", + "event_type_duplicate_copy_text": "{{slug}}-kopyala", "set_as_default": "Varsayılan olarak ayarla" } diff --git a/apps/web/public/static/locales/uk/common.json b/apps/web/public/static/locales/uk/common.json index 25f28a8974..9824f28532 100644 --- a/apps/web/public/static/locales/uk/common.json +++ b/apps/web/public/static/locales/uk/common.json @@ -561,6 +561,8 @@ "duration": "Тривалість", "available_durations": "Доступні варіанти тривалості", "default_duration": "Тривалість за замовчуванням", + "default_duration_no_options": "Спочатку виберіть доступні варіанти тривалості", + "multiple_duration_mins": "{{count}} $t(minute_timeUnit)", "minutes": "Хвилини", "round_robin": "Ротація", "round_robin_description": "Кілька учасників команди призначаються для нарад циклічно й по черзі.", @@ -626,6 +628,7 @@ "teams": "Команди", "team": "Команда", "team_billing": "Виставлення рахунків для команд", + "team_billing_description": "Керуйте виставленням рахунків у своїй команді", "upgrade_to_flexible_pro_title": "Ми змінили умови оплати для команд", "upgrade_to_flexible_pro_message": "У вашій команді є учасники без придбаних місць. Перейдіть на план Pro, щоб отримати всі потрібні місця.", "changed_team_billing_info": "Станом на січень 2022 року оплата з учасників команди стягується за кількістю місць. Учасників вашої команди, які безкоштовно користувалися функціями Pro, тепер переведено на 14-денні пробні версії. Щойно пробний період завершиться, учасників вашої команди, для яких не придбано план Pro, буде приховано.", @@ -701,6 +704,7 @@ "hide_event_type": "Приховати тип заходу", "edit_location": "Змінити розташування", "into_the_future": "у майбутньому", + "when_booked_with_less_than_notice": "Якщо бронювання створено менше ніж за до заходу", "within_date_range": "У діапазоні дат", "indefinitely_into_future": "Колись у майбутньому", "add_new_custom_input_field": "Додати нове власне поле введення", @@ -720,6 +724,7 @@ "delete_account_confirmation_message": "Справді видалити обліковий запис {{appName}}? Усі, кому ви надавали посилання на свій обліковий запис, більше не зможуть бронювати ваш час за його допомогою. Усі збережені налаштування буде втрачено.", "integrations": "Інтеграції", "apps": "Додатки", + "apps_listing": "Список додатків", "category_apps": "Додатки з категорії «{{category}}»", "app_store": "App Store", "app_store_description": "Спілкування та технології на робочому місці.", @@ -743,6 +748,7 @@ "toggle_calendars_conflict": "Увімкніть ті календарі, які потрібно перевірити на наявність конфліктів, щоб уникнути подвійних бронювань.", "select_destination_calendar": "Створюйте заходи в календарі", "connect_additional_calendar": "Підключити додатковий календар", + "calendar_updated_successfully": "Календар оновлено", "conferencing": "Відеоконференції", "calendar": "Календар", "payments": "Платежі", @@ -775,6 +781,7 @@ "trending_apps": "Популярні додатки", "explore_apps": "Додатки з категорії «{{category}}»", "installed_apps": "Установлені додатки", + "free_to_use_apps": "Безкоштовні", "no_category_apps": "{{category}} — немає додатків", "no_category_apps_description_calendar": "Додайте додаток для календаря, щоб перевіряти, чи немає конфліктів у розкладі, і уникати подвійних бронювань", "no_category_apps_description_conferencing": "Спробуйте додати додаток для конференцій, щоб інтегрувати можливість відеорозмов зі своїми клієнтами", @@ -813,6 +820,8 @@ "verify_wallet": "Пройдіть перевірку гаманця", "connect_metamask": "Підключіть Metamask", "create_events_on": "Створюйте заходи в календарі", + "enterprise_license": "Це корпоративна функція", + "enterprise_license_description": "Щоб увімкнути цю функцію, отримайте ключ розгортання в консолі {{consoleUrl}} і додайте його у свій файл .env як CALCOM_LICENSE_KEY. Якщо у вашої команди вже є ліцензія, зверніться по допомогу за адресою {{supportMail}}.", "missing_license": "Відсутня ліцензія", "signup_requires": "Потрібна комерційна ліцензія", "signup_requires_description": "{{companyName}} зараз не надає безкоштовну версію сторінки реєстрації з відкритим кодом. Щоб отримати повний доступ до складових функціоналу реєстрації, потрібно придбати комерційну ліцензію. Для особистого використання та створення облікових записів радимо Prisma Data Platform або будь-який інший інтерфейс Postgres.", @@ -904,6 +913,7 @@ "user_impersonation_heading": "Виконання ролі користувача", "user_impersonation_description": "Ви можете дозволити нашій команді підтримки тимчасово входити в систему під вашим іменем, щоб швидко вирішувати проблеми, про які ви повідомляєте.", "team_impersonation_description": "Дозвольте адміністраторам своєї команди тимчасово входити під вашим іменем.", + "allow_booker_to_select_duration": "Дозволити автору бронювання вибирати тривалість", "impersonate_user_tip": "Усі випадки використання цієї функції підпадають під аудит.", "impersonating_user_warning": "Виконується роль користувача {{user}}.", "impersonating_stop_instructions": "<0>Натисніть тут, щоб зупинити.", @@ -1021,6 +1031,9 @@ "error_removing_app": "Не вдалося вилучити додаток", "web_conference": "Вебконференція", "requires_confirmation": "Потрібне підтвердження", + "always_requires_confirmation": "Завжди", + "requires_confirmation_threshold": "Вимагати підтвердження, якщо бронювання створено менше ніж за {{time}} $t({{unit}}_timeUnit) до заходу", + "may_require_confirmation": "Може вимагати підтвердження", "nr_event_type_one": "{{count}} тип заходу", "nr_event_type_other": "Типів заходів: {{count}}", "add_action": "Додати дію", @@ -1108,6 +1121,9 @@ "event_limit_tab_description": "Як часто ваш час можуть бронювати", "event_advanced_tab_description": "Налаштування календаря та інше…", "event_advanced_tab_title": "Додатково", + "event_setup_multiple_duration_error": "Налаштування заходу: якщо мають бути доступні різні варіанти, потрібно вказати принаймні один.", + "event_setup_multiple_duration_default_error": "Налаштування заходу: виберіть припустиму тривалість за замовчуванням.", + "event_setup_booking_limits_error": "Ліміти на бронювання мають бути впорядковані за зростанням. [день,тиждень,місяць,рік]", "select_which_cal": "Виберіть календар, у який додаватимуться бронювання", "custom_event_name": "Користувацька назва події", "custom_event_name_description": "Вибирайте для заходів власні назви, що показуватимуться в календарі", @@ -1161,8 +1177,11 @@ "invoices": "Рахунки-фактури", "embeds": "Вставки", "impersonation": "Вхід під іншим іменем", + "impersonation_description": "Налаштування входу під іншим іменем", "users": "Користувачі", "profile_description": "Керуйте налаштуваннями свого профілю {{appName}}", + "users_description": "Тут наведено список усіх користувачів", + "users_listing": "Список користувачів", "general_description": "Налаштуйте параметри мови й часового поясу", "calendars_description": "Налаштуйте, як типи заходів мають взаємодіяти з вашими календарями", "appearance_description": "Налаштуйте варіанти оформлення свого бронювання", @@ -1367,6 +1386,7 @@ "number_sms_notifications": "Номер телефону (SMS-сповіщення)", "attendee_email_workflow": "Адреса ел. пошти учасника", "attendee_email_info": "Адреса ел. пошти особи, яка бронює", + "kbar_search_placeholder": "Введіть команду або пошуковий запис…", "invalid_credential": "Отакої! Схоже, дозвіл більше не дійсний або його відкликано. Перевстановіть додаток знову.", "choose_common_schedule_team_event": "Виберіть спільний розклад", "choose_common_schedule_team_event_description": "Увімкніть цей параметр, щоб використовувати спільний для двох ведучих розклад. Якщо цей параметр вимкнено, бронювання для кожного з ведучих відбуватиметься за їхніми власними графіками.", @@ -1377,5 +1397,43 @@ "test_preview": "Перевірити попередній перегляд", "route_to": "Кінцева точка", "test_preview_description": "Перевірте свою форму переспрямування без надсилання даних", - "test_routing": "Перевірка переспрямування" + "test_routing": "Перевірка переспрямування", + "payment_app_disabled": "Додаток для оплати вимкнув адміністратор", + "edit_event_type": "Редагувати тип заходу", + "collective_scheduling": "Колективне планування", + "make_it_easy_to_book": "Просте бронювання у випадках, коли всі члени вашої команди доступні.", + "find_the_best_person": "Пошук найкращого доступного члена команди та циклічна ротація між ними.", + "fixed_round_robin": "Фіксована циклічна ротація", + "add_one_fixed_attendee": "Додавання одного фіксованого учасника та циклічна ротація між кількома учасниками.", + "calcom_is_better_with_team": "З Cal.com краще працювати в командах", + "add_your_team_members": "Додавайте членів своєї команди в типи заходів. Колективне планування дає змогу включати всіх або знаходити найвідповіднішого члена завдяки циклічній ротації.", + "booking_limit_reached": "Для цього типу заходу досягнуто ліміт бронювання", + "admin_has_disabled": "Адміністратор вимкнув {{appName}}", + "disabled_app_affects_event_type": "Адміністратор вимкнув {{appName}}, що впливає на ваш тип заходу «{{eventType}}»", + "disable_payment_app": "Адміністратор вимкнув {{appName}}, що впливає на ваш тип заходу «{{title}}». Учасники все одно можуть бронювати події такого типу, але від них не вимагатиметься оплата. Ви можете приховати цей тип заходу, щоб цього не ставалося, та дочекатися на активацію способу оплати з боку адміністратора.", + "payment_disabled_still_able_to_book": "Учасники все одно можуть бронювати події такого типу, але від них не вимагатиметься оплата. Ви можете приховати цей тип заходу, щоб цього не ставалося, та дочекатися на активацію способу оплати з боку адміністратора.", + "app_disabled_with_event_type": "Адміністратор вимкнув {{appName}}, що впливає на ваш тип заходу «{{title}}».", + "app_disabled_video": "Адміністратор вимкнув {{appName}}, що впливає на ваші типи заходів. Якщо у вас є типи заходів, де {{appName}} визначає розташування, за замовчуванням використовуватиметься Cal Video.", + "app_disabled_subject": "{{appName}} вимкнено", + "navigate_installed_apps": "Перейти до встановлених додатків", + "disabled_calendar": "Якщо ви встановите інший календар, у нього буде додано нові бронювання. Якщо цього не станеться, під’єднайте новий календар, щоб не пропускати нові бронювання.", + "enable_apps": "Увімкнення додатків", + "enable_apps_description": "Активуйте додатки, які користувачі зможуть інтегрувати з Cal.com", + "app_is_enabled": "{{appName}} увімкнено", + "app_is_disabled": "{{appName}} вимкнено", + "keys_have_been_saved": "Ключі збережено", + "disable_app": "Вимкнути додаток", + "disable_app_description": "Якщо вимкнути цей додаток, у користувачів можуть виникнути проблеми із роботою з Cal", + "edit_keys": "Редагувати ключі", + "no_available_apps": "Немає доступних додатків", + "no_available_apps_description": "Перевірте, чи в «packages/app-store» є додатки для розгортання", + "no_apps": "У цьому екземплярі Cal немає ввімкнених додатків", + "apps_settings": "Налаштування додатків", + "fill_this_field": "Заповніть це поле", + "options": "Варіанти", + "enter_option": "Введіть варіант {{index}}", + "add_an_option": "Додайте варіант", + "radio": "Радіо", + "event_type_duplicate_copy_text": "{{slug}}-копія", + "set_as_default": "Встановити за замовчуванням" } diff --git a/apps/web/public/static/locales/vi/common.json b/apps/web/public/static/locales/vi/common.json index d9a721558c..ad82726ea2 100644 --- a/apps/web/public/static/locales/vi/common.json +++ b/apps/web/public/static/locales/vi/common.json @@ -559,6 +559,10 @@ "collective": "Tập thể", "collective_description": "Lên lịch họp khi tất cả các thành viên trong nhóm đã chọn đều có mặt.", "duration": "Khoảng thời gian", + "available_durations": "Khoảng thời gian khả dụng", + "default_duration": "Khoảng thời gian mặc định", + "default_duration_no_options": "Vui lòng chọn trước tiên những khoảng thời gian khả dụng", + "multiple_duration_mins": "{{count}}$t(minute_timeUnit)", "minutes": "Phút", "round_robin": "Round Robin", "round_robin_description": "Luân phiên những cuộc họp giữa các thành viên trong nhóm.", @@ -624,6 +628,7 @@ "teams": "Các nhóm", "team": "Nhóm", "team_billing": "Thanh toán nhóm", + "team_billing_description": "Quản lí thanh toán cho đội ngũ của bạn", "upgrade_to_flexible_pro_title": "Chúng tôi đã thay đổi thanh toán cho các nhóm", "upgrade_to_flexible_pro_message": "Có những thành viên trong nhóm của bạn không có gói. Nâng cấp gói PRO của bạn để trang trải những gói bị thiếu.", "changed_team_billing_info": "Kể từ tháng 1 năm 2022, chúng tôi tính phí trên cơ sở từng người cho các thành viên trong nhóm. Các thành viên trong nhóm của bạn mà từng có PRO miễn phí nay sẽ chuyển sang dùng thử 14 ngày. Khi thời gian dùng thử hết hạn, các thành viên này sẽ bị ẩn khỏi nhóm của bạn trừ khi bạn nâng cấp.", @@ -699,6 +704,7 @@ "hide_event_type": "Ẩn loại sự kiện", "edit_location": "Chỉnh sửa vị trí", "into_the_future": "trong tương lai", + "when_booked_with_less_than_notice": "Khi đặt hẹn với khoảng thời gian thông báo ít hơn ", "within_date_range": "Trong phạm vi ngày", "indefinitely_into_future": "Vô thời hạn trong tương lai", "add_new_custom_input_field": "Thêm trường tùy chỉnh mới", @@ -718,6 +724,7 @@ "delete_account_confirmation_message": "Bạn có chắc chắn muốn xóa tài khoản {{appName}} của mình không? Bất kỳ ai mà bạn đã chia sẻ liên kết tài khoản của mình sẽ không thể đặt trước bằng liên kết đó nữa và mọi tùy chọn bạn đã lưu sẽ bị mất.", "integrations": "Tích hợp", "apps": "Ứng dụng", + "apps_listing": "Danh sách ứng dụng", "category_apps": "Ứng dụng {{category}}", "app_store": "Cửa hàng ứng dụng", "app_store_description": "Kết nối con người, công nghệ và nơi làm việc.", @@ -741,6 +748,7 @@ "toggle_calendars_conflict": "Bật lịch mà bạn muốn kiểm tra trùng ngày để tránh đặt lịch hẹn trùng.", "select_destination_calendar": "Tạo sự kiện trên", "connect_additional_calendar": "Kết nối lịch bổ sung", + "calendar_updated_successfully": "Đã cập nhật lịch thành công", "conferencing": "Hội nghị", "calendar": "Lịch", "payments": "Thanh toán", @@ -773,6 +781,7 @@ "trending_apps": "Ứng dụng thịnh hành", "explore_apps": "{{category}} ứng dụng", "installed_apps": "Ứng dụng đã cài đặt", + "free_to_use_apps": "Miễn phí", "no_category_apps": "Không có ứng dụng {{category}}", "no_category_apps_description_calendar": "Thêm một ứng dụng lịch để kiểm tra xung đột nhằm tránh đặt lịch hẹn trùng", "no_category_apps_description_conferencing": "Thử thêm vào một ứng dụng hội nghị để hợp nhất cuộc gọi video với khách hàng của bạn", @@ -811,6 +820,8 @@ "verify_wallet": "Xác minh Ví", "connect_metamask": "Kết nối Metamask", "create_events_on": "Tạo sự kiện trên", + "enterprise_license": "Đây là tính năng doanh nghiệp", + "enterprise_license_description": "Để bật tính năng này, nhận khoá triển khai tại console {{consoleUrl}} và thêm nó vào .env của bạn ở dạng CALCOM_LICENSE_KEY. Nếu nhóm của bạn đã có giấy phép, vui lòng liên hệ {{supportMail}} để được trợ giúp.", "missing_license": "Giấy phép bị thiếu", "signup_requires": "Yêu cầu giấy phép thương mại", "signup_requires_description": "{{companyName}} hiện không cung cấp phiên bản nguồn mở miễn phí của trang đăng ký. Để nhận toàn quyền truy cập vào các thành phần đăng ký, bạn cần có giấy phép thương mại. Đối với mục đích sử dụng cá nhân, chúng tôi khuyên bạn nên sử dụng Nền tảng dữ liệu Prisma hoặc bất kỳ giao diện Postgres nào khác để tạo tài khoản.", @@ -902,6 +913,7 @@ "user_impersonation_heading": "Mạo danh người dùng", "user_impersonation_description": "Cho phép đội ngũ hỗ trợ tạm thời đăng nhập với tư cách là bạn nhằm giúp chúng tôi nhanh chóng giải quyết mọi vấn đề mà bạn báo cáo cho chúng tôi.", "team_impersonation_description": "Cho phép các quản trị viên của nhóm bạn tạm thời đăng nhập bằng danh tính của bạn.", + "allow_booker_to_select_duration": "Cho phép người đặt hẹn chọn khoảng thời gian", "impersonate_user_tip": "Tất cả những lần dùng tính năng này đều bị kiểm toán.", "impersonating_user_warning": "Đang mạo danh tên người dùng \"{{user}}\".", "impersonating_stop_instructions": "<0>Nhấp vào đây để ngừng lại.", @@ -1019,6 +1031,9 @@ "error_removing_app": "Lỗi khi gỡ bỏ ứng dụng", "web_conference": "Hội nghị web", "requires_confirmation": "Yêu cầu xác nhận", + "always_requires_confirmation": "Luôn luôn", + "requires_confirmation_threshold": "Cần xác nhận nếu đặt hẹn với thông báo trước chưa đầy {{time}}$t({{unit}}_timeUnit)", + "may_require_confirmation": "Có thể yêu cầu xác nhận", "nr_event_type_one": "{{count}} loại sự kiện", "nr_event_type_other": "{{count}} loại sự kiện", "add_action": "Thêm hoạt động", @@ -1106,6 +1121,9 @@ "event_limit_tab_description": "Bạn có thể được đặt lịch bao lần", "event_advanced_tab_description": "Cài đặt cho lịch & nhiều tính năng khác...", "event_advanced_tab_title": "Nâng cao", + "event_setup_multiple_duration_error": "Thiết lập sự kiện: Nhiều khoảng thời gian cần ít nhất 1 tuỳ chọn.", + "event_setup_multiple_duration_default_error": "Thiết lập sự kiện: Vui lòng chọn một khoảng thời gian mặc định hợp lệ.", + "event_setup_booking_limits_error": "Giới hạn lịch hẹn phải theo thứ tự tăng dần. [day,week,month,year]", "select_which_cal": "Chọn lịch nào cần thêm lịch hẹn vào", "custom_event_name": "Tên sự kiện tuỳ chỉnh", "custom_event_name_description": "Tạo những tên sự kiện tuỳ chỉnh để hiển thị ở phần sự kiện lịch", @@ -1159,8 +1177,11 @@ "invoices": "Hoá đơn", "embeds": "Nhúng", "impersonation": "Mạo danh", + "impersonation_description": "Cài đặt để quản lý mạo danh người dùng", "users": "Người dùng", "profile_description": "Quản lí cài đặt cho hồ sơ {{appName}} của bạn", + "users_description": "Tại đây bạn có thể tìm thấy danh sách tất cả người dùng", + "users_listing": "Danh sách người dùng", "general_description": "Quản lí cài đặt cho ngôn ngữ và múi giờ của bạn", "calendars_description": "Cấu hình cách các loại sự kiện của bạn tương tác với lịch của bạn", "appearance_description": "Quản lí cài đặt cho giao diện lịch hẹn của bạn", @@ -1365,6 +1386,7 @@ "number_sms_notifications": "Số điện thoại (thông báo SMS)", "attendee_email_workflow": "Email người tham dự", "attendee_email_info": "Email người tham gia lịch hẹn", + "kbar_search_placeholder": "Nhập một lệnh hoặc tìm kiếm...", "invalid_credential": "Ôi không! Có vẻ như quyền đã hết hạn hoặc đã bị thu hồi. Vui lòng cài đặt lại.", "choose_common_schedule_team_event": "Chọn một lịch chung", "choose_common_schedule_team_event_description": "Bật mục này nếu bạn muốn dùng lịch thông thường giữa các chủ sự kiện. Khi tắt đi, mỗi chủ sự kiện sẽ được đặt lịch theo lịch mặc định của họ.", @@ -1375,5 +1397,9 @@ "test_preview": "Kiểm tra Xem trước", "route_to": "Định hướng đến", "test_preview_description": "Kiểm tra biểu mẫu định hướng mà không cần gửi bất kỳ dữ liệu nào", - "test_routing": "Kiểm tra định hướng" + "test_routing": "Kiểm tra định hướng", + "payment_app_disabled": "Một quản trị viên đã vô hiệu hoá một ứng dụng thanh toán", + "edit_event_type": "Sửa loại sự kiện", + "collective_scheduling": "Lên lịch tập thể", + "admin_apps_description": "Bật các ứng dụng cho thực thể Cal của bạn" } diff --git a/apps/web/public/static/locales/zh-CN/common.json b/apps/web/public/static/locales/zh-CN/common.json index 1cce43bc18..b2e9fb33f6 100644 --- a/apps/web/public/static/locales/zh-CN/common.json +++ b/apps/web/public/static/locales/zh-CN/common.json @@ -1418,6 +1418,7 @@ "disable_app": "禁用应用", "disable_app_description": "禁用此应用可能会导致您的用户与 Cal 的交互方式出现问题", "edit_keys": "编辑密钥", + "admin_apps_description": "为您的 Cal 实例启用应用", "no_available_apps": "没有可用的应用", "no_available_apps_description": "请确保您在“packages/app-store”下的部署中有应用", "no_apps": "此 Cal 实例中未启用任何应用", From a54952b41a8a2d5a6369d8d6202611a7c7782ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20L=C3=B3pez?= Date: Thu, 15 Dec 2022 13:19:35 -0700 Subject: [PATCH 4/8] Adds tests for date overrides (#6027) --- apps/web/pages/availability/[schedule].tsx | 6 +- apps/web/pages/availability/troubleshoot.tsx | 95 +++++++++++-------- apps/web/playwright/availability.e2e.ts | 54 +++++++++++ .../components/DateOverrideInputDialog.tsx | 8 +- .../schedules/components/DateOverrideList.tsx | 2 +- 5 files changed, 124 insertions(+), 41 deletions(-) create mode 100644 apps/web/playwright/availability.e2e.ts diff --git a/apps/web/pages/availability/[schedule].tsx b/apps/web/pages/availability/[schedule].tsx index a4e320249f..20d7eabd09 100644 --- a/apps/web/pages/availability/[schedule].tsx +++ b/apps/web/pages/availability/[schedule].tsx @@ -74,7 +74,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => { excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))} onChange={(ranges) => append({ ranges })} Trigger={ - } @@ -128,7 +128,9 @@ export default function Availability({ schedule }: { schedule: number }) { } + render={({ field }) => ( + + )} /> } subtitle={ diff --git a/apps/web/pages/availability/troubleshoot.tsx b/apps/web/pages/availability/troubleshoot.tsx index bef2365538..b033bb66e9 100644 --- a/apps/web/pages/availability/troubleshoot.tsx +++ b/apps/web/pages/availability/troubleshoot.tsx @@ -1,10 +1,10 @@ -import { useState } from "react"; - import dayjs from "@calcom/dayjs"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { RouterOutputs, trpc } from "@calcom/trpc/react"; import { Shell, SkeletonText } from "@calcom/ui"; +import useRouterQuery from "@lib/hooks/useRouterQuery"; + type User = RouterOutputs["viewer"]["me"]; export interface IBusySlot { @@ -16,7 +16,9 @@ export interface IBusySlot { const AvailabilityView = ({ user }: { user: User }) => { const { t } = useLocale(); - const [selectedDate, setSelectedDate] = useState(dayjs()); + const { date, setQuery: setSelectedDate } = useRouterQuery("date"); + const selectedDate = dayjs(date); + const formattedSelectedDate = selectedDate.format("YYYY-MM-DD"); const { data, isLoading } = trpc.viewer.availability.user.useQuery( { @@ -30,6 +32,17 @@ const AvailabilityView = ({ user }: { user: User }) => { } ); + const overrides = + data?.dateOverrides.reduce((acc, override) => { + if ( + formattedSelectedDate !== dayjs(override.start).format("YYYY-MM-DD") && + formattedSelectedDate !== dayjs(override.end).format("YYYY-MM-DD") + ) + return acc; + acc.push({ ...override, source: "Date override" }); + return acc; + }, [] as IBusySlot[]) || []; + return (
@@ -37,9 +50,9 @@ const AvailabilityView = ({ user }: { user: User }) => { { - if (e.target.value) setSelectedDate(dayjs(e.target.value)); + if (e.target.value) setSelectedDate(e.target.value); }} /> {t("hover_over_bold_times_tip")} @@ -49,39 +62,47 @@ const AvailabilityView = ({ user }: { user: User }) => { {t("your_day_starts_at")} {convertMinsToHrsMins(user.startTime)}
- {isLoading ? ( - <> - - - - ) : data && data.busy.length > 0 ? ( - data.busy - .sort((a: IBusySlot, b: IBusySlot) => (a.start > b.start ? -1 : 1)) - .map((slot: IBusySlot) => ( -
-
- {t("calendar_shows_busy_between")}{" "} - - {dayjs(slot.start).format("HH:mm")} - {" "} - {t("and")}{" "} - - {dayjs(slot.end).format("HH:mm")} - {" "} - {t("on")} {dayjs(slot.start).format("D")}{" "} - {t(dayjs(slot.start).format("MMMM").toLowerCase())} {dayjs(slot.start).format("YYYY")} - {slot.title && ` - (${slot.title})`} - {slot.source && {` - (source: ${slot.source})`}} + {(() => { + if (isLoading) + return ( + <> + + + + ); + + if (data && (data.busy.length > 0 || overrides.length > 0)) + return [...data.busy, ...overrides] + .sort((a: IBusySlot, b: IBusySlot) => (a.start > b.start ? -1 : 1)) + .map((slot: IBusySlot) => ( +
+
+ {t("calendar_shows_busy_between")}{" "} + + {dayjs(slot.start).format("HH:mm")} + {" "} + {t("and")}{" "} + + {dayjs(slot.end).format("HH:mm")} + {" "} + {t("on")} {dayjs(slot.start).format("D")}{" "} + {t(dayjs(slot.start).format("MMMM").toLowerCase())} {dayjs(slot.start).format("YYYY")} + {slot.title && ` - (${slot.title})`} + {slot.source && {` - (source: ${slot.source})`}} +
-
- )) - ) : ( -
-
{t("calendar_no_busy_slots")}
-
- )} + )); + return ( +
+
{t("calendar_no_busy_slots")}
+
+ ); + })()}
diff --git a/apps/web/playwright/availability.e2e.ts b/apps/web/playwright/availability.e2e.ts new file mode 100644 index 0000000000..4bebb0f0d8 --- /dev/null +++ b/apps/web/playwright/availability.e2e.ts @@ -0,0 +1,54 @@ +import { expect } from "@playwright/test"; + +import dayjs from "@calcom/dayjs"; + +import { test } from "./lib/fixtures"; + +test.describe.configure({ mode: "parallel" }); + +test.describe("Availablity tests", () => { + test.beforeEach(async ({ page, users }) => { + const user = await users.create(); + await user.login(); + await page.goto("/availability"); + // We wait until loading is finished + await page.waitForSelector('[data-testid="schedules"]'); + }); + + test.afterEach(async ({ users }) => { + await users.deleteAll(); + }); + + test("Date Overrides", async ({ page }) => { + await test.step("Can add a date override", async () => { + await page.locator('[data-testid="schedules"] > li a').click(); + await page.locator('[data-testid="add-override"]').click(); + await page.locator('[id="modal-title"]').waitFor(); + await page.locator('[data-testid="incrementMonth"]').click(); + await page.locator('[data-testid="day"][data-disabled="false"]').nth(0).click(); + await page.locator('[data-testid="date-override-mark-unavailable"]').click(); + await page.locator('[data-testid="add-override-submit-btn"]').click(); + await expect(page.locator('[data-testid="date-overrides-list"] > li')).toHaveCount(1); + await page.locator('[form="availability-form"][type="submit"]').click(); + }); + + await test.step("Date override is displayed in troubleshooter", async () => { + const response = await page.waitForResponse("**/api/trpc/viewer.availability.schedule.update?batch=1"); + const json = await response.json(); + // @ts-expect-error trust me bro + const date = json[0].result.data.json.schedule.availability.find((a) => !!a.date); + const troubleshooterURL = `/availability/troubleshoot?date=${dayjs(date.date).format("YYYY-MM-DD")}`; + await page.goto(troubleshooterURL); + await expect(page.locator('[data-testid="troubleshooter-busy-time"]')).toHaveCount(1); + }); + }); + + test("Availablity pages", async ({ page }) => { + await test.step("Can add a new schedule", async () => { + await page.locator('[data-testid="new-schedule"]').click(); + await page.locator('[id="name"]').fill("More working hours"); + page.locator('[type="submit"]').click(); + await expect(page.locator("[data-testid=availablity-title]")).toHaveValue("More working hours"); + }); + }); +}); diff --git a/packages/features/schedules/components/DateOverrideInputDialog.tsx b/packages/features/schedules/components/DateOverrideInputDialog.tsx index f9a0282fe5..1fb8484753 100644 --- a/packages/features/schedules/components/DateOverrideInputDialog.tsx +++ b/packages/features/schedules/components/DateOverrideInputDialog.tsx @@ -146,10 +146,16 @@ const DateOverrideForm = ({ label={t("date_overrides_mark_all_day_unavailable_one")} checked={datesUnavailable} onCheckedChange={setDatesUnavailable} + data-testid="date-override-mark-unavailable" />
- diff --git a/packages/features/schedules/components/DateOverrideList.tsx b/packages/features/schedules/components/DateOverrideList.tsx index 5637b291ea..146a0a9e5c 100644 --- a/packages/features/schedules/components/DateOverrideList.tsx +++ b/packages/features/schedules/components/DateOverrideList.tsx @@ -38,7 +38,7 @@ const DateOverrideList = ({ }; return ( -
    +
      {items.map((item, index) => (
    • From 20402ac26c63ecdae0e9b06a52b9b2acf4991b24 Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Thu, 15 Dec 2022 21:48:22 +0100 Subject: [PATCH 5/8] Fixes for testing workflow action (#6005) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * additional checks for testing workflow action * fix old variable in testAction mutaiton * remove workflowId * only allow team plan to test action Co-authored-by: CarinaWolli Co-authored-by: alannnc Co-authored-by: Omar López --- .../components/WorkflowStepContainer.tsx | 9 +--- .../trpc/server/routers/viewer/workflows.tsx | 54 ++++++++++++++----- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx index 45febd510c..aa8c2547bb 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -565,11 +565,9 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { ); testActionMutation.mutate({ - action: step.action, + step, emailSubject, reminderBody, - template: step.template, - sender: step.sender || SENDER_ID, }); } else { const isNumberValid = @@ -603,12 +601,9 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { ); testActionMutation.mutate({ - action: step.action, + step, emailSubject: "", reminderBody: reminderBody || "", - template: step.template, - sendTo: step.sendTo || "", - sender: step.sender || SENDER_ID, }); setConfirmationDialogOpen(false); }}> diff --git a/packages/trpc/server/routers/viewer/workflows.tsx b/packages/trpc/server/routers/viewer/workflows.tsx index 1b3be67122..5b460fb7c3 100644 --- a/packages/trpc/server/routers/viewer/workflows.tsx +++ b/packages/trpc/server/routers/viewer/workflows.tsx @@ -825,17 +825,51 @@ export const workflowsRouter = router({ testAction: authedRateLimitedProcedure({ intervalInMs: 10000, limit: 3 }) .input( z.object({ - action: z.enum(WORKFLOW_ACTIONS), + step: z.object({ + id: z.number(), + stepNumber: z.number(), + action: z.enum(WORKFLOW_ACTIONS), + workflowId: z.number(), + sendTo: z.string().optional().nullable(), + reminderBody: z.string().optional().nullable(), + emailSubject: z.string().optional().nullable(), + template: z.enum(WORKFLOW_TEMPLATES), + numberRequired: z.boolean().nullable(), + sender: z.string().optional().nullable(), + }), emailSubject: z.string(), reminderBody: z.string(), - template: z.enum(WORKFLOW_TEMPLATES), - sendTo: z.string().optional(), - sender: z.string().optional(), }) ) .mutation(async ({ ctx, input }) => { - const { action, emailSubject, reminderBody, template, sendTo, sender } = input; + const { user } = ctx; + const { step, emailSubject, reminderBody } = input; + const { action, template, sendTo, sender } = step; + + const senderID = sender || SENDER_ID; + try { + const userWorkflow = await ctx.prisma.workflow.findUnique({ + where: { + id: step.workflowId, + }, + select: { + userId: true, + steps: true, + }, + }); + + if (!userWorkflow || userWorkflow.userId !== user.id) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + if (isSMSAction(step.action)) { + const hasTeamPlan = (await ctx.prisma.membership.count({ where: { userId: user.id } })) > 0; + if (!hasTeamPlan) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "Team plan needed" }); + } + } + const booking = await ctx.prisma.booking.findFirst({ orderBy: { createdAt: "desc", @@ -919,7 +953,7 @@ export const workflowsRouter = router({ reminderBody, 0, template, - sender || SENDER_ID + senderID ); return { message: "Notification sent" }; } @@ -973,15 +1007,7 @@ export const workflowsRouter = router({ if (!eventTypeWorkflow) throw new TRPCError({ code: "UNAUTHORIZED", message: "This event type does not belong to the user" }); - // NOTE: This was unused - // const eventType = await ctx.prisma.eventType.findFirst({ - // where: { - // id: eventTypeId, - // }, - // }); - //check if event type is already active - const isActive = await ctx.prisma.workflowsOnEventTypes.findFirst({ where: { workflowId, From 688541923be52f0fe8b9951972b0297411244206 Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Thu, 15 Dec 2022 22:43:07 +0100 Subject: [PATCH 6/8] Save meeting url to metadata of bookings and add it to webhook payload (#5773) * small design fix for time unit select * save video cal url to booking in metaData * save video url when bookings are confirmed * fix e2e tests * remove not needed waitForNavigation * add metadata to booking created webhook payload * add migration * fix type error * adjust e2e tests * add bookingMetadataSchema * add missing change from merge * add new line to expected test result * update snapshot * fix e2e tests * Update confirm.ts Co-authored-by: CarinaWolli Co-authored-by: zomars --- apps/web/playwright/webhook.e2e.ts | 1 + .../webhookResponse--calcom-web.txt | 2 +- .../features/bookings/lib/handleNewBooking.ts | 13 ++++++++++--- .../components/TimeTimeUnitInput.tsx | 2 +- .../lib/server/queries/bookings/confirm.ts | 19 ++++++++++++------- packages/lib/test/builder.ts | 1 + .../migration.sql | 2 ++ packages/prisma/schema.prisma | 2 ++ packages/prisma/zod-utils.ts | 6 ++++++ 9 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 packages/prisma/migrations/20221129125935_add_metadata_to_booking/migration.sql diff --git a/apps/web/playwright/webhook.e2e.ts b/apps/web/playwright/webhook.e2e.ts index cf1c64b3f0..9c48421222 100644 --- a/apps/web/playwright/webhook.e2e.ts +++ b/apps/web/playwright/webhook.e2e.ts @@ -67,6 +67,7 @@ test("add webhook & test that creating an event triggers a webhook call", async body.payload.eventTypeId = dynamic; body.payload.videoCallData = dynamic; body.payload.appsStatus = dynamic; + body.payload.metadata.videoCallUrl = dynamic; // if we change the shape of our webhooks, we can simply update this by clicking `u` // console.log("BODY", body); diff --git a/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt b/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt index df45e9df6d..18700cb784 100644 --- a/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt +++ b/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt @@ -1 +1 @@ -{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between Nameless and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Nameless","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","videoCallData":"[redacted/dynamic]","appsStatus":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}} \ No newline at end of file +{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between Nameless and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Nameless","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","videoCallData":"[redacted/dynamic]","appsStatus":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{"videoCallUrl":"[redacted/dynamic]"},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}} \ No newline at end of file diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 438d6a69e5..880fc4dc24 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -835,6 +835,8 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) { evt.appsStatus = Object.values(calcAppsStatus); } + let videoCallUrl; + if (originalRescheduledBooking?.uid) { // Use EventManager to conditionally use all needed integrations. const updateManager = await eventManager.reschedule( @@ -869,9 +871,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) { metadata.conferenceData = updatedEvent.conferenceData; metadata.entryPoints = updatedEvent.entryPoints; handleAppsStatus(results, booking); + videoCallUrl = metadata.hangoutLink || videoCallUrl; } } - if (noEmail !== true) { await sendRescheduledEmails({ ...evt, @@ -893,6 +895,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) { results = createManager.results; referencesToCreate = createManager.referencesToCreate; + + videoCallUrl = evt.videoCallData && evt.videoCallData.url ? evt.videoCallData.url : null; + if (results.length > 0 && results.every((res) => !res.success)) { const error = { errorCode: "BookingCreatingMeetingFailed", @@ -909,6 +914,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) { metadata.conferenceData = results[0].createdEvent?.conferenceData; metadata.entryPoints = results[0].createdEvent?.entryPoints; handleAppsStatus(results, booking); + videoCallUrl = metadata.hangoutLink || videoCallUrl; } if (noEmail !== true) { await sendScheduledEmails({ @@ -945,7 +951,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) { } log.debug(`Booking ${organizerUser.username} completed`); - + const metadata = videoCallUrl ? { videoCallUrl } : undefined; if (isConfirmedByDefault) { const eventTrigger: WebhookTriggerEvents = rescheduleUid ? WebhookTriggerEvents.BOOKING_RESCHEDULED @@ -1001,7 +1007,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) { ...eventTypeInfo, bookingId, rescheduleUid, - metadata: reqBody.metadata, + metadata: { ...metadata, ...reqBody.metadata }, eventTypeId, status: "ACCEPTED", smsReminderNumber: booking?.smsReminderNumber || undefined, @@ -1043,6 +1049,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) { uid: booking.uid, }, data: { + metadata, references: { createMany: { data: referencesToCreate, diff --git a/packages/features/ee/workflows/components/TimeTimeUnitInput.tsx b/packages/features/ee/workflows/components/TimeTimeUnitInput.tsx index 411d588535..b9a0a01e71 100644 --- a/packages/features/ee/workflows/components/TimeTimeUnitInput.tsx +++ b/packages/features/ee/workflows/components/TimeTimeUnitInput.tsx @@ -42,7 +42,7 @@ export const TimeTimeUnitInput = (props: Props) => { +
      + {form.formState.errors.steps && form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo && (

      {form.formState?.errors?.steps[step.stepNumber - 1]?.sendTo?.message || ""}

      )} + {numberVerified ? ( +
      + {t("number_verified")} +
      + ) : ( + <> +
      + { + setVerificationCode(e.target.value); + }} + required + /> + +
      + + )} )} {isSenderIdNeeded && ( @@ -525,6 +616,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { className="mt-7 w-full" onClick={() => { let isEmpty = false; + if (!form.getValues(`steps.${step.stepNumber - 1}.sendTo`) && isPhoneNumberNeeded) { form.setError(`steps.${step.stepNumber - 1}.sendTo`, { type: "custom", @@ -532,6 +624,13 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { }); isEmpty = true; } + + if (!numberVerified && isPhoneNumberNeeded) { + form.setError(`steps.${step.stepNumber - 1}.sendTo`, { + type: "custom", + message: t("not_verified"), + }); + } if ( form.getValues(`steps.${step.stepNumber - 1}.template`) === WorkflowTemplates.CUSTOM ) { @@ -576,7 +675,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { ? false : true; - if (isPhoneNumberNeeded && isNumberValid && !isEmpty) { + if (isPhoneNumberNeeded && isNumberValid && !isEmpty && numberVerified) { setConfirmationDialogOpen(true); } } diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts index e104b41c88..8639f4a6db 100644 --- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts +++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts @@ -52,7 +52,9 @@ export const scheduleWorkflowReminders = async ( step.reminderBody || "", step.id, step.template, - step.sender || SENDER_ID + step.sender || SENDER_ID, + workflow.userId, + step.numberVerificationPending ); } else if ( step.action === WorkflowActions.EMAIL_ATTENDEE || @@ -121,7 +123,9 @@ export const sendCancelledReminders = async ( step.reminderBody || "", step.id, step.template, - step.sender || SENDER_ID + step.sender || SENDER_ID, + workflow.userId, + step.numberVerificationPending ); } else if ( step.action === WorkflowActions.EMAIL_ATTENDEE || diff --git a/packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts b/packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts index 6119550974..35cf2616f5 100644 --- a/packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts +++ b/packages/features/ee/workflows/lib/reminders/smsProviders/twilioProvider.ts @@ -49,3 +49,26 @@ export const cancelSMS = async (referenceId: string) => { assertTwilio(twilio); await twilio.messages(referenceId).update({ status: "canceled" }); }; + +export const sendVerificationCode = async (phoneNumber: string) => { + assertTwilio(twilio); + if (process.env.TWILIO_VERIFY_SID) { + await twilio.verify + .services(process.env.TWILIO_VERIFY_SID) + .verifications.create({ to: phoneNumber, channel: "sms" }); + } +}; + +export const verifyNumber = async (phoneNumber: string, code: string) => { + assertTwilio(twilio); + if (process.env.TWILIO_VERIFY_SID) { + try { + const verification_check = await twilio.verify.v2 + .services(process.env.TWILIO_VERIFY_SID) + .verificationChecks.create({ to: phoneNumber, code: code }); + return verification_check.status; + } catch (e) { + return "failed"; + } + } +}; diff --git a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts index ffea491ba2..611d70782b 100644 --- a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts @@ -50,7 +50,9 @@ export const scheduleSMSReminder = async ( message: string, workflowStepId: number, template: WorkflowTemplates, - sender: string + sender: string, + userId: number, + isVerificationPending = false ) => { const { startTime, endTime } = evt; const uid = evt.uid as string; @@ -60,6 +62,18 @@ export const scheduleSMSReminder = async ( const senderID = getSenderId(reminderPhone, sender); + //SMS_ATTENDEE action does not need to be verified + //isVerificationPending is from all already existing workflows (once they edit their workflow, they will also have to verify the number) + async function getIsNumberVerified() { + if (action === WorkflowActions.SMS_ATTENDEE) return true; + const verifiedNumber = await prisma.verifiedNumber.findFirst({ + where: { userId, phoneNumber: reminderPhone || "" }, + }); + if (!!verifiedNumber) return true; + return isVerificationPending; + } + const isNumberVerified = await getIsNumberVerified(); + if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT) { scheduledDate = timeSpan.time && timeUnit ? dayjs(startTime).subtract(timeSpan.time, timeUnit) : null; } else if (triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) { @@ -93,7 +107,7 @@ export const scheduleSMSReminder = async ( break; } - if (message.length > 0 && reminderPhone) { + if (message.length > 0 && reminderPhone && isNumberVerified) { //send SMS when event is booked/cancelled/rescheduled if ( triggerEvent === WorkflowTriggerEvents.NEW_EVENT || diff --git a/packages/features/ee/workflows/lib/reminders/verifyPhoneNumber.ts b/packages/features/ee/workflows/lib/reminders/verifyPhoneNumber.ts new file mode 100644 index 0000000000..4f911cd59a --- /dev/null +++ b/packages/features/ee/workflows/lib/reminders/verifyPhoneNumber.ts @@ -0,0 +1,22 @@ +import prisma from "@calcom/prisma"; + +import * as twilio from "./smsProviders/twilioProvider"; + +export const sendVerificationCode = async (phoneNumber: string) => { + return twilio.sendVerificationCode(phoneNumber); +}; + +export const verifyPhoneNumber = async (phoneNumber: string, code: string, userId: number) => { + const verificationStatus = await twilio.verifyNumber(phoneNumber, code); + + if (verificationStatus === "approved") { + await prisma.verifiedNumber.create({ + data: { + userId, + phoneNumber, + }, + }); + return true; + } + return false; +}; diff --git a/packages/features/ee/workflows/pages/workflow.tsx b/packages/features/ee/workflows/pages/workflow.tsx index 0ddb707368..67f1d9df7e 100644 --- a/packages/features/ee/workflows/pages/workflow.tsx +++ b/packages/features/ee/workflows/pages/workflow.tsx @@ -104,6 +104,8 @@ function WorkflowPage() { } ); + const { data: verifiedNumbers } = trpc.viewer.workflows.getVerifiedNumbers.useQuery(); + useEffect(() => { if (workflow && !isLoading) { setSelectedEventTypes( @@ -175,6 +177,7 @@ function WorkflowPage() { handleSubmit={async (values) => { let activeOnEventTypeIds: number[] = []; let isEmpty = false; + let isVerified = true; values.steps.forEach((step) => { const isSMSAction = @@ -199,9 +202,22 @@ function WorkflowPage() { step.emailSubject = translateVariablesToEnglish(step.emailSubject, { locale: i18n.language, t }); } isEmpty = !isEmpty ? isBodyEmpty : isEmpty; + + //check if phone number is verified + if ( + step.action === WorkflowActions.SMS_NUMBER && + !verifiedNumbers?.find((verifiedNumber) => verifiedNumber.phoneNumber === step.sendTo) + ) { + isVerified = false; + + form.setError(`steps.${step.stepNumber - 1}.sendTo`, { + type: "custom", + message: t("not_verified"), + }); + } }); - if (!isEmpty) { + if (!isEmpty && isVerified) { if (values.activeOn) { activeOnEventTypeIds = values.activeOn.map((option) => { return parseInt(option.value, 10); @@ -216,6 +232,7 @@ function WorkflowPage() { time: values.time || null, timeUnit: values.timeUnit || null, }); + utils.viewer.workflows.getVerifiedNumbers.invalidate(); } }}> { + const { phoneNumber } = input; + return sendVerificationCode(phoneNumber); + }), + verifyPhoneNumber: authedProcedure + .input( + z.object({ + phoneNumber: z.string(), + code: z.string(), + }) + ) + .mutation(async ({ ctx, input }) => { + const { phoneNumber, code } = input; + const { user } = ctx; + const verifyStatus = await verifyPhoneNumber(phoneNumber, code, user.id); + return verifyStatus; + }), + getVerifiedNumbers: authedProcedure.query(async ({ ctx }) => { + const { user } = ctx; + const verifiedNumbers = await ctx.prisma.verifiedNumber.findMany({ + where: { + userId: user.id, + }, + }); + + return verifiedNumbers; + }), getWorkflowActionOptions: authedProcedure.query(async ({ ctx }) => { const userId = ctx.user.id; const hasTeamPlan = (await ctx.prisma.membership.count({ where: { userId } })) > 0; diff --git a/packages/ui/form/PhoneInput.tsx b/packages/ui/form/PhoneInput.tsx index b02656a3a2..e8e00f5867 100644 --- a/packages/ui/form/PhoneInput.tsx +++ b/packages/ui/form/PhoneInput.tsx @@ -9,15 +9,22 @@ export type PhoneInputProps = Props< required: boolean; }, FormValues ->; +> & { onChange?: (e: any) => void }; -function PhoneInput({ control, name, className, ...rest }: PhoneInputProps) { +function PhoneInput({ + control, + name, + className, + onChange, + ...rest +}: PhoneInputProps) { return ( Date: Thu, 15 Dec 2022 15:18:40 -0700 Subject: [PATCH 8/8] Bump loader-utils from 1.4.0 to 1.4.2 (#6045) Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.2. - [Release notes](https://github.com/webpack/loader-utils/releases) - [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md) - [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.2) --- updated-dependencies: - dependency-name: loader-utils dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 191ccaa51d..76ec0d9cae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7903,16 +7903,11 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@16.9.1", "@types/node@>=4.0", "@types/node@>=8.1.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0": +"@types/node@*", "@types/node@16.9.1", "@types/node@>=4.0", "@types/node@>=8.1.0", "@types/node@^12.12.54", "@types/node@^12.12.6", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0": version "16.9.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708" integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g== -"@types/node@^12.12.54", "@types/node@^12.12.6": - version "12.20.55" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" - integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== - "@types/nodemailer@^6.4.5": version "6.4.5" resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.5.tgz#09011ac73259245475d1688e4ba101860567dc39" @@ -17827,9 +17822,9 @@ loader-runner@^4.2.0: integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== loader-utils@^1.2.3: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + version "1.4.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" + integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" @@ -19117,11 +19112,16 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +minimist@^1.2.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" @@ -26914,6 +26914,13 @@ zustand@^4.0.0: dependencies: use-sync-external-store "1.2.0" +zustand@^4.1.4: + version "4.1.5" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.1.5.tgz#7402b511f5b23ccb0f9ba6d20ae01ec817e16eb6" + integrity sha512-PsdRT8Bvq22Yyh1tvpgdHNE7OAeFKqJXUxtJvj1Ixw2B9O2YZ1M34ImQ+xyZah4wZrR4lENMoDUutKPpyXCQ/Q== + dependencies: + use-sync-external-store "1.2.0" + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"