diff --git a/packages/app-builder/src/components/Cases/CaseDecisions.tsx b/packages/app-builder/src/components/Cases/CaseDecisions.tsx index 7773582d..51d2bb7b 100644 --- a/packages/app-builder/src/components/Cases/CaseDecisions.tsx +++ b/packages/app-builder/src/components/Cases/CaseDecisions.tsx @@ -11,6 +11,7 @@ import { type RuleExecution, } from '@app-builder/models/decision'; import { type OperatorFunction } from '@app-builder/models/editable-operators'; +import { type LicenseEntitlements } from '@app-builder/models/license'; import { type RuleSnoozeWithRuleId } from '@app-builder/models/rule-snooze'; import { type ScenarioIterationRule } from '@app-builder/models/scenario-iteration-rule'; import { ReviewDecisionModal } from '@app-builder/routes/ressources+/cases+/review-decision'; @@ -64,19 +65,18 @@ interface DecisionsDetail { export function CaseDecisions({ decisions, + featureAccess, + entitlements, caseDecisionsPromise, }: { decisions: Decision[]; + featureAccess: { + isReadSnoozeAvailable: boolean; + isCreateSnoozeAvailable: boolean; + }; + entitlements: LicenseEntitlements; caseDecisionsPromise: Promise< - [ - TableModel[], - CustomList[], - DecisionsDetail[], - { - isReadSnoozeAvailable: boolean; - isCreateSnoozeAvailable: boolean; - }, - ] + [TableModel[], CustomList[], DecisionsDetail[]] >; }) { const { t } = useTranslation(casesI18n); @@ -151,12 +151,7 @@ export function CaseDecisions({ }> - {([ - dataModel, - customLists, - decisionsDetail, - featureAccess, - ]) => { + {([dataModel, customLists, decisionsDetail]) => { return ( ); @@ -273,12 +269,14 @@ function DecisionDetail({ decisionsDetail, dataModel, customLists, + entitlements, featureAccess, }: { decision: Decision; decisionsDetail: DecisionsDetail[]; dataModel: TableModel[]; customLists: CustomList[]; + entitlements: LicenseEntitlements; featureAccess: { isReadSnoozeAvailable: boolean; isCreateSnoozeAvailable: boolean; @@ -365,11 +363,11 @@ function DecisionDetail({ }} /> - {featureAccess.isReadSnoozeAvailable && - pivotValues.length > 0 ? ( + {pivotValues.length > 0 ? ( - ) : isCreateSnoozeAvailable ? ( - - - ) : ( - - {t('cases:case_detail.add_rule_snooze.no_access')} - + match(entitlements.ruleSnoozes) + .with('allowed', () => + isCreateSnoozeAvailable ? ( + + + + ) : ( + + {t('cases:case_detail.add_rule_snooze.no_access')} + + ), + ) + .with('restricted', () => ( + + )) + .with('test', () => + isCreateSnoozeAvailable ? ( + + + + ) : ( + + {t('cases:case_detail.add_rule_snooze.no_access')} + + ), + ) + .exhaustive() )} ); diff --git a/packages/app-builder/src/components/GithubBanner.tsx b/packages/app-builder/src/components/GithubBanner.tsx new file mode 100644 index 00000000..20ac9199 --- /dev/null +++ b/packages/app-builder/src/components/GithubBanner.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Icon } from 'ui-icons'; + +export const GithubBanner = () => { + const [isShown, setVisibility] = useState( + (localStorage.getItem('show-github-banner') as 'true' | 'false') ?? 'true', + ); + + const { t } = useTranslation(['common']); + + return isShown === 'true' ? ( +
+ + { + localStorage.setItem('show-github-banner', 'false'); + setVisibility('false'); + }} + /> +
+ ) : null; +}; diff --git a/packages/app-builder/src/components/Navigation.tsx b/packages/app-builder/src/components/Navigation.tsx index 2351bc89..6cbe6bc8 100644 --- a/packages/app-builder/src/components/Navigation.tsx +++ b/packages/app-builder/src/components/Navigation.tsx @@ -13,6 +13,7 @@ export interface SidebarLinkProps { Icon: (props: Omit) => JSX.Element; labelTKey: ParseKeys<['navigation']>; to: string; + children?: React.ReactNode; } export const sidebarLink = cva( @@ -30,7 +31,12 @@ export const sidebarLink = cva( }, ); -export function SidebarLink({ Icon, labelTKey, to }: SidebarLinkProps) { +export function SidebarLink({ + Icon, + labelTKey, + to, + children, +}: SidebarLinkProps) { const { t } = useTranslation(navigationI18n); return ( @@ -39,6 +45,7 @@ export function SidebarLink({ Icon, labelTKey, to }: SidebarLinkProps) { {t(labelTKey)} + {children} ); } diff --git a/packages/app-builder/src/components/Nudge.tsx b/packages/app-builder/src/components/Nudge.tsx new file mode 100644 index 00000000..d31b6141 --- /dev/null +++ b/packages/app-builder/src/components/Nudge.tsx @@ -0,0 +1,77 @@ +import { + Hovercard, + HovercardAnchor, + HovercardProvider, +} from '@ariakit/react/hovercard'; +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import { CtaClassName } from 'ui-design-system'; +import { Icon } from 'ui-icons'; + +type NudgeProps = { + content: string; + className?: string; + link?: string; + kind?: 'test' | 'restricted'; +}; + +export const Nudge = ({ + content, + link, + className, + kind = 'restricted', +}: NudgeProps) => { + const { t } = useTranslation(['scenarios', 'common']); + + return ( + + + + + + {t('common:premium')} +
+

{content}

+ {link ? ( + + {t('common:check_on_docs')} + + ) : null} +
+ + {t('common:upgrade')} + +
+
+ ); +}; diff --git a/packages/app-builder/src/components/Page.tsx b/packages/app-builder/src/components/Page.tsx index 9d5ce969..1a2fdbcc 100644 --- a/packages/app-builder/src/components/Page.tsx +++ b/packages/app-builder/src/components/Page.tsx @@ -2,9 +2,12 @@ import { Link, useNavigate } from '@remix-run/react'; import { cva } from 'class-variance-authority'; import clsx from 'clsx'; import { useTranslation } from 'react-i18next'; +import { ClientOnly } from 'remix-utils/client-only'; import { Tooltip } from 'ui-design-system'; import { Icon } from 'ui-icons'; +import { GithubBanner } from './GithubBanner'; + function PageMain({ className, ...props }: React.ComponentProps<'div'>) { return (
) { +function PageHeader({ + className, + children, + ...props +}: React.ComponentProps<'div'>) { return (
+ > + {() => } + {children} +
); } diff --git a/packages/app-builder/src/components/Scenario/TestRun/TestRunNudge.tsx b/packages/app-builder/src/components/Scenario/TestRun/TestRunNudge.tsx new file mode 100644 index 00000000..914a1ae7 --- /dev/null +++ b/packages/app-builder/src/components/Scenario/TestRun/TestRunNudge.tsx @@ -0,0 +1,34 @@ +import { CalloutV2 } from '@app-builder/components/Callout'; +import { Nudge } from '@app-builder/components/Nudge'; +import { useTranslation } from 'react-i18next'; +import { Button } from 'ui-design-system'; +import { Icon } from 'ui-icons'; + +export const TestRunNudge = () => { + const { t } = useTranslation(['scenarios']); + + return ( +
+

+ {t('scenarios:home.testrun')} +

+
+
+ + +
+ {t('scenarios:testrun.description')} +
+
+ +
+
+
+ ); +}; diff --git a/packages/app-builder/src/components/Scenario/Workflow/WorkflowNudge.tsx b/packages/app-builder/src/components/Scenario/Workflow/WorkflowNudge.tsx new file mode 100644 index 00000000..281b11e0 --- /dev/null +++ b/packages/app-builder/src/components/Scenario/Workflow/WorkflowNudge.tsx @@ -0,0 +1,35 @@ +import { CalloutV2 } from '@app-builder/components/Callout'; +import { Nudge } from '@app-builder/components/Nudge'; +import { useTranslation } from 'react-i18next'; +import { Button } from 'ui-design-system'; +import { Icon } from 'ui-icons'; + +export const WorkflowNudge = () => { + const { t } = useTranslation(['scenarios', 'workflows']); + + return ( +
+

+ {t('scenarios:home.workflow')} +

+
+
+ + +
+ {t('scenarios:home.workflow_description')} +
+
+ +
+
+
+ ); +}; diff --git a/packages/app-builder/src/infra/license-api.ts b/packages/app-builder/src/infra/license-api.ts index f07b0ea2..f3afe852 100644 --- a/packages/app-builder/src/infra/license-api.ts +++ b/packages/app-builder/src/infra/license-api.ts @@ -1,22 +1,54 @@ -import { licenseApi } from 'marble-api'; +import { + fetchWithAuthMiddleware, + licenseApi, + type TokenService, +} from 'marble-api'; import * as R from 'remeda'; import { type FunctionKeys } from 'typescript-utils'; +//TODO: To remove + export type LicenseApi = { [P in FunctionKeys]: (typeof licenseApi)[P]; }; -function getLicenseAPIClient({ baseUrl }: { baseUrl: string }): LicenseApi { +export type GetLicenseAPIClientWithAuth = ( + tokenService: TokenService, +) => LicenseApi; + +function getLicenseAPIClient({ + tokenService, + baseUrl, +}: { + baseUrl: string; + tokenService?: TokenService; +}): LicenseApi { + const fetch = tokenService + ? fetchWithAuthMiddleware({ + tokenService, + getAuthorizationHeader: (token) => ({ + name: 'Authorization', + value: `Bearer ${token}`, + }), + }) + : undefined; + const { defaults, servers, ...api } = licenseApi; //@ts-expect-error can't infer args return R.mapValues(api, (value) => (...args) => { // @ts-expect-error can't infer args - return value(...args, { baseUrl }); + return value(...args, { fetch, baseUrl }); }); } -export function initializeLicenseAPIClient({ baseUrl }: { baseUrl: string }) { + +export function initializeLicenseAPIClient({ baseUrl }: { baseUrl: string }): { + licenseApi: LicenseApi; + getLicenseAPIClientWithAuth: GetLicenseAPIClientWithAuth; +} { return { - licenseAPIClient: getLicenseAPIClient({ baseUrl }), + licenseApi: getLicenseAPIClient({ baseUrl }), + getLicenseAPIClientWithAuth: (tokenService: TokenService) => + getLicenseAPIClient({ tokenService, baseUrl }), }; } diff --git a/packages/app-builder/src/locales/ar/cases.json b/packages/app-builder/src/locales/ar/cases.json index 13ba7426..bd2bc682 100644 --- a/packages/app-builder/src/locales/ar/cases.json +++ b/packages/app-builder/src/locales/ar/cases.json @@ -114,5 +114,6 @@ "case_detail.history.event_detail.rule_snooze_created.validity": "صحة", "case_detail.pivot_values.snooze_from_to": "من {{from}} ← إلى {{to}}", "case_detail.rules_execution.show_hit_only": "عرض الضربة فقط", - "case_detail.trigger_object": "كائن الزناد" + "case_detail.trigger_object": "كائن الزناد", + "case_detail.add_rule_snooze.nudge": "قم بتأجيل القاعدة لتعطيلها مؤقتًا لتجنب النتائج الإيجابية الخاطئة" } diff --git a/packages/app-builder/src/locales/ar/common.json b/packages/app-builder/src/locales/ar/common.json index 284e10ed..299460a9 100644 --- a/packages/app-builder/src/locales/ar/common.json +++ b/packages/app-builder/src/locales/ar/common.json @@ -58,5 +58,9 @@ "show": "يعرض", "from_to": "من {{start_date}} إلى {{end_date}}", "live": "يعيش", - "errors.data.duplicate_test_run": "التشغيل التجريبي نشط بالفعل لهذا السيناريو" + "errors.data.duplicate_test_run": "التشغيل التجريبي نشط بالفعل لهذا السيناريو", + "check_on_docs": "تعرف على المزيد حول وثائقنا", + "github_banner": "هل أعجبك تطبيق ماربل؟ \nادعم مجهود المصدر المفتوح من خلال إعطائنا نجمة", + "premium": "ميزة متميزة", + "upgrade": "قم بالترقية الآن" } diff --git a/packages/app-builder/src/locales/ar/navigation.json b/packages/app-builder/src/locales/ar/navigation.json index 014166ae..fd34e813 100644 --- a/packages/app-builder/src/locales/ar/navigation.json +++ b/packages/app-builder/src/locales/ar/navigation.json @@ -28,5 +28,6 @@ "expand": "يوسع", "transfercheck.alerts": "التنبيهات", "transfercheck.alerts.received": "التنبيهات المستلمة", - "transfercheck.alerts.sent": "التنبيهات المرسلة" + "transfercheck.alerts.sent": "التنبيهات المرسلة", + "analytics.nudge": "تحليل قراراتك" } diff --git a/packages/app-builder/src/locales/ar/scenarios.json b/packages/app-builder/src/locales/ar/scenarios.json index 1751ba04..bc5c9696 100644 --- a/packages/app-builder/src/locales/ar/scenarios.json +++ b/packages/app-builder/src/locales/ar/scenarios.json @@ -367,5 +367,6 @@ "edit_string_template.title": "قالب السلسلة", "edit_string_template.variables.label": "المتغيرات", "edit_string_template.description": "قم بإنشاء نص بقيم الحقول (معرفة المزيد)", - "edit_operand.data_type.string.field_placeholder": "حدد حقل سلسلة..." + "edit_operand.data_type.string.field_placeholder": "حدد حقل سلسلة...", + "testrun.nudge": "قارن الإصدار المباشر بإصدار جديد أو أقدم دون المساس بالبيانات المباشرة" } diff --git a/packages/app-builder/src/locales/ar/settings.json b/packages/app-builder/src/locales/ar/settings.json index 2a66cf34..1e996390 100644 --- a/packages/app-builder/src/locales/ar/settings.json +++ b/packages/app-builder/src/locales/ar/settings.json @@ -107,5 +107,7 @@ "scenario_default_timezone.more_results_one": "{{count}} منطقة زمنية إضافية.\nقم بتحسين البحث لرؤيتهم.", "scenario_default_timezone.more_results_other": "{{count}} مناطق زمنية إضافية.\nقم بتحسين البحث لرؤيتهم.", "scenario_default_timezone.no_match": "لا توجد منطقة زمنية تطابق بحثك", - "scenario_default_timezone.not_set": "لم يتم ضبطه" + "scenario_default_timezone.not_set": "لم يتم ضبطه", + "users.role.nudge": "قم بتعيين الأدوار وتبسيط سير عمل فريقك بسهولة", + "webhooks.nudge": "استخدم Webhooks لتلقي إشعارات في الوقت الفعلي عند حدوث أحداث في مثيل Marble الخاص بك" } diff --git a/packages/app-builder/src/locales/ar/workflows.json b/packages/app-builder/src/locales/ar/workflows.json index 44d6f784..cc2588a4 100644 --- a/packages/app-builder/src/locales/ar/workflows.json +++ b/packages/app-builder/src/locales/ar/workflows.json @@ -53,5 +53,6 @@ "toast.success.create_workflow": " تم إعداد سير العمل بنجاح.", "toast.success.delete_workflow": "تم حذف سير العمل بنجاح.", "detail_panel.add_to_case_if_possible.case_name.label": "اسم الحالة إذا لم يتم العثور على حالة مطابقة", - "detail_panel.create_case.case_name.label": "اسم الحالة" + "detail_panel.create_case.case_name.label": "اسم الحالة", + "nudge": "اسمح لـ Marble بالتعامل مع المهام المتكررة من خلال سير العمل حتى تتمكن من التركيز على ما يهم حقًا" } diff --git a/packages/app-builder/src/locales/en/cases.json b/packages/app-builder/src/locales/en/cases.json index 5ada594e..c0415d47 100644 --- a/packages/app-builder/src/locales/en/cases.json +++ b/packages/app-builder/src/locales/en/cases.json @@ -87,6 +87,7 @@ "case_detail.add_a_comment.placeholder": "Write a note", "case_detail.add_a_comment.post": "post a comment", "case_detail.add_rule_snooze.title": "Snooze a rule", + "case_detail.add_rule_snooze.nudge": "Snooze a rule to disable it temporarily in order to avoid false positive", "case_detail.add_rule_snooze.callout": "Snoozing a rule for a specific pivot value will prevent it from triggering for a certain amount of time (learn more)", "case_detail.add_rule_snooze.comment.label": "Comment", "case_detail.add_rule_snooze.comment.placeholder": "Explain why you are snoozing this rule", diff --git a/packages/app-builder/src/locales/en/common.json b/packages/app-builder/src/locales/en/common.json index 851bf1c5..84cc99c8 100644 --- a/packages/app-builder/src/locales/en/common.json +++ b/packages/app-builder/src/locales/en/common.json @@ -4,6 +4,8 @@ "false": "False", "from_to": "From {{start_date}} to {{end_date}}", "live": "Live", + "premium": "Premium Feature", + "upgrade": "Upgrade now", "auth.logout": "Log out", "search": "Search", "error_one": "{{count}} error", @@ -28,6 +30,8 @@ "errors.firebase_network_error": "A network error occured. Try to log in again.", "errors.too_many_requests": "Too many requests. Please try again later.", "cancel": "Cancel", + "check_on_docs": "Learn more on our documentation", + "github_banner": "Do you like the Marble App ? Support the Open Source Effort by giving us a star on", "save": "Save", "delete": "Delete", "close": "Close", diff --git a/packages/app-builder/src/locales/en/navigation.json b/packages/app-builder/src/locales/en/navigation.json index 7f7f7214..024dea29 100644 --- a/packages/app-builder/src/locales/en/navigation.json +++ b/packages/app-builder/src/locales/en/navigation.json @@ -4,6 +4,7 @@ "lists": "Lists", "decisions": "Decisions", "analytics": "Analytics", + "analytics.nudge": "Analyze your decisions & rules triggers through a beautiful panel", "history": "History", "scenario.trigger": "Trigger", "scenario.rules": "Rules", diff --git a/packages/app-builder/src/locales/en/scenarios.json b/packages/app-builder/src/locales/en/scenarios.json index 8ce62e1a..0449a49a 100644 --- a/packages/app-builder/src/locales/en/scenarios.json +++ b/packages/app-builder/src/locales/en/scenarios.json @@ -48,6 +48,7 @@ "trigger.trigger_object.title": "Trigger conditions", "trigger.trigger_object.callout": "Determines whether the scenario is relevant for each trigger object (learn more)", "trigger.trigger_manual_execution.button": "Launch now", + "testrun.nudge": "Compare a live version with a new one or an older one without compromising live data", "testrun.home": "List of comparisons", "testrun.not_allowed": "No live version or alternative version available", "testrun.description": "Test and compare a scenario version with a live one", diff --git a/packages/app-builder/src/locales/en/settings.json b/packages/app-builder/src/locales/en/settings.json index 57ea8a16..553fdec6 100644 --- a/packages/app-builder/src/locales/en/settings.json +++ b/packages/app-builder/src/locales/en/settings.json @@ -1,5 +1,6 @@ { "users": "Users", + "users.role.nudge": "Assign roles and streamline your team's workflow with ease", "users.new_user": "New user", "users.new_user.create": "Create new user", "users.update_user": "Update user", @@ -76,6 +77,7 @@ "tags.cases": "Cases", "api": "API", "webhooks": "Webhooks", + "webhooks.nudge": "Use Webhooks to receive realt-time notifications whenever events happen in your Marble instance", "webhook_details": "Webhook details", "webhook_secrets": "Webhook secrets", "webhooks.new_webhook": "New webhook", diff --git a/packages/app-builder/src/locales/en/workflows.json b/packages/app-builder/src/locales/en/workflows.json index ef346bca..f1ec808b 100644 --- a/packages/app-builder/src/locales/en/workflows.json +++ b/packages/app-builder/src/locales/en/workflows.json @@ -1,4 +1,5 @@ { + "nudge": "Let Marble handle repetitive tasks through workflows so you can focus on what truly matters", "trigger": "trigger", "trigger_node.decision_created.title": "Decision created", "trigger_node.decision_created.entity": "decision", diff --git a/packages/app-builder/src/locales/fr/cases.json b/packages/app-builder/src/locales/fr/cases.json index dbca4916..461cde3c 100644 --- a/packages/app-builder/src/locales/fr/cases.json +++ b/packages/app-builder/src/locales/fr/cases.json @@ -114,5 +114,6 @@ "case_detail.history.event_detail.rule_snooze_created.validity": "validité", "case_detail.pivot_values.snooze_from_to": "De {{de}} -> À {{à}}", "case_detail.rules_execution.show_hit_only": "Afficher uniquement les hits", - "case_detail.trigger_object": "Objet déclencheur" + "case_detail.trigger_object": "Objet déclencheur", + "case_detail.add_rule_snooze.nudge": "Désactivez temporairement une règle afin d'éviter les faux positifs" } diff --git a/packages/app-builder/src/locales/fr/common.json b/packages/app-builder/src/locales/fr/common.json index e48ae464..fd91dd73 100644 --- a/packages/app-builder/src/locales/fr/common.json +++ b/packages/app-builder/src/locales/fr/common.json @@ -58,5 +58,9 @@ "validation_error_other": "{{count}} erreurs de validation", "from_to": "Du {{start_date}} au {{end_date}}", "live": "En direct", - "errors.data.duplicate_test_run": "Un test est déjà actif pour ce scénario" + "errors.data.duplicate_test_run": "Un test est déjà actif pour ce scénario", + "check_on_docs": "En savoir plus sur notre documentation", + "github_banner": "Aimez-vous l'application Marble ? Dites le nous en nous attribuant une étoile sur", + "premium": "Fonctionnalité Premium", + "upgrade": "Mettre à niveau maintenant" } diff --git a/packages/app-builder/src/locales/fr/navigation.json b/packages/app-builder/src/locales/fr/navigation.json index a25feb26..8ed6d229 100644 --- a/packages/app-builder/src/locales/fr/navigation.json +++ b/packages/app-builder/src/locales/fr/navigation.json @@ -28,5 +28,6 @@ "transfercheck.alerts": "Alertes", "transfercheck.alerts.received": "Alertes reçues", "transfercheck.alerts.sent": "Alertes envoyées", - "transfercheck.transfers": "Transferts" + "transfercheck.transfers": "Transferts", + "analytics.nudge": "Analysez vos décisions à travers un tableau de bord graphique" } diff --git a/packages/app-builder/src/locales/fr/scenarios.json b/packages/app-builder/src/locales/fr/scenarios.json index 33ee5e0b..efdc37c0 100644 --- a/packages/app-builder/src/locales/fr/scenarios.json +++ b/packages/app-builder/src/locales/fr/scenarios.json @@ -367,5 +367,6 @@ "edit_string_template.title": "Modèle de texte", "edit_string_template.variables.label": "Variables", "edit_string_template.description": "Créer un texte avec des valeurs de champs (en savoir plus)", - "edit_operand.data_type.string.field_placeholder": "Sélectionnez un champ texte..." + "edit_operand.data_type.string.field_placeholder": "Sélectionnez un champ texte...", + "testrun.nudge": "Comparez une version en direct avec une nouvelle ou une ancienne sans compromettre les données en direct" } diff --git a/packages/app-builder/src/locales/fr/settings.json b/packages/app-builder/src/locales/fr/settings.json index 61049f9c..5515fc2f 100644 --- a/packages/app-builder/src/locales/fr/settings.json +++ b/packages/app-builder/src/locales/fr/settings.json @@ -107,5 +107,7 @@ "scenenario_execution": "Paramètres d'exécution du scénario", "scenario_default_timezone.more_results_one": "{{count}} fuseau horaire supplémentaire.\n\nAffinez votre recherche pour les voir.", "scenario_default_timezone.more_results_other": "{{count}} fuseaux horaires supplémentaires.\n\nAffinez votre recherche pour les voir.", - "scenario_default_timezone.not_set": "Non défini" + "scenario_default_timezone.not_set": "Non défini", + "users.role.nudge": "Attribuez des rôles et rationalisez facilement le flux de travail de votre équipe", + "webhooks.nudge": "Utilisez des Webhooks pour recevoir des notifications en temps réel chaque fois que des événements se produisent dans votre instance Marble" } diff --git a/packages/app-builder/src/locales/fr/workflows.json b/packages/app-builder/src/locales/fr/workflows.json index 5960f6a3..85abac78 100644 --- a/packages/app-builder/src/locales/fr/workflows.json +++ b/packages/app-builder/src/locales/fr/workflows.json @@ -53,5 +53,6 @@ "trigger_node.decision_created.entity": "décision", "trigger_node.decision_created.title": "Décision créée", "detail_panel.add_to_case_if_possible.case_name.label": "Nom de l'investigation si aucune investigation correspondante n'est trouvée", - "detail_panel.create_case.case_name.label": "Nom de l'investigation" + "detail_panel.create_case.case_name.label": "Nom de l'investigation", + "nudge": "Laissez Marble gérer les tâches répétitives via les workflow afin que vous puissiez vous concentrer sur ce qui compte vraiment" } diff --git a/packages/app-builder/src/models/license.ts b/packages/app-builder/src/models/license.ts index 501491f4..b8e24da0 100644 --- a/packages/app-builder/src/models/license.ts +++ b/packages/app-builder/src/models/license.ts @@ -1,33 +1,22 @@ -import { type LicenseValidationDto } from 'marble-api/generated/license-api'; +import { + type FeatureAccessDto, + type LicenseEntitlementsDto, +} from 'marble-api/generated/license-api'; export interface LicenseEntitlements { - sso: boolean; - workflows: boolean; - analytics: boolean; - dataEnrichment: boolean; - userRoles: boolean; - webhooks: boolean; - ruleSnoozes: boolean; + workflows: FeatureAccessDto; + analytics: FeatureAccessDto; + userRoles: FeatureAccessDto; + webhooks: FeatureAccessDto; + ruleSnoozes: FeatureAccessDto; + testRun: FeatureAccessDto; + sanctions: FeatureAccessDto; } -export type LicenseValidationCode = - | 'VALID' - | 'EXPIRED' - | 'NOT_FOUND' - | 'OVERDUE' - | 'SUSPENDED'; - -export interface LicenseValidation { - code: LicenseValidationCode; - entitlements: LicenseEntitlements; -} - -export function adaptLicenseValidation( - dto: LicenseValidationDto, -): LicenseValidation { +export function adaptLicenseEntitlements( + dto: LicenseEntitlementsDto, +): LicenseEntitlements { return { - code: dto.license_validation_code, - /** * When adding a new entitlement, there is a "chicken egg" problem. * In non dev environments, the entitlements are fetched from the backend. @@ -37,16 +26,12 @@ export function adaptLicenseValidation( * To solve this, adapt the dto to the new entitlement using an existing one (ex: dto.license_entitlements.webhooks) * At the moment, any existing license has all entitlements set to true, so using an existing one as a fallback is not a problem. */ - entitlements: { - sso: dto.license_entitlements.sso, - workflows: dto.license_entitlements.workflows, - analytics: dto.license_entitlements.analytics, - dataEnrichment: dto.license_entitlements.data_enrichment, - userRoles: dto.license_entitlements.user_roles, - webhooks: dto.license_entitlements.webhooks, - // TODO(ruleSnoozes): remove this line once the backend supports rule snoozing in existing license (needs a migration on existing licenses) - ruleSnoozes: dto.license_entitlements.webhooks, - // ruleSnoozes: dto.license_entitlements.rule_snoozes, - }, + workflows: dto.workflows, + analytics: dto.analytics, + userRoles: dto.roles, + webhooks: dto.webhooks, + ruleSnoozes: dto.webhooks, + testRun: dto.test_run, + sanctions: dto.sanctions, }; } diff --git a/packages/app-builder/src/models/marble-session.ts b/packages/app-builder/src/models/marble-session.ts index f31249a2..ff0c3470 100644 --- a/packages/app-builder/src/models/marble-session.ts +++ b/packages/app-builder/src/models/marble-session.ts @@ -3,9 +3,14 @@ import { type Token } from 'marble-api'; import { type CreatedApiKey } from './api-keys'; import { type AuthErrors } from './auth-errors'; +import { type LicenseEntitlements } from './license'; import { type CurrentUser } from './user'; -export type AuthData = { authToken: Token; user: CurrentUser }; +export type AuthData = { + authToken: Token; + user: CurrentUser; + entitlements: LicenseEntitlements; +}; export type AuthFlashData = { authError: { message: AuthErrors }; createdApiKey: CreatedApiKey; diff --git a/packages/app-builder/src/repositories/LicenseRepository.ts b/packages/app-builder/src/repositories/LicenseRepository.ts index 335c9c4e..7149f361 100644 --- a/packages/app-builder/src/repositories/LicenseRepository.ts +++ b/packages/app-builder/src/repositories/LicenseRepository.ts @@ -1,38 +1,35 @@ import { type LicenseApi } from '@app-builder/infra/license-api'; import { - adaptLicenseValidation, - type LicenseValidation, + adaptLicenseEntitlements, + type LicenseEntitlements, } from '@app-builder/models/license'; export interface LicenseRepository { - validateLicenseKey(licenseKey: string): Promise; + getEntitlements(organizationId: string): Promise; + isSsoEnabled(): Promise; } -export function getLicenseRepository( - licenseAPIClient: LicenseApi, - devEnvironment: boolean, -): LicenseRepository { - return { - validateLicenseKey: async (licenseKey: string) => { - if (devEnvironment) { - return Promise.resolve({ - code: 'VALID', - entitlements: { - sso: true, - workflows: true, - analytics: true, - dataEnrichment: true, - userRoles: true, - // In dev environment (like docker-compose), webhooks are disabled since we do not have Convoy dedicated for dev - webhooks: false, - ruleSnoozes: true, - }, - }); +export const makeGetLicenseRepository = () => { + return (client: LicenseApi): LicenseRepository => ({ + getEntitlements: async (organizationId: string) => { + if (import.meta.env.PROD) { + const { feature_access } = await client.getEntitlements(organizationId); + if (!import.meta.env.PROD) { + feature_access.webhooks = 'restricted'; + feature_access.analytics = 'restricted'; + } + return adaptLicenseEntitlements(feature_access); } - const licenseValidationDto = - await licenseAPIClient.validateLicense(licenseKey); - - return adaptLicenseValidation(licenseValidationDto); + return Promise.resolve({ + sanctions: 'allowed', + ruleSnoozes: 'test', + userRoles: 'allowed', + webhooks: 'restricted', + analytics: 'restricted', + workflows: 'allowed', + testRun: 'allowed', + }); }, - }; -} + isSsoEnabled: async () => (await client.isSsoEnabled()).is_sso_enabled, + }); +}; diff --git a/packages/app-builder/src/repositories/init.server.ts b/packages/app-builder/src/repositories/init.server.ts index ea4945c6..3618aae0 100644 --- a/packages/app-builder/src/repositories/init.server.ts +++ b/packages/app-builder/src/repositories/init.server.ts @@ -1,4 +1,7 @@ -import { type LicenseApi } from '@app-builder/infra/license-api'; +import { + type GetLicenseAPIClientWithAuth, + type LicenseApi, +} from '@app-builder/infra/license-api'; import { type GetMarbleCoreAPIClientWithAuth } from '@app-builder/infra/marblecore-api'; import { type GetTransfercheckAPIClientWithAuth } from '@app-builder/infra/transfercheck-api'; @@ -10,7 +13,7 @@ import { makeGetDataModelRepository } from './DataModelRepository'; import { makeGetDecisionRepository } from './DecisionRepository'; import { makeGetEditorRepository } from './EditorRepository'; import { makeGetInboxRepository } from './InboxRepository'; -import { getLicenseRepository } from './LicenseRepository'; +import { makeGetLicenseRepository } from './LicenseRepository'; import { makeGetOrganizationRepository } from './OrganizationRepository'; import { makeGetPartnerRepository } from './PartnerRepository'; import { makeGetRuleSnoozeRepository } from './RuleSnoozeRepository'; @@ -30,15 +33,16 @@ import { makeGetUserRepository } from './UserRepository'; import { makeGetWebhookRepository } from './WebhookRepository'; export function makeServerRepositories({ - devEnvironment, sessionStorageRepositoryOptions, - licenseAPIClient, + getLicenseApiClientWithoutAuth, + getLicenseAPIClientWithAuth, getMarbleCoreAPIClientWithAuth, getTransfercheckAPIClientWithAuth, }: { devEnvironment: boolean; sessionStorageRepositoryOptions: SessionStorageRepositoryOptions; - licenseAPIClient: LicenseApi; + getLicenseApiClientWithoutAuth: () => LicenseApi; + getLicenseAPIClientWithAuth: GetLicenseAPIClientWithAuth; getMarbleCoreAPIClientWithAuth: GetMarbleCoreAPIClientWithAuth; getTransfercheckAPIClientWithAuth: GetTransfercheckAPIClientWithAuth; }) { @@ -53,6 +57,8 @@ export function makeServerRepositories({ lngStorageRepository: getLngStorageRepository( sessionStorageRepositoryOptions, ), + getLicenseApiClientWithoutAuth, + getLicenseAPIClientWithAuth, getMarbleCoreAPIClientWithAuth, getTransfercheckAPIClientWithAuth, getUserRepository: makeGetUserRepository(), @@ -74,7 +80,7 @@ export function makeServerRepositories({ getWebhookRepository: makeGetWebhookRepository(), getRuleSnoozeRepository: makeGetRuleSnoozeRepository(), getTestRunRepository: makeGetTestRunRepository(), - licenseRepository: getLicenseRepository(licenseAPIClient, devEnvironment), + getLicenseRepository: makeGetLicenseRepository(), }; } diff --git a/packages/app-builder/src/root.tsx b/packages/app-builder/src/root.tsx index a11d5f6a..be1ec69a 100644 --- a/packages/app-builder/src/root.tsx +++ b/packages/app-builder/src/root.tsx @@ -63,14 +63,12 @@ export const links: LinksFunction = () => [ ]; export async function loader({ request }: LoaderFunctionArgs) { - const { i18nextService, toastSessionService, csrfService, licenseService } = - serverServices; + const { i18nextService, toastSessionService, csrfService } = serverServices; const locale = await i18nextService.getLocale(request); const [toastSession, [csrfToken, csrfCookieHeader]] = await Promise.all([ toastSessionService.getSession(request), csrfService.commitToken(request), - licenseService.getLicenseEntitlements(), ]); const toastMessage = getToastMessage(toastSession); @@ -136,7 +134,7 @@ export function Layout({ children }: { children: React.ReactNode }) { - + {children}