diff --git a/.changeset/quick-rings-wave.md b/.changeset/quick-rings-wave.md new file mode 100644 index 0000000000000..0ea22897ff452 --- /dev/null +++ b/.changeset/quick-rings-wave.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/i18n": minor +"@rocket.chat/ui-client": minor +--- + +Added new Admin Feature Preview management view, this will allow the workspace admins to both enable feature previewing in the workspace as well as define which feature previews are enabled by default for the users in the workspace. diff --git a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx index 82c39c5c1b104..e54e2b72d6751 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx @@ -1,7 +1,7 @@ import { Badge } from '@rocket.chat/fuselage'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; -import { defaultFeaturesPreview, useFeaturePreviewList } from '@rocket.chat/ui-client'; import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; +import { defaultFeaturesPreview, usePreferenceFeaturePreviewList } from '@rocket.chat/ui-client'; import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; @@ -9,7 +9,7 @@ export const useAccountItems = (): GenericMenuItemProps[] => { const t = useTranslation(); const router = useRouter(); - const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList(); + const { unseenFeatures, featurePreviewEnabled } = usePreferenceFeaturePreviewList(); const handleMyAccount = useEffectEvent(() => { router.navigate('/account'); diff --git a/apps/meteor/client/hooks/useFeaturePreviewEnableQuery.ts b/apps/meteor/client/hooks/useFeaturePreviewEnableQuery.ts new file mode 100644 index 0000000000000..fd88f0237d297 --- /dev/null +++ b/apps/meteor/client/hooks/useFeaturePreviewEnableQuery.ts @@ -0,0 +1,28 @@ +import type { FeaturePreviewProps } from '@rocket.chat/ui-client'; +import { useMemo } from 'react'; + +const handleFeaturePreviewEnableQuery = (item: FeaturePreviewProps, _: any, features: FeaturePreviewProps[]) => { + if (item.enableQuery) { + const expected = item.enableQuery.value; + const received = features.find((el) => el.name === item.enableQuery?.name)?.value; + if (expected !== received) { + item.disabled = true; + item.value = false; + } else { + item.disabled = false; + } + } + return item; +}; + +const groupFeaturePreview = (features: FeaturePreviewProps[]) => + Object.entries( + features.reduce((result, currentValue) => { + (result[currentValue.group] = result[currentValue.group] || []).push(currentValue); + return result; + }, {} as Record), + ); + +export const useFeaturePreviewEnableQuery = (features: FeaturePreviewProps[]) => { + return useMemo(() => groupFeaturePreview(features.map(handleFeaturePreviewEnableQuery)), [features]); +}; diff --git a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx index 2be6b2b1dea21..51ab7a198a677 100644 --- a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx +++ b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx @@ -1,7 +1,7 @@ import { Badge } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { defaultFeaturesPreview, useFeaturePreviewList } from '@rocket.chat/ui-client'; import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; +import { defaultFeaturesPreview, usePreferenceFeaturePreviewList } from '@rocket.chat/ui-client'; import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; @@ -9,7 +9,7 @@ export const useAccountItems = (): GenericMenuItemProps[] => { const t = useTranslation(); const router = useRouter(); - const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList(); + const { unseenFeatures, featurePreviewEnabled } = usePreferenceFeaturePreviewList(); const handleMyAccount = useMutableCallback(() => { router.navigate('/account'); diff --git a/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewBadge.tsx b/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewBadge.tsx deleted file mode 100644 index c109ca1aefb53..0000000000000 --- a/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewBadge.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Badge } from '@rocket.chat/fuselage'; -import { useFeaturePreviewList } from '@rocket.chat/ui-client'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -const AccountFeaturePreviewBadge = () => { - const { t } = useTranslation(); - const { unseenFeatures } = useFeaturePreviewList(); - - if (!unseenFeatures) { - return null; - } - - return ( - - {unseenFeatures} - - ); -}; - -export default AccountFeaturePreviewBadge; diff --git a/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewPage.tsx b/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewPage.tsx index dd9ab6a90959a..358d2394003b1 100644 --- a/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewPage.tsx +++ b/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewPage.tsx @@ -1,4 +1,3 @@ -import { css } from '@rocket.chat/css-in-js'; import { ButtonGroup, Button, @@ -13,9 +12,10 @@ import { FieldLabel, FieldRow, FieldHint, + Callout, + Margins, } from '@rocket.chat/fuselage'; -import type { FeaturePreviewProps } from '@rocket.chat/ui-client'; -import { useFeaturePreviewList } from '@rocket.chat/ui-client'; +import { usePreferenceFeaturePreviewList } from '@rocket.chat/ui-client'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; import type { ChangeEvent } from 'react'; @@ -23,26 +23,12 @@ import React, { useEffect, Fragment } from 'react'; import { useForm } from 'react-hook-form'; import { Page, PageHeader, PageScrollableContentWithShadow, PageFooter } from '../../../components/Page'; +import { useFeaturePreviewEnableQuery } from '../../../hooks/useFeaturePreviewEnableQuery'; -const handleEnableQuery = (features: FeaturePreviewProps[]) => { - return features.map((item) => { - if (item.enableQuery) { - const expected = item.enableQuery.value; - const received = features.find((el) => el.name === item.enableQuery?.name)?.value; - if (expected !== received) { - item.disabled = true; - item.value = false; - } else { - item.disabled = false; - } - } - return item; - }); -}; const AccountFeaturePreviewPage = () => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); - const { features, unseenFeatures } = useFeaturePreviewList(); + const { features, unseenFeatures } = usePreferenceFeaturePreviewList(); const setUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); @@ -85,12 +71,7 @@ const AccountFeaturePreviewPage = () => { setValue('featuresPreview', updated, { shouldDirty: true }); }; - const grouppedFeaturesPreview = Object.entries( - handleEnableQuery(featuresPreview).reduce((result, currentValue) => { - (result[currentValue.group] = result[currentValue.group] || []).push(currentValue); - return result; - }, {} as Record), - ); + const grouppedFeaturesPreview = useFeaturePreviewEnableQuery(featuresPreview); return ( @@ -105,14 +86,11 @@ const AccountFeaturePreviewPage = () => { )} {featuresPreview.length > 0 && ( <> - - {t('Feature_preview_page_description')} + + + {t('Feature_preview_page_description')} + {t('Feature_preview_page_callout')} + {grouppedFeaturesPreview?.map(([group, features], index) => ( diff --git a/apps/meteor/client/views/account/sidebarItems.tsx b/apps/meteor/client/views/account/sidebarItems.tsx index ca0376be329d3..fa2ab8bd5e408 100644 --- a/apps/meteor/client/views/account/sidebarItems.tsx +++ b/apps/meteor/client/views/account/sidebarItems.tsx @@ -1,10 +1,9 @@ -import { defaultFeaturesPreview } from '@rocket.chat/ui-client'; +import { defaultFeaturesPreview, FeaturePreviewBadge } from '@rocket.chat/ui-client'; import React from 'react'; import { hasPermission, hasAtLeastOnePermission } from '../../../app/authorization/client'; import { settings } from '../../../app/settings/client'; import { createSidebarItems } from '../../lib/createSidebarItems'; -import AccountFeaturePreviewBadge from './featurePreview/AccountFeaturePreviewBadge'; export const { registerSidebarItem: registerAccountSidebarItem, @@ -54,7 +53,7 @@ export const { href: '/account/feature-preview', i18nLabel: 'Feature_preview', icon: 'flask', - badge: () => , + badge: () => , permissionGranted: () => settings.get('Accounts_AllowFeaturePreview') && defaultFeaturesPreview?.length > 0, }, { diff --git a/apps/meteor/client/views/admin/featurePreview/AdminFeaturePreviewPage.tsx b/apps/meteor/client/views/admin/featurePreview/AdminFeaturePreviewPage.tsx new file mode 100644 index 0000000000000..615fd20cf5a6c --- /dev/null +++ b/apps/meteor/client/views/admin/featurePreview/AdminFeaturePreviewPage.tsx @@ -0,0 +1,127 @@ +import { + ButtonGroup, + Button, + Box, + ToggleSwitch, + Accordion, + Field, + FieldGroup, + FieldLabel, + FieldRow, + FieldHint, + Callout, + Margins, +} from '@rocket.chat/fuselage'; +import { useDefaultSettingFeaturePreviewList } from '@rocket.chat/ui-client'; +import type { TranslationKey } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useTranslation, useSettingsDispatch } from '@rocket.chat/ui-contexts'; +import type { ChangeEvent } from 'react'; +import React, { Fragment } from 'react'; +import { useForm } from 'react-hook-form'; + +import { Page, PageHeader, PageScrollableContentWithShadow, PageFooter } from '../../../components/Page'; +import { useFeaturePreviewEnableQuery } from '../../../hooks/useFeaturePreviewEnableQuery'; +import { useEditableSetting } from '../EditableSettingsContext'; +import Setting from '../settings/Setting'; +import SettingsGroupPageSkeleton from '../settings/SettingsGroupPage/SettingsGroupPageSkeleton'; + +const AdminFeaturePreviewPage = () => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const allowFeaturePreviewSetting = useEditableSetting('Accounts_AllowFeaturePreview'); + const { features } = useDefaultSettingFeaturePreviewList(); + + const { + watch, + formState: { isDirty }, + setValue, + handleSubmit, + reset, + } = useForm({ + defaultValues: { featuresPreview: features }, + }); + const { featuresPreview } = watch(); + const dispatch = useSettingsDispatch(); + + const handleSave = async () => { + try { + const featuresToBeSaved = featuresPreview.map((feature) => ({ name: feature.name, value: feature.value })); + + await dispatch([ + { _id: allowFeaturePreviewSetting!._id, value: allowFeaturePreviewSetting!.value }, + { _id: 'Accounts_Default_User_Preferences_featuresPreview', value: JSON.stringify(featuresToBeSaved) }, + ]); + dispatchToastMessage({ type: 'success', message: t('Preferences_saved') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + reset({ featuresPreview }); + } + }; + + const handleFeatures = (e: ChangeEvent) => { + const updated = featuresPreview.map((item) => (item.name === e.target.name ? { ...item, value: e.target.checked } : item)); + setValue('featuresPreview', updated, { shouldDirty: true }); + }; + + const grouppedFeaturesPreview = useFeaturePreviewEnableQuery(featuresPreview); + + if (!allowFeaturePreviewSetting) { + // TODO: Implement FeaturePreviewSkeleton component + return ; + } + + return ( + + + + + + + {t('Feature_preview_admin_page_description')} + {t('Feature_preview_page_callout')} + {t('Feature_preview_admin_page_callout')} + + + + + {grouppedFeaturesPreview?.map(([group, features], index) => ( + + + {features.map((feature) => ( + + + + {t(feature.i18n)} + + + {feature.description && {t(feature.description)}} + + {feature.imageUrl && } + + ))} + + + ))} + + + + + + + + + + + ); +}; + +export default AdminFeaturePreviewPage; diff --git a/apps/meteor/client/views/admin/featurePreview/AdminFeaturePreviewRoute.tsx b/apps/meteor/client/views/admin/featurePreview/AdminFeaturePreviewRoute.tsx new file mode 100644 index 0000000000000..a7d6bd77d1360 --- /dev/null +++ b/apps/meteor/client/views/admin/featurePreview/AdminFeaturePreviewRoute.tsx @@ -0,0 +1,26 @@ +import { usePermission } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import React, { memo } from 'react'; + +import SettingsProvider from '../../../providers/SettingsProvider'; +import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; +import EditableSettingsProvider from '../settings/EditableSettingsProvider'; +import AdminFeaturePreviewPage from './AdminFeaturePreviewPage'; + +const AdminFeaturePreviewRoute = (): ReactElement => { + const canViewFeaturesPreview = usePermission('manage-cloud'); + + if (!canViewFeaturesPreview) { + return ; + } + + return ( + + + + + + ); +}; + +export default memo(AdminFeaturePreviewRoute); diff --git a/apps/meteor/client/views/admin/routes.tsx b/apps/meteor/client/views/admin/routes.tsx index f70df1625871e..d244d5e2f19be 100644 --- a/apps/meteor/client/views/admin/routes.tsx +++ b/apps/meteor/client/views/admin/routes.tsx @@ -104,6 +104,10 @@ declare module '@rocket.chat/ui-contexts' { pathname: `/admin/subscription`; pattern: '/admin/subscription'; }; + 'admin-feature-preview': { + pathname: '/admin/feature-preview'; + pattern: '/admin/feature-preview'; + }; } } @@ -237,3 +241,8 @@ registerAdminRoute('/subscription', { name: 'subscription', component: lazy(() => import('./subscription/SubscriptionRoute')), }); + +registerAdminRoute('/feature-preview', { + name: 'admin-feature-preview', + component: lazy(() => import('./featurePreview/AdminFeaturePreviewRoute')), +}); diff --git a/apps/meteor/client/views/admin/sidebarItems.ts b/apps/meteor/client/views/admin/sidebarItems.ts index 013206d9e9a89..fc7d307396d4b 100644 --- a/apps/meteor/client/views/admin/sidebarItems.ts +++ b/apps/meteor/client/views/admin/sidebarItems.ts @@ -1,3 +1,5 @@ +import { defaultFeaturesPreview } from '@rocket.chat/ui-client'; + import { hasPermission, hasAtLeastOnePermission, hasAllPermission } from '../../../app/authorization/client'; import { createSidebarItems } from '../../lib/createSidebarItems'; @@ -129,6 +131,12 @@ export const { icon: 'emoji', permissionGranted: (): boolean => hasPermission('manage-emoji'), }, + { + href: '/admin/feature-preview', + i18nLabel: 'Feature_preview', + icon: 'flask', + permissionGranted: () => defaultFeaturesPreview?.length > 0, + }, { href: '/admin/settings', i18nLabel: 'Settings', diff --git a/apps/meteor/public/images/featurePreview/enhanced-navigation.png b/apps/meteor/public/images/featurePreview/enhanced-navigation.png new file mode 100644 index 0000000000000..4240326ba985d Binary files /dev/null and b/apps/meteor/public/images/featurePreview/enhanced-navigation.png differ diff --git a/apps/meteor/public/images/featurePreview/resizable-contextual-bar.png b/apps/meteor/public/images/featurePreview/resizable-contextual-bar.png new file mode 100644 index 0000000000000..c36c7e44a29d8 Binary files /dev/null and b/apps/meteor/public/images/featurePreview/resizable-contextual-bar.png differ diff --git a/apps/meteor/public/images/featurePreview/timestamp.png b/apps/meteor/public/images/featurePreview/timestamp.png new file mode 100644 index 0000000000000..7573f97db55b7 Binary files /dev/null and b/apps/meteor/public/images/featurePreview/timestamp.png differ diff --git a/apps/meteor/server/settings/accounts.ts b/apps/meteor/server/settings/accounts.ts index b4da1cd913e9f..81166af5c2001 100644 --- a/apps/meteor/server/settings/accounts.ts +++ b/apps/meteor/server/settings/accounts.ts @@ -743,6 +743,11 @@ export const createAccountSettings = () => i18nLabel: 'Sidebar_Sections_Order', i18nDescription: 'Sidebar_Sections_Order_Description', }); + + await this.add('Accounts_Default_User_Preferences_featuresPreview', '[]', { + type: 'string', + public: true, + }); }); await this.section('Avatar', async function () { diff --git a/apps/meteor/tests/end-to-end/api/miscellaneous.ts b/apps/meteor/tests/end-to-end/api/miscellaneous.ts index b8341f7c0994f..d933f1f3c4b34 100644 --- a/apps/meteor/tests/end-to-end/api/miscellaneous.ts +++ b/apps/meteor/tests/end-to-end/api/miscellaneous.ts @@ -186,6 +186,7 @@ describe('miscellaneous', () => { 'muteFocusedConversations', 'notifyCalendarEvents', 'enableMobileRinging', + 'featuresPreview', ].filter((p) => Boolean(p)); expect(res.body).to.have.property('success', true); diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 9f37642263dae..a0387b41aa336 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -85,6 +85,7 @@ "Accounts_AllowEmailChange": "Allow Email Change", "Accounts_AllowEmailNotifications": "Allow Email Notifications", "Accounts_AllowFeaturePreview": "Allow Feature Preview", + "Accounts_AllowFeaturePreview_Description": "Make feature preview available to all workspace members.", "Accounts_AllowPasswordChange": "Allow Password Change", "Accounts_AllowPasswordChangeForOAuthUsers": "Allow Password Change for OAuth Users", "Accounts_AllowRealNameChange": "Allow Name Change", @@ -1125,8 +1126,8 @@ "Common_Access": "Common Access", "Commit": "Commit", "Community": "Community", - "Contextualbar_resizable": "Contextual bar resizable", - "Contextualbar_resizable_description": "Allows you to adjust the size of the contextual bar by simply dragging, giving you instant customization and flexibility", + "Contextualbar_resizable": "Resizable contextual bar", + "Contextualbar_resizable_description": "Adjust the size of the contextual bar by clicking and dragging the edge, giving you instant customization and flexibility.", "Free_Edition": "Free edition", "Composer_not_available_phone_calls": "Messages are not available on phone calls", "Condensed": "Condensed", @@ -1949,8 +1950,8 @@ "Enable_Password_History": "Enable Password History", "Enable_Password_History_Description": "When enabled, users won't be able to update their passwords to some of their most recently used passwords.", "Enable_Svg_Favicon": "Enable SVG favicon", - "Enable_timestamp": "Enable timestamp parsing in messages", - "Enable_timestamp_description": "Enable timestamps to be parsed in messages", + "Enable_timestamp": "Timestamp in messages", + "Enable_timestamp_description": "Render Unix timestamps inside messages in your local (system) timezone.", "Enable_to_bypass_email_verification": "Enable to bypass email verification", "Enable_two-factor_authentication": "Enable two-factor authentication via TOTP", "Enable_two-factor_authentication_email": "Enable two-factor authentication via Email", @@ -2284,7 +2285,10 @@ "Favorite_Rooms": "Enable Favorite Rooms", "Favorites": "Favorites", "Feature_preview": "Feature preview", - "Feature_preview_page_description": "Welcome to the features preview page! Here, you can enable the latest cutting-edge features that are currently under development and not yet officially released.\n\nPlease note that these configurations are still in the testing phase and may not be stable or fully functional.", + "Feature_preview_page_description": "Enable the latest features that are currently under development.", + "Feature_preview_page_callout": "Feature previews are being tested and may not be stable or fully functional. Features may become premium capabilities once officially released.", + "Feature_preview_admin_page_description": "Choose what feature previews to make available to workspace members.", + "Feature_preview_admin_page_callout": "Features enabled here will be enabled to each user in their feature preview preferences.", "featured": "featured", "Featured": "Featured", "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "This feature depends on the above selected call provider to be enabled from the administration settings (Admin -> Video Conference).", @@ -4363,7 +4367,7 @@ "Queue_Time": "Queue Time", "Queue_management": "Queue Management", "Quick_reactions": "Quick reactions", - "Quick_reactions_description": "The three most used reactions get an easy access while your mouse is over the message", + "Quick_reactions_description": "Easily access your most used and most recent emoji message reactions by hovering on a message.", "quote": "quote", "Quote": "Quote", "Random": "Random", diff --git a/packages/i18n/src/locales/hi-IN.i18n.json b/packages/i18n/src/locales/hi-IN.i18n.json index 090e081e83fa2..3110d6e82a679 100644 --- a/packages/i18n/src/locales/hi-IN.i18n.json +++ b/packages/i18n/src/locales/hi-IN.i18n.json @@ -2169,7 +2169,6 @@ "Favorite_Rooms": "पसंदीदा कमरे सक्षम करें", "Favorites": "पसंदीदा", "Feature_preview": "फ़ीचर पूर्वावलोकन", - "Feature_preview_page_description": "फीचर पूर्वावलोकन पृष्ठ पर आपका स्वागत है! यहां, आप नवीनतम अत्याधुनिक सुविधाओं को सक्षम कर सकते हैं जो वर्तमान में विकास के अधीन हैं और अभी तक आधिकारिक तौर पर जारी नहीं की गई हैं।\n\nकृपया ध्यान दें कि ये कॉन्फ़िगरेशन अभी भी परीक्षण चरण में हैं और स्थिर या पूरी तरह कार्यात्मक नहीं हो सकते हैं।", "featured": "प्रदर्शित", "Featured": "प्रदर्शित", "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "यह सुविधा प्रशासन सेटिंग्स (एडमिन -> वीडियो कॉन्फ्रेंस) से सक्षम होने के लिए उपरोक्त चयनित कॉल प्रदाता पर निर्भर करती है।", @@ -4128,7 +4127,6 @@ "Queue_Time": "कतार समय", "Queue_management": "कतार प्रबंधन", "Quick_reactions": "त्वरित प्रतिक्रियाएँ", - "Quick_reactions_description": "जब आपका माउस संदेश पर होता है तो सबसे अधिक उपयोग की जाने वाली तीन प्रतिक्रियाओं तक आसान पहुंच मिलती है", "quote": "उद्धरण", "Quote": "उद्धरण", "Random": "Random", @@ -4987,7 +4985,7 @@ "The_application_will_be_able_to": "<1>{{appName}} यह करने में सक्षम होगा:", "The_channel_name_is_required": "चैनल का नाम आवश्यक है", "The_emails_are_being_sent": "ईमेल भेजे जा रहे हैं.", - "The_empty_room__roomName__will_be_removed_automatically": "खाली कमरा {{roomName}} स्वचालित रूप से हटा दिया जाएगा।", + "The_empty_room__roomName__will_be_removed_automatically": "खाली कमरा {{roomName}} स्वचालित रूप से हटा दिया जाएगा।", "The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server": "छवि का आकार बदलना काम नहीं करेगा क्योंकि हम आपके सर्वर पर स्थापित ImageMagick या ग्राफ़िक्सMagick का पता नहीं लगा सकते हैं।", "The_message_is_a_discussion_you_will_not_be_able_to_recover": "संदेश एक चर्चा है आप संदेशों को पुनर्प्राप्त नहीं कर पाएंगे!", "The_mobile_notifications_were_disabled_to_all_users_go_to_Admin_Push_to_enable_the_Push_Gateway_again": "मोबाइल सूचनाएं सभी उपयोगकर्ताओं के लिए अक्षम कर दी गई थीं, पुश गेटवे को फिर से सक्षम करने के लिए \"एडमिन > पुश\" पर जाएं", @@ -6134,4 +6132,4 @@ "Unlimited_seats": "असीमित सीटें", "Unlimited_MACs": "असीमित एमएसी", "Unlimited_seats_MACs": "असीमित सीटें और एमएसी" -} \ No newline at end of file +} diff --git a/packages/ui-client/src/components/FeaturePreview/FeaturePreviewBadge.tsx b/packages/ui-client/src/components/FeaturePreview/FeaturePreviewBadge.tsx new file mode 100644 index 0000000000000..eece30cc72803 --- /dev/null +++ b/packages/ui-client/src/components/FeaturePreview/FeaturePreviewBadge.tsx @@ -0,0 +1,21 @@ +import { Badge } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; + +import { usePreferenceFeaturePreviewList } from '../../hooks/usePreferenceFeaturePreviewList'; + +const FeaturePreviewBadge = () => { + const t = useTranslation(); + const { unseenFeatures } = usePreferenceFeaturePreviewList(); + + if (!unseenFeatures) { + return null; + } + + return ( + + {unseenFeatures} + + ); +}; + +export default FeaturePreviewBadge; diff --git a/packages/ui-client/src/components/FeaturePreview/index.ts b/packages/ui-client/src/components/FeaturePreview/index.ts new file mode 100644 index 0000000000000..f6b8e5f2071e7 --- /dev/null +++ b/packages/ui-client/src/components/FeaturePreview/index.ts @@ -0,0 +1,2 @@ +export { FeaturePreview, FeaturePreviewOn, FeaturePreviewOff } from './FeaturePreview'; +export { default as FeaturePreviewBadge } from './FeaturePreviewBadge'; diff --git a/packages/ui-client/src/components/index.ts b/packages/ui-client/src/components/index.ts index 8642983229aa7..7308c8e754316 100644 --- a/packages/ui-client/src/components/index.ts +++ b/packages/ui-client/src/components/index.ts @@ -11,7 +11,7 @@ export * as UserStatus from './UserStatus'; export * from './Header'; export * from './HeaderV2'; export * from './MultiSelectCustom/MultiSelectCustom'; -export * from './FeaturePreview/FeaturePreview'; +export * from './FeaturePreview'; export * from './RoomBanner'; export { default as UserAutoComplete } from './UserAutoComplete'; export * from './GenericMenu'; diff --git a/packages/ui-client/src/hooks/useDefaultSettingFeaturePreviewList.ts b/packages/ui-client/src/hooks/useDefaultSettingFeaturePreviewList.ts new file mode 100644 index 0000000000000..373862379cc1e --- /dev/null +++ b/packages/ui-client/src/hooks/useDefaultSettingFeaturePreviewList.ts @@ -0,0 +1,12 @@ +import { useSetting } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { parseSetting, useFeaturePreviewList } from './useFeaturePreviewList'; + +export const useDefaultSettingFeaturePreviewList = () => { + const featurePreviewSettingJSON = useSetting('Accounts_Default_User_Preferences_featuresPreview'); + + const settingFeaturePreview = useMemo(() => parseSetting(featurePreviewSettingJSON), [featurePreviewSettingJSON]); + + return useFeaturePreviewList(settingFeaturePreview ?? []); +}; diff --git a/packages/ui-client/src/hooks/useFeaturePreview.ts b/packages/ui-client/src/hooks/useFeaturePreview.ts index 4bdda9c9251a7..bd46adfdefff5 100644 --- a/packages/ui-client/src/hooks/useFeaturePreview.ts +++ b/packages/ui-client/src/hooks/useFeaturePreview.ts @@ -1,7 +1,8 @@ -import { type FeaturesAvailable, useFeaturePreviewList } from './useFeaturePreviewList'; +import { type FeaturesAvailable } from './useFeaturePreviewList'; +import { usePreferenceFeaturePreviewList } from './usePreferenceFeaturePreviewList'; export const useFeaturePreview = (featureName: FeaturesAvailable) => { - const { features } = useFeaturePreviewList(); + const { features } = usePreferenceFeaturePreviewList(); const currentFeature = features?.find((feature) => feature.name === featureName); diff --git a/packages/ui-client/src/hooks/useFeaturePreviewList.ts b/packages/ui-client/src/hooks/useFeaturePreviewList.ts index ff103a8d84ef1..08bda4ff81ffe 100644 --- a/packages/ui-client/src/hooks/useFeaturePreviewList.ts +++ b/packages/ui-client/src/hooks/useFeaturePreviewList.ts @@ -1,5 +1,4 @@ import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import { useUserPreference, useSetting } from '@rocket.chat/ui-contexts'; export type FeaturesAvailable = | 'quickReactions' @@ -24,6 +23,7 @@ export type FeaturePreviewProps = { }; }; +// TODO: Move the features preview array to another directory to be accessed from both BE and FE. export const defaultFeaturesPreview: FeaturePreviewProps[] = [ { name: 'quickReactions', @@ -47,6 +47,7 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [ i18n: 'Enable_timestamp', description: 'Enable_timestamp_description', group: 'Message', + imageUrl: 'images/featurePreview/timestamp.png', value: false, enabled: true, }, @@ -55,6 +56,7 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [ i18n: 'Contextualbar_resizable', description: 'Contextualbar_resizable_description', group: 'Navigation', + imageUrl: 'images/featurePreview/resizable-contextual-bar.png', value: false, enabled: true, }, @@ -63,6 +65,7 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [ i18n: 'New_navigation', description: 'New_navigation_description', group: 'Navigation', + imageUrl: 'images/featurePreview/enhanced-navigation.png', value: false, enabled: true, }, @@ -82,22 +85,27 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [ export const enabledDefaultFeatures = defaultFeaturesPreview.filter((feature) => feature.enabled); -export const useFeaturePreviewList = () => { - const featurePreviewEnabled = useSetting('Accounts_AllowFeaturePreview'); - const userFeaturesPreview = useUserPreference('featuresPreview'); - - if (!featurePreviewEnabled) { - return { unseenFeatures: 0, features: [] as FeaturePreviewProps[], featurePreviewEnabled }; +// TODO: Remove this logic after we have a way to store object settings. +export const parseSetting = (setting?: FeaturePreviewProps[] | string) => { + if (typeof setting === 'string') { + try { + return JSON.parse(setting) as FeaturePreviewProps[]; + } catch (_) { + return; + } } + return setting; +}; +export const useFeaturePreviewList = (featuresList: Pick[]) => { const unseenFeatures = enabledDefaultFeatures.filter( - (feature) => !userFeaturesPreview?.find((userFeature) => userFeature.name === feature.name), + (defaultFeature) => !featuresList?.find((feature) => feature.name === defaultFeature.name), ).length; - const mergedFeatures = enabledDefaultFeatures.map((feature) => { - const userFeature = userFeaturesPreview?.find((userFeature) => userFeature.name === feature.name); - return { ...feature, ...userFeature }; + const mergedFeatures = enabledDefaultFeatures.map((defaultFeature) => { + const features = featuresList?.find((feature) => feature.name === defaultFeature.name); + return { ...defaultFeature, ...features }; }); - return { unseenFeatures, features: mergedFeatures, featurePreviewEnabled }; + return { unseenFeatures, features: mergedFeatures }; }; diff --git a/packages/ui-client/src/hooks/useFeaturePreviewList.spec.tsx b/packages/ui-client/src/hooks/usePreferenceFeaturePreviewList.spec.tsx similarity index 79% rename from packages/ui-client/src/hooks/useFeaturePreviewList.spec.tsx rename to packages/ui-client/src/hooks/usePreferenceFeaturePreviewList.spec.tsx index e348cfb6a8643..ac3d6f92d51aa 100644 --- a/packages/ui-client/src/hooks/useFeaturePreviewList.spec.tsx +++ b/packages/ui-client/src/hooks/usePreferenceFeaturePreviewList.spec.tsx @@ -1,10 +1,11 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; import { renderHook } from '@testing-library/react'; -import { useFeaturePreviewList, enabledDefaultFeatures } from './useFeaturePreviewList'; +import { enabledDefaultFeatures } from './useFeaturePreviewList'; +import { usePreferenceFeaturePreviewList } from './usePreferenceFeaturePreviewList'; it('should return the number of unseen features and Accounts_AllowFeaturePreview enabled ', () => { - const { result } = renderHook(() => useFeaturePreviewList(), { + const { result } = renderHook(() => usePreferenceFeaturePreviewList(), { legacyRoot: true, wrapper: mockAppRoot().withSetting('Accounts_AllowFeaturePreview', true).build(), }); @@ -18,7 +19,7 @@ it('should return the number of unseen features and Accounts_AllowFeaturePreview }); it('should return the number of unseen features and Accounts_AllowFeaturePreview disabled ', () => { - const { result } = renderHook(() => useFeaturePreviewList(), { + const { result } = renderHook(() => usePreferenceFeaturePreviewList(), { legacyRoot: true, wrapper: mockAppRoot().withSetting('Accounts_AllowFeaturePreview', false).build(), }); @@ -32,7 +33,7 @@ it('should return the number of unseen features and Accounts_AllowFeaturePreview }); it('should return 0 unseen features', () => { - const { result } = renderHook(() => useFeaturePreviewList(), { + const { result } = renderHook(() => usePreferenceFeaturePreviewList(), { legacyRoot: true, wrapper: mockAppRoot() .withSetting('Accounts_AllowFeaturePreview', true) @@ -49,7 +50,7 @@ it('should return 0 unseen features', () => { }); it('should ignore removed feature previews', () => { - const { result } = renderHook(() => useFeaturePreviewList(), { + const { result } = renderHook(() => usePreferenceFeaturePreviewList(), { legacyRoot: true, wrapper: mockAppRoot() .withSetting('Accounts_AllowFeaturePreview', true) @@ -72,7 +73,7 @@ it('should ignore removed feature previews', () => { }); it('should turn off ignored feature previews', async () => { - const { result } = renderHook(() => useFeaturePreviewList(), { + const { result } = renderHook(() => usePreferenceFeaturePreviewList(), { legacyRoot: true, wrapper: mockAppRoot() .withSetting('Accounts_AllowFeaturePreview', true) diff --git a/packages/ui-client/src/hooks/usePreferenceFeaturePreviewList.ts b/packages/ui-client/src/hooks/usePreferenceFeaturePreviewList.ts new file mode 100644 index 0000000000000..d7c4c13417d28 --- /dev/null +++ b/packages/ui-client/src/hooks/usePreferenceFeaturePreviewList.ts @@ -0,0 +1,16 @@ +import { useSetting, useUserPreference } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { FeaturePreviewProps, parseSetting, useFeaturePreviewList } from './useFeaturePreviewList'; + +export const usePreferenceFeaturePreviewList = () => { + const featurePreviewEnabled = useSetting('Accounts_AllowFeaturePreview'); + const userFeaturesPreviewPreference = useUserPreference('featuresPreview'); + const userFeaturesPreview = useMemo(() => parseSetting(userFeaturesPreviewPreference), [userFeaturesPreviewPreference]); + const { unseenFeatures, features } = useFeaturePreviewList(userFeaturesPreview ?? []); + + if (!featurePreviewEnabled) { + return { unseenFeatures: 0, features: [] as FeaturePreviewProps[], featurePreviewEnabled }; + } + return { unseenFeatures, features, featurePreviewEnabled }; +}; diff --git a/packages/ui-client/src/index.ts b/packages/ui-client/src/index.ts index 3e640343da5b2..a96ef265aadc2 100644 --- a/packages/ui-client/src/index.ts +++ b/packages/ui-client/src/index.ts @@ -1,5 +1,7 @@ export * from './components'; export * from './hooks/useFeaturePreview'; +export * from './hooks/useDefaultSettingFeaturePreviewList'; export * from './hooks/useFeaturePreviewList'; +export * from './hooks/usePreferenceFeaturePreviewList'; export * from './hooks/useDocumentTitle'; export * from './helpers';