From 0105657b0da0eb33839e839f38c6a857f2524745 Mon Sep 17 00:00:00 2001 From: Anna McPhee Date: Sat, 9 Nov 2024 00:38:41 +1100 Subject: [PATCH] Squashed commit of the following: commit 32d3e82ad9e6147f559c97a8f6a1ac38079cd85c Author: Richard Ortiz Date: Fri Nov 8 12:16:03 2024 +0100 Global Styles on Personal: Added FEATURE_STYLE_CUSTOMIZATION to Personal Plan (#96159) * Added FEATURE_STYLE_CUSTOMIZATION to Personal plan when the feature flag is enabled * Fixed code style issue. Added the feature to the upgrade comparison table commit b4ddf2fa05b3621d3ae62d26f8bc2a1bcf184c73 Author: Tony Arcangelini <33258733+arcangelini@users.noreply.github.com> Date: Fri Nov 8 04:15:22 2024 -0600 Help Center: use support interactions (#96049) * Help Center: use support interactions * useChat hook added - account for ZD * Yeah... I know this is a lot * Fix update source to event_source * Interactions loading consistently * Fix bug when switching interaction * Fix types * minor fixup * Fix types * Fix a few bugs * Fixed get conversation function return type * Fix useEffect dependency * Working History * Working History 2 * Add stability to data fetching * Fix handoff * Fix types * Fix first reply to ZD * Fix bug in old version * Improved recent conversations * Fix tracking * Help Center: use support interactions * useChat hook added - account for ZD * Yeah... I know this is a lot * Fix update source to event_source * Interactions loading consistently * Fix bug when switching interaction * Fix types * minor fixup * Fix types * Fixed get conversation function return type * Fix a few bugs * Fix useEffect dependency * Working History * Working History 2 * Add stability to data fetching * Fix handoff * Fix types * Fix first reply to ZD * Fix bug in old version * Improved recent conversations * Fix tracking * Fix regex to convert text url --------- Co-authored-by: heavyweight Co-authored-by: Renan Co-authored-by: escapemanuele commit 904fe23d3c177ea96ae852bf545c78f96ebef436 Author: Madhu Dollu Date: Fri Nov 8 12:52:45 2024 +0530 remove email upsell from checkout/thank-you page (#96138) commit f7c82074276d73ec2b89d205b8e7ee5165c04250 Author: okmttdhr, tada Date: Fri Nov 8 15:45:54 2024 +0900 Remove unnecessary style overrides regarding fly-out panel (#96097) commit 23a4db855d133ab0f5562e79a796ba6ec2cb006f Author: Yashwin Poojary Date: Fri Nov 8 09:53:50 2024 +0530 A4A > E2E tests: Creating a new E2E test user (#96132) commit 536b9b8a7f270dccac773f30a5404cca843d6ef6 Author: Candy Tsai Date: Fri Nov 8 11:16:19 2024 +0800 Fix the flickering of upsell banners in /hosting-config (#96141) * Fix the flickering of upsell banners in /hosting-config * Show banner for ecommerce trial commit cdd3d6b0b3a90278ccb30db34d07dd80e7f5549b Author: Candy Tsai Date: Fri Nov 8 11:15:57 2024 +0800 Update /hosting-config layout (#96136) * Move PHP version right after WordPress version * Remove period from Learn more * Remove period for Review privacy settings commit 479fd7953b9e64c5cf270042e61092df1a3e677f Author: Paulo Marcos Trentin Date: Thu Nov 7 18:54:58 2024 -0300 Fixed plugin count on the old interface (#96168) commit c3ed7e2100f26c004b2e7ab48ee90405990f8c57 Author: Paulo Marcos Trentin Date: Thu Nov 7 17:52:00 2024 -0300 Updated auto-update copy (#96165) commit 06e5b5ad16cb979d467e0e22988bb0065f4042ad Author: Paulo Marcos Trentin Date: Thu Nov 7 17:16:16 2024 -0300 Jetpack cloud visual improvements for DataViews integration (#96156) * Jetpack cloud improvements: - Fixed title, edge of the table, search box, filters aligments - Fixed accent color and hover state colors - Fixed "Button style" on DataViews filters * Improved loading style commit 6aa6b9e86b9d8cd93ce3f3b92f10e44165e4554b Author: Paulo Marcos Trentin Date: Thu Nov 7 17:06:05 2024 -0300 Optimized how we load plugins and improved load state (#96150) * Improved screen loading state and plugin processing speed * Test updated commit 9648a04b13a1ac1ce2ba078d2fc9168343d81bc6 Author: Chris Holder Date: Thu Nov 7 14:04:25 2024 -0600 Reader Recent Overhaul: Add styling for sidebar section (#96122) commit efefa75a5e850f4a53f3646fc2a0424353350ed5 Author: Allison Levine <1689238+allilevine@users.noreply.github.com> Date: Thu Nov 7 14:23:35 2024 -0500 Reader Recent Feed Overhaul: Fix site icon (#96152) * Fix site icon for recent feed overhaul. * Use avatar as backup icon. * Fix type. commit a7444c9141f67990dab1f670fdf870ba5d1a1f2b Author: Allison Levine <1689238+allilevine@users.noreply.github.com> Date: Thu Nov 7 13:43:14 2024 -0500 Reader Recent Feed Overhaul: Add mobile view (#96127) * Add mobile view to Recent Feed Overhaul. * Use CSS to show hide the post. * Fix featured image getting squished. * Revert type changes. commit 925549f162997f81da127b380add36e946814d78 Author: Calypso Bot Date: Thu Nov 7 19:35:41 2024 +0100 chore(deps): update webpack packages (#91492) * chore(deps): update webpack packages * Update snapshots --------- Co-authored-by: Renovate Bot Co-authored-by: Chris Holder commit 19b5be31e2aecefe03c8dfaccdded519ff2c93df Author: Anthony Grullon Date: Thu Nov 7 13:30:10 2024 -0500 Help Center: Update odie header text based on pathname condition (#96153) commit f7cd9610698ef40fe76f007eba0d4232c2980a52 Author: Anthony Grullon Date: Thu Nov 7 12:28:21 2024 -0500 Help Center Chat: Enhance file upload button styles for better accessibility (#96148) * Help Center Chat: Enhance file upload button styles for better accessibility * change outline color commit c68e28c4c5615ee5f2293c7527c8df2a1eb3c066 Author: Roberto Aranda Date: Thu Nov 7 18:14:53 2024 +0100 Performance Profiler: Remove Generated with AI badge as it confuses users (#96147) commit 9ff488fd40dc2627e6d7520b6363fee466b71dc7 Author: Emanuele Buccelli Date: Thu Nov 7 17:45:45 2024 +0100 Help Center: Fix styling padding (#96146) * Fix padding * Fix styles commit c8e6928466baf682d3ad4837fe2e7e6ba5d5248a Author: Dusty Reagan Date: Thu Nov 7 09:57:50 2024 -0600 Recent Feed Overhaul: Add FullPostView layout prop (#96072) * Add FullPostView layout prop * Styling in progress * Call translate directly * Name and follow count working * Disable showRelatedPosts if not isDefaultLayout * Added site icon * Completed header formating * Works and updated style * Remove type update * Update comment * Set a default site icon if no src exists and check for default layout commit b7e65ef25a0890ad88f9a151adf70580c72ec17c Author: Gabriel Caires Date: Thu Nov 7 15:41:35 2024 +0000 Post-purchase: Automatically cancel migration when user exits migration (#96123) * Refactor migration status update hook * Set migration as pending when the credentials are required * Set migration as pending when is DIY * Fix wrong type keyword * Cancel migration when user click on back button * Cancel a migration when the user select Import instead of migrate * Add tests * Rename hook * When the feature flag is off we should continue setting started commit d537efc0548e6ff5c971d532c7cf1a50e35e558c Author: Damien Alleyne <31164683+d-alleyne@users.noreply.github.com> Date: Thu Nov 7 11:28:49 2024 -0400 Refactor: Move global staticFilters constant to class method (#96116) * Refactor: Move global staticFilters constant to class method Follow-up to https://github.com/Automattic/wp-calypso/pull/95995#pullrequestreview-2412589439 * Implement code review suggestions commit fd0906b9550f721214d85695aa0f8bf741e021e9 Author: Aurorum <43215253+Aurorum@users.noreply.github.com> Date: Thu Nov 7 15:19:13 2024 +0000 Site Address Changer: Fix Bleeding Dialog Styles (#96142) * Start new branch * Add showCloseIcon * Remove unneeded styles commit 1f1ece8f9fc6f93d2fd7b77163640f7ada96aa40 Author: Gabriel Caires Date: Thu Nov 7 14:55:04 2024 +0000 Post- Purchase: Set a migration as `pending-DIFM/DIF` when user arrives on the credential/instructions step (#96103) commit 5f34350ee2886d5707f4036d7fe499ba51f38e41 Author: Paulo Cruz Date: Thu Nov 7 10:26:02 2024 -0300 Fix duplicated email verified notice (#96125) commit 9e3be02b4bfc701beb17b804813060220e930ee7 Author: Renan Carvalho Date: Thu Nov 7 13:49:52 2024 +0100 Help Center: Add temp flag for showing simplified chat history (#96139) * Add temporary flag for displaying simplified chat history * Improve border commit 172dc36517e824e73eefca803dc6b5ef9fe0ead3 Author: Yuliyan Slavchev Date: Thu Nov 7 14:18:57 2024 +0200 localizeUrl: Update wp-login rule to support Romanian and Vietnamese (#95943) commit 665844367750d32aa1054e6dd8e4f89243df09af Author: Ashar Fuadi Date: Thu Nov 7 18:57:31 2024 +0700 Untangling: Add Marketing -> Connections (#96135) commit 7575c0f73a84472ed8ecfecd5e2c07f6d67fb614 Author: Roberto Aranda Date: Thu Nov 7 12:40:11 2024 +0100 Performance Profiler: Consolidate all time metrics to seconds (#96140) commit fe086b8222f9fb5f8621fbe5bbb51ad68db66d5b Author: Mikael Korpela Date: Thu Nov 7 13:03:50 2024 +0200 Importer: use "import" term for CTA rather than "Start import" (#96111) commit e782f6005bae1306a53c682156b651c99934e1f3 Author: Kosta Date: Thu Nov 7 11:29:31 2024 +0100 Help Center: add image upload for chats (#96105) * HelpCenter: add upload (wip) * Minor fixes and refactoring * Changed icon * Use correct constants * Improve isAttachingFile state * Store global clientId as fallback * Fix attach button style * Move up the flag check * Improved function --------- Co-authored-by: escapemanuele Co-authored-by: Anthony Grullon Co-authored-by: Renan commit 9f3d6332ca137c932155d60210225444de4205cf Author: Anna McPhee <30754158+annacmc@users.noreply.github.com> Date: Thu Nov 7 04:08:25 2024 -0600 Stats: Chart refresh- CSS styling (#95980) * create new chart instance behind feature flag * adds a className prop to the chart * Add ChartHeader component for better composition * update show/hide legend prop for legacy vs new chart * Adds dedicated header component with title and controls props * Reorganise header layout * Adds activeTab to the chart title * remove control section for a future PR * add a showChartHeader prop * small fix to header title display * add in accidentally removed comment * add date filtering enabled CSS classname * add chart styling * remove !importants, increase title font size * adjust font weight * update axis marger border color * fix the code styling commit c7878746e53de81fca8431402971f7e0e844c32d Author: Emanuele Buccelli Date: Thu Nov 7 10:42:11 2024 +0100 Help Center: Update copy (#96076) * Update copy * Update help center copies with flag * Change the right component * Fix Odie initial message * Help is on the way * Change header text * Minimized History * Less Odie in HC --------- Co-authored-by: Anthony Grullon --- apps/blaze-dashboard/package.json | 6 +- apps/command-palette-wp-admin/package.json | 4 +- apps/happy-blocks/package.json | 4 +- apps/help-center/package.json | 6 +- apps/notifications/package.json | 4 +- apps/o2-blocks/package.json | 4 +- apps/odyssey-stats/package.json | 6 +- apps/whats-new/package.json | 4 +- apps/wpcom-block-editor/package.json | 4 +- .../sections/sites/sites-dashboard/style.scss | 4 - .../assets/images/thank-you-upsell/email.jpg | Bin 69566 -> 0 bytes client/blocks/reader-full-post/header.jsx | 72 +++- client/blocks/reader-full-post/index.jsx | 104 +++--- client/blocks/reader-full-post/style.scss | 231 +++++++----- client/blocks/site-address-changer/index.jsx | 2 +- client/blocks/site-address-changer/style.scss | 15 - client/components/dataviews/style.scss | 11 +- client/components/email-verification/index.js | 1 + client/data/site-migration/landing/logger.ts | 38 ++ client/data/site-migration/landing/types.ts | 8 + .../use-migration-cancellation/index.ts | 48 +++ .../use-migration-cancellation/test/index.tsx | 48 +++ .../use-update-migration-status/index.ts | 65 ++++ .../test/index.tsx | 49 +++ .../use-update-migration-status.ts | 42 --- client/hosting/server-settings/main.tsx | 8 +- client/hosting/server-settings/test/index.js | 1 + .../sections/jetpack-social/connections.jsx | 4 +- .../importer-migrate-message/index.tsx | 9 +- .../site-migration-credentials/index.tsx | 13 + .../site-migration-credentials/test/index.tsx | 15 + .../site-migration-how-to-migrate/index.tsx | 14 +- .../use-pending-migration-status.ts | 54 --- .../index.tsx | 5 + .../site-migration-instructions/index.tsx | 17 +- .../test/index.tsx | 17 + .../checkout/checkout-thank-you/index.tsx | 3 +- .../redesign-v2/pages/domain-only.tsx | 77 ---- .../my-sites/importer/author-mapping-pane.jsx | 11 +- .../content-upload/author-mapping-pane.jsx | 9 +- client/my-sites/marketing/controller.js | 2 +- client/my-sites/marketing/main.jsx | 2 +- client/my-sites/media-library/content.jsx | 2 +- .../my-sites/media-library/test/content.jsx | 2 +- client/my-sites/plugins/main.jsx | 26 +- .../plugins-list/style-compatibilty.scss | 11 + .../my-sites/plugins/plugins-list/style.scss | 33 ++ .../plugins/plugins-list/use-actions.tsx | 4 +- .../site-admin-interface/index.js | 2 +- .../stats/stats-chart-tabs/style.scss | 34 ++ client/my-sites/themes/theme-showcase.jsx | 61 +-- client/package.json | 2 +- .../components/insights-section/index.tsx | 37 +- client/performance-profiler/utils/metrics.ts | 6 +- client/reader/recent/index.tsx | 54 +-- client/reader/recent/recent-seen-field.tsx | 7 +- client/reader/recent/style.scss | 74 +++- client/reader/recent/types.ts | 11 +- client/reader/sidebar/index.jsx | 14 +- .../sidebar/reader-sidebar-recent/index.tsx | 107 +++--- .../sidebar/reader-sidebar-recent/style.scss | 64 ++++ client/reader/sidebar/style.scss | 1 + client/reader/stream/style.scss | 1 + client/reader/style.scss | 2 +- client/sites/components/dotcom-style.scss | 65 +++- .../components/site-preview-pane/constants.ts | 2 + .../site-preview-pane/dotcom-preview-pane.tsx | 3 +- client/sites/components/style.scss | 61 +-- .../marketing/connections/README.md | 0 .../connections/account-dialog-account.jsx | 0 .../connections/account-dialog-account.scss | 0 .../marketing/connections/account-dialog.jsx | 0 .../marketing/connections/account-dialog.scss | 0 .../marketing/connections/bluesky.tsx | 0 .../marketing/connections/connection.jsx | 0 .../marketing/connections/connections.jsx | 0 .../connections/google-photos-migration.jsx | 0 .../connections/google-plus-deprecation.jsx | 0 client/sites/marketing/connections/index.tsx | 34 ++ .../connections/inline-connection-action.jsx | 0 .../connections/inline-connection.jsx | 2 +- .../connections/mailchimp-settings.jsx | 0 .../marketing/connections/mastodon.tsx | 0 .../marketing/connections/service-action.jsx | 6 +- .../service-connected-accounts.jsx | 0 .../connections/service-description.jsx | 0 .../connections/service-description.scss | 0 .../marketing/connections/service-example.jsx | 0 .../connections/service-examples.jsx | 0 .../connections/service-examples.scss | 0 .../connections/service-placeholder.jsx | 0 .../marketing/connections/service-tip.jsx | 0 .../marketing/connections/service.jsx | 0 .../marketing/connections/services-group.jsx | 0 .../marketing/connections/services-group.scss | 0 .../connections/services/bluesky.jsx | 2 +- .../connections/services/facebook.js | 2 +- .../connections/services/fediverse.jsx | 0 .../connections/services/google-drive.js | 2 +- .../services/google-my-business.js | 2 +- .../connections/services/google-photos.js | 2 +- .../marketing/connections/services/index.js | 0 .../services/instagram-business.jsx | 2 +- .../connections/services/instagram.jsx | 2 +- .../connections/services/mailchimp.js | 2 +- .../connections/services/mastodon.jsx | 2 +- .../connections/services/nextdoor.jsx | 2 +- .../connections/services/p2-github.js | 2 +- .../connections/services/p2-slack.js | 2 +- .../connections/services/threads.jsx | 2 +- .../marketing/connections/test/mastodon.jsx | 0 .../marketing/connections/types.ts | 0 client/sites/marketing/controller.tsx | 35 +- client/sites/marketing/index.tsx | 18 +- .../{my-sites => sites}/marketing/style.scss | 0 client/sites/settings/caches/form.tsx | 8 +- .../settings/web-server/web-server-form.tsx | 2 +- .../tools/database/restore-db-password.js | 12 +- client/sites/tools/sftp-ssh/sftp-form.tsx | 2 +- client/state/hosting/reducer.js | 4 +- client/state/hosting/test/reducer.js | 8 +- client/state/plugins/installed/selectors.js | 8 +- .../state/plugins/installed/test/selectors.js | 58 +-- client/state/reader-ui/sidebar/actions.js | 3 +- desktop/package.json | 2 +- package.json | 8 +- packages/calypso-apps-builder/package.json | 4 +- packages/calypso-build/package.json | 4 +- .../calypso-e2e/src/secrets/encrypted.enc | Bin 15680 -> 15968 bytes .../src/secrets/secrets-manager.ts | 4 + packages/calypso-products/src/plans-list.tsx | 49 ++- packages/calypso-storybook/package.json | 2 +- packages/components/package.json | 2 +- .../data-stores/src/help-center/actions.ts | 9 +- .../data-stores/src/help-center/reducer.ts | 11 +- .../data-stores/src/help-center/selectors.ts | 1 + packages/design-carousel/package.json | 2 +- packages/design-picker/package.json | 2 +- packages/domains-table/package.json | 2 +- packages/fingerprintjs/package.json | 12 +- .../components/help-center-chat-history.tsx | 43 ++- .../src/components/help-center-chat.scss | 10 + .../src/components/help-center-chat.tsx | 6 - .../components/help-center-contact-form.tsx | 35 +- .../components/help-center-contact-page.tsx | 3 - .../help-center-contact-support-option.tsx | 22 +- .../src/components/help-center-content.tsx | 61 ++- .../components/help-center-feedback-form.tsx | 79 ++-- .../src/components/help-center-header.scss | 4 - .../src/components/help-center-header.tsx | 21 +- .../help-center-recent-conversations.tsx | 18 +- .../src/components/help-center-smooch.tsx | 16 +- .../help-center-support-chat-message.scss | 7 + .../help-center-support-chat-message.tsx | 14 +- packages/help-center/src/components/utils.tsx | 20 +- .../hooks/use-reset-support-interaction.ts | 29 ++ packages/i18n-utils/src/locales.ts | 2 + packages/i18n-utils/src/localize-url.tsx | 3 +- packages/jetpack-ai-calypso/package.json | 2 +- packages/languages/package.json | 2 +- packages/launchpad-navigator/package.json | 2 +- packages/launchpad/package.json | 2 +- packages/odie-client/README.md | 37 -- .../message/direct-escalation-link.tsx | 10 +- .../message/dislike-feedback-message.tsx | 18 +- .../src/components/message/error-message.tsx | 2 +- .../src/components/message/get-support.tsx | 2 +- .../src/components/message/index.tsx | 2 +- .../src/components/message/jump-to-recent.tsx | 6 +- .../components/message/message-content.tsx | 2 +- .../components/message/messages-container.tsx | 24 +- .../src/components/message/sources.tsx | 2 +- .../components/message/style_redesign.scss | 15 +- .../src/{ => components/message}/types.d.ts | 0 .../src/components/message/uri-transformer.ts | 8 +- .../src/components/message/user-message.tsx | 2 +- .../message/was-this-helpful-buttons.tsx | 13 +- .../send-message-input/attachment-button.tsx | 81 ++++ .../components/send-message-input/index.tsx | 11 +- .../components/send-message-input/style.scss | 15 + packages/odie-client/src/constants.ts | 50 ++- packages/odie-client/src/context/index.tsx | 352 ++++++------------ .../src/context/use-load-previous-chat.ts | 78 ---- .../src/data/broadcast-messages.ts | 45 +++ .../data/handle-support-interactions-fetch.ts | 4 +- packages/odie-client/src/data/index.ts | 114 +----- .../data/use-get-support-interaction-by-id.ts | 2 +- .../src/data/use-get-support-interactions.ts | 27 +- .../src/data/use-get-zendesk-conversation.ts | 10 +- .../data/use-manage-support-interaction.ts | 30 +- .../odie-client/src/data/use-odie-chat.ts | 40 ++ .../{query => data}/use-send-odie-feedback.ts | 10 +- .../src/data/use-send-odie-message.ts | 134 +++++++ packages/odie-client/src/hooks/index.ts | 7 + .../use-auto-scroll.ts} | 12 +- .../hooks/use-create-zendesk-conversation.ts | 68 ++++ .../src/hooks/use-get-combined-chat.ts | 79 ++++ .../use-odie-user-tracking.ts} | 11 +- .../{query => hooks}/use-send-chat-message.ts | 22 +- .../src/hooks/use-send-zendesk-message.ts | 38 ++ .../use-zendesk-message-listener.ts | 32 +- packages/odie-client/src/index.tsx | 6 +- .../query/use-create-zendesk-conversation.ts | 63 ---- .../odie-client/src/query/use-odie-chat.ts | 78 ---- .../src/query/use-send-odie-message.ts | 92 ----- .../src/query/use-send-zendesk-message.ts | 31 -- .../src/{types/index.ts => types.ts} | 133 ++++--- .../get-odie-initial-message.ts | 16 +- packages/odie-client/src/utils/index.ts | 3 +- .../src/utils/is-odie-allowed-bot.ts | 5 +- .../odie-client/src/utils/storage-utils.ts | 2 + .../src/utils/zendesk-message-converter.ts | 4 +- packages/odie-client/tsconfig.json | 7 +- packages/odie-client/types/index.d.ts | 2 +- packages/onboarding/package.json | 2 +- packages/plans-grid-next/package.json | 2 +- packages/privacy-toolset/package.json | 2 +- packages/search/package.json | 2 +- packages/social-previews/package.json | 2 +- .../webpack-config-flag-plugin/package.json | 2 +- .../test/__snapshots__/index.js.snap | 100 ++--- .../package.json | 2 +- .../package.json | 4 +- .../test/__snapshots__/index.js.snap | 8 +- packages/webpack-rtl-plugin/package.json | 4 +- packages/wpcom-proxy-request/package.json | 2 +- packages/wpcom.js/package.json | 2 +- packages/zendesk-client/src/constants.ts | 4 + packages/zendesk-client/src/index.ts | 1 + .../zendesk-client/src/use-attach-file.tsx | 64 ++++ packages/zendesk-client/src/use-smooch.ts | 8 +- yarn.lock | 291 ++++++++------- 232 files changed, 2808 insertions(+), 2019 deletions(-) delete mode 100644 client/assets/images/thank-you-upsell/email.jpg create mode 100644 client/data/site-migration/landing/logger.ts create mode 100644 client/data/site-migration/landing/types.ts create mode 100644 client/data/site-migration/landing/use-migration-cancellation/index.ts create mode 100644 client/data/site-migration/landing/use-migration-cancellation/test/index.tsx create mode 100644 client/data/site-migration/landing/use-update-migration-status/index.ts create mode 100644 client/data/site-migration/landing/use-update-migration-status/test/index.tsx delete mode 100644 client/data/site-migration/use-update-migration-status.ts delete mode 100644 client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-how-to-migrate/use-pending-migration-status.ts create mode 100644 client/reader/sidebar/reader-sidebar-recent/style.scss rename client/{my-sites => sites}/marketing/connections/README.md (100%) rename client/{my-sites => sites}/marketing/connections/account-dialog-account.jsx (100%) rename client/{my-sites => sites}/marketing/connections/account-dialog-account.scss (100%) rename client/{my-sites => sites}/marketing/connections/account-dialog.jsx (100%) rename client/{my-sites => sites}/marketing/connections/account-dialog.scss (100%) rename client/{my-sites => sites}/marketing/connections/bluesky.tsx (100%) rename client/{my-sites => sites}/marketing/connections/connection.jsx (100%) rename client/{my-sites => sites}/marketing/connections/connections.jsx (100%) rename client/{my-sites => sites}/marketing/connections/google-photos-migration.jsx (100%) rename client/{my-sites => sites}/marketing/connections/google-plus-deprecation.jsx (100%) create mode 100644 client/sites/marketing/connections/index.tsx rename client/{my-sites => sites}/marketing/connections/inline-connection-action.jsx (100%) rename client/{my-sites => sites}/marketing/connections/inline-connection.jsx (89%) rename client/{my-sites => sites}/marketing/connections/mailchimp-settings.jsx (100%) rename client/{my-sites => sites}/marketing/connections/mastodon.tsx (100%) rename client/{my-sites => sites}/marketing/connections/service-action.jsx (96%) rename client/{my-sites => sites}/marketing/connections/service-connected-accounts.jsx (100%) rename client/{my-sites => sites}/marketing/connections/service-description.jsx (100%) rename client/{my-sites => sites}/marketing/connections/service-description.scss (100%) rename client/{my-sites => sites}/marketing/connections/service-example.jsx (100%) rename client/{my-sites => sites}/marketing/connections/service-examples.jsx (100%) rename client/{my-sites => sites}/marketing/connections/service-examples.scss (100%) rename client/{my-sites => sites}/marketing/connections/service-placeholder.jsx (100%) rename client/{my-sites => sites}/marketing/connections/service-tip.jsx (100%) rename client/{my-sites => sites}/marketing/connections/service.jsx (100%) rename client/{my-sites => sites}/marketing/connections/services-group.jsx (100%) rename client/{my-sites => sites}/marketing/connections/services-group.scss (100%) rename client/{my-sites => sites}/marketing/connections/services/bluesky.jsx (81%) rename client/{my-sites => sites}/marketing/connections/services/facebook.js (94%) rename client/{my-sites => sites}/marketing/connections/services/fediverse.jsx (100%) rename client/{my-sites => sites}/marketing/connections/services/google-drive.js (95%) rename client/{my-sites => sites}/marketing/connections/services/google-my-business.js (98%) rename client/{my-sites => sites}/marketing/connections/services/google-photos.js (96%) rename client/{my-sites => sites}/marketing/connections/services/index.js (100%) rename client/{my-sites => sites}/marketing/connections/services/instagram-business.jsx (92%) rename client/{my-sites => sites}/marketing/connections/services/instagram.jsx (96%) rename client/{my-sites => sites}/marketing/connections/services/mailchimp.js (95%) rename client/{my-sites => sites}/marketing/connections/services/mastodon.jsx (78%) rename client/{my-sites => sites}/marketing/connections/services/nextdoor.jsx (78%) rename client/{my-sites => sites}/marketing/connections/services/p2-github.js (96%) rename client/{my-sites => sites}/marketing/connections/services/p2-slack.js (96%) rename client/{my-sites => sites}/marketing/connections/services/threads.jsx (55%) rename client/{my-sites => sites}/marketing/connections/test/mastodon.jsx (100%) rename client/{my-sites => sites}/marketing/connections/types.ts (100%) rename client/{my-sites => sites}/marketing/style.scss (100%) create mode 100644 packages/help-center/src/hooks/use-reset-support-interaction.ts rename packages/odie-client/src/{ => components/message}/types.d.ts (100%) create mode 100644 packages/odie-client/src/components/send-message-input/attachment-button.tsx delete mode 100644 packages/odie-client/src/context/use-load-previous-chat.ts create mode 100644 packages/odie-client/src/data/broadcast-messages.ts create mode 100644 packages/odie-client/src/data/use-odie-chat.ts rename packages/odie-client/src/{query => data}/use-send-odie-feedback.ts (74%) create mode 100644 packages/odie-client/src/data/use-send-odie-message.ts create mode 100644 packages/odie-client/src/hooks/index.ts rename packages/odie-client/src/{useAutoScroll.ts => hooks/use-auto-scroll.ts} (71%) create mode 100644 packages/odie-client/src/hooks/use-create-zendesk-conversation.ts create mode 100644 packages/odie-client/src/hooks/use-get-combined-chat.ts rename packages/odie-client/src/{track-location/useOdieUserTracking.ts => hooks/use-odie-user-tracking.ts} (92%) rename packages/odie-client/src/{query => hooks}/use-send-chat-message.ts (79%) create mode 100644 packages/odie-client/src/hooks/use-send-zendesk-message.ts rename packages/odie-client/src/{utils => hooks}/use-zendesk-message-listener.ts (55%) delete mode 100644 packages/odie-client/src/query/use-create-zendesk-conversation.ts delete mode 100644 packages/odie-client/src/query/use-odie-chat.ts delete mode 100644 packages/odie-client/src/query/use-send-odie-message.ts delete mode 100644 packages/odie-client/src/query/use-send-zendesk-message.ts rename packages/odie-client/src/{types/index.ts => types.ts} (57%) rename packages/odie-client/src/{context => utils}/get-odie-initial-message.ts (59%) create mode 100644 packages/zendesk-client/src/use-attach-file.tsx diff --git a/apps/blaze-dashboard/package.json b/apps/blaze-dashboard/package.json index 9b8f2aa82d237e..7dfd3b8e169e59 100644 --- a/apps/blaze-dashboard/package.json +++ b/apps/blaze-dashboard/package.json @@ -57,16 +57,16 @@ "@automattic/webpack-extensive-lodash-replacement-plugin": "workspace:^", "@automattic/webpack-inline-constant-exports-plugin": "workspace:^", "@automattic/wp-babel-makepot": "workspace:^", - "@wordpress/dependency-extraction-webpack-plugin": "^5.9.0", + "@wordpress/dependency-extraction-webpack-plugin": "^6.11.0", "autoprefixer": "^10.2.5", "gettext-parser": "^6.0.0", - "html-webpack-plugin": "^5.6.0", + "html-webpack-plugin": "^5.6.3", "lodash": "^4.17.21", "mkdirp": "^1.0.4", "node-fetch": "^2.6.6", "path-browserify": "^1.0.1", "postcss": "^8.3.11", - "webpack": "^5.94.0", + "webpack": "^5.95.0", "webpack-bundle-analyzer": "^4.10.2" } } diff --git a/apps/command-palette-wp-admin/package.json b/apps/command-palette-wp-admin/package.json index 591f0db5680805..d3386995f54641 100644 --- a/apps/command-palette-wp-admin/package.json +++ b/apps/command-palette-wp-admin/package.json @@ -43,14 +43,14 @@ "@automattic/calypso-eslint-overrides": "workspace:^", "@automattic/languages": "workspace:^", "@automattic/wp-babel-makepot": "workspace:^", - "@wordpress/dependency-extraction-webpack-plugin": "^5.9.0", + "@wordpress/dependency-extraction-webpack-plugin": "^6.11.0", "gettext-parser": "^6.0.0", "lodash": "^4.17.21", "mkdirp": "^1.0.4", "node-fetch": "^2.6.6", "npm-run-all": "^4.1.5", "postcss": "^8.4.5", - "webpack": "^5.94.0", + "webpack": "^5.95.0", "webpack-bundle-analyzer": "^4.10.2" } } diff --git a/apps/happy-blocks/package.json b/apps/happy-blocks/package.json index 2d910512b81024..ef033c3214fedf 100644 --- a/apps/happy-blocks/package.json +++ b/apps/happy-blocks/package.json @@ -53,10 +53,10 @@ "@automattic/calypso-apps-builder": "workspace:^", "@emotion/react": "^11.11.1", "@testing-library/react": "^15.0.7", - "@wordpress/readable-js-assets-webpack-plugin": "^3.0.0", + "@wordpress/readable-js-assets-webpack-plugin": "^3.11.0", "copy-webpack-plugin": "^10.2.4", "glob": "^7.1.6", "postcss": "^8.4.5", - "webpack": "^5.94.0" + "webpack": "^5.95.0" } } diff --git a/apps/help-center/package.json b/apps/help-center/package.json index 84a608cba6f4ad..b4c860be1f8bfc 100644 --- a/apps/help-center/package.json +++ b/apps/help-center/package.json @@ -49,11 +49,11 @@ "@automattic/calypso-build": "workspace:^", "@automattic/calypso-typescript-config": "workspace:^", "@automattic/wp-babel-makepot": "workspace:^", - "@wordpress/dependency-extraction-webpack-plugin": "5.9.0", - "@wordpress/readable-js-assets-webpack-plugin": "3.0.0", + "@wordpress/dependency-extraction-webpack-plugin": "6.11.0", + "@wordpress/readable-js-assets-webpack-plugin": "3.11.0", "copy-webpack-plugin": "^10.2.4", "typescript": "^5.3.3", - "webpack": "^5.94.0" + "webpack": "^5.95.0" }, "peerDependencies": { "@automattic/calypso-router": "workspace:^", diff --git a/apps/notifications/package.json b/apps/notifications/package.json index 1f1dd97bc8c8f6..3ac5c1d595970f 100644 --- a/apps/notifications/package.json +++ b/apps/notifications/package.json @@ -53,9 +53,9 @@ "@automattic/calypso-eslint-overrides": "workspace:^", "@automattic/languages": "workspace:^", "@automattic/wp-babel-makepot": "workspace:^", - "html-webpack-plugin": "^5.6.0", + "html-webpack-plugin": "^5.6.3", "postcss": "^8.4.5", "postcss-custom-properties": "^11.0.0", - "webpack": "^5.94.0" + "webpack": "^5.95.0" } } diff --git a/apps/o2-blocks/package.json b/apps/o2-blocks/package.json index e0dec33309401e..e745d34a90b4e6 100644 --- a/apps/o2-blocks/package.json +++ b/apps/o2-blocks/package.json @@ -47,8 +47,8 @@ "devDependencies": { "@automattic/calypso-apps-builder": "workspace:^", "@automattic/calypso-eslint-overrides": "workspace:^", - "@wordpress/readable-js-assets-webpack-plugin": "^3.0.0", + "@wordpress/readable-js-assets-webpack-plugin": "^3.11.0", "postcss": "^8.4.5", - "webpack": "^5.94.0" + "webpack": "^5.95.0" } } diff --git a/apps/odyssey-stats/package.json b/apps/odyssey-stats/package.json index 158255f4b3dc5e..7321f20e275697 100644 --- a/apps/odyssey-stats/package.json +++ b/apps/odyssey-stats/package.json @@ -62,11 +62,11 @@ "@automattic/wp-babel-makepot": "workspace:^", "@babel/core": "^7.25.8", "@size-limit/file": "^8.2.6", - "@wordpress/dependency-extraction-webpack-plugin": "^5.9.0", + "@wordpress/dependency-extraction-webpack-plugin": "^6.11.0", "autoprefixer": "^10.2.5", "babel-jest": "^29.6.1", "gettext-parser": "^6.0.0", - "html-webpack-plugin": "^5.6.0", + "html-webpack-plugin": "^5.6.3", "jest": "^29.7.0", "lodash": "^4.17.21", "mkdirp": "^1.0.4", @@ -74,7 +74,7 @@ "path-browserify": "^1.0.1", "postcss": "^8.3.11", "size-limit": "^8.2.6", - "webpack": "^5.94.0", + "webpack": "^5.95.0", "webpack-bundle-analyzer": "^4.10.2" } } diff --git a/apps/whats-new/package.json b/apps/whats-new/package.json index 1b5b2a7e72d6b6..fdcb13a92e3dcc 100644 --- a/apps/whats-new/package.json +++ b/apps/whats-new/package.json @@ -46,14 +46,14 @@ "@automattic/calypso-eslint-overrides": "workspace:^", "@automattic/languages": "workspace:^", "@automattic/wp-babel-makepot": "workspace:^", - "@wordpress/dependency-extraction-webpack-plugin": "^5.9.0", + "@wordpress/dependency-extraction-webpack-plugin": "^6.11.0", "gettext-parser": "^6.0.0", "lodash": "^4.17.21", "mkdirp": "^1.0.4", "node-fetch": "^2.6.6", "npm-run-all": "^4.1.5", "postcss": "^8.4.5", - "webpack": "^5.94.0", + "webpack": "^5.95.0", "webpack-bundle-analyzer": "^4.10.2" } } diff --git a/apps/wpcom-block-editor/package.json b/apps/wpcom-block-editor/package.json index c4dcd53167d3b4..3cf62e059f3384 100644 --- a/apps/wpcom-block-editor/package.json +++ b/apps/wpcom-block-editor/package.json @@ -57,10 +57,10 @@ "@automattic/calypso-apps-builder": "workspace:^", "@automattic/calypso-build": "workspace:^", "@automattic/calypso-eslint-overrides": "workspace:^", - "@wordpress/dependency-extraction-webpack-plugin": "^5.9.0", + "@wordpress/dependency-extraction-webpack-plugin": "^6.11.0", "npm-run-all": "^4.1.5", "postcss": "^8.4.5", - "webpack": "^5.94.0", + "webpack": "^5.95.0", "webpack-bundle-analyzer": "^4.10.2" } } diff --git a/client/a8c-for-agencies/sections/sites/sites-dashboard/style.scss b/client/a8c-for-agencies/sections/sites/sites-dashboard/style.scss index 85d8fa355f5e72..584f8e086206de 100644 --- a/client/a8c-for-agencies/sections/sites/sites-dashboard/style.scss +++ b/client/a8c-for-agencies/sections/sites/sites-dashboard/style.scss @@ -409,10 +409,6 @@ align-items: stretch; overflow: hidden; } - - .components-button:focus:not(:disabled) { - box-shadow: 0 0 0 2px var(--color-primary-light); - } } .a4a-layout__top-wrapper, diff --git a/client/assets/images/thank-you-upsell/email.jpg b/client/assets/images/thank-you-upsell/email.jpg deleted file mode 100644 index 98c7523696f37a4665b6be0e9a41662ba6caeced..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69566 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 diff --git a/client/blocks/reader-full-post/header.jsx b/client/blocks/reader-full-post/header.jsx index ae869d71396da1..d110aa5efa73be 100644 --- a/client/blocks/reader-full-post/header.jsx +++ b/client/blocks/reader-full-post/header.jsx @@ -1,13 +1,17 @@ +import { Gridicon } from '@automattic/components'; +import formatNumber from '@automattic/components/src/number-formatters/lib/format-number'; import clsx from 'clsx'; +import { translate, getLocaleSlug } from 'i18n-calypso'; import PropTypes from 'prop-types'; import TagsList from 'calypso/blocks/reader-post-card/tags-list'; +import ReaderSiteStreamLink from 'calypso/blocks/reader-site-stream-link'; import AutoDirection from 'calypso/components/auto-direction'; import ExternalLink from 'calypso/components/external-link'; import TimeSince from 'calypso/components/time-since'; import { recordPermalinkClick } from 'calypso/reader/stats'; import ReaderFullPostHeaderPlaceholder from './placeholders/header'; -const ReaderFullPostHeader = ( { post, authorProfile } ) => { +const ReaderFullPostHeader = ( { post, authorProfile, layout } ) => { const handlePermalinkClick = () => { recordPermalinkClick( 'full_post_title', post ); }; @@ -16,7 +20,10 @@ const ReaderFullPostHeader = ( { post, authorProfile } ) => { recordPermalinkClick( 'timestamp_full_post', post ); }; - const classes = { 'reader-full-post__header': true }; + const classes = { + 'reader-full-post__header': true, + 'reader-full-post__header--recent': layout === 'recent', + }; if ( ! post.title || post.title.trim().length < 1 ) { classes[ 'is-missing-title' ] = true; } @@ -25,9 +32,35 @@ const ReaderFullPostHeader = ( { post, authorProfile } ) => { return ; } + // Rather than pass in additional props for the `recent` layout, we extract the props we need from authorProfile. + const { props: { author, siteIcon, feedIcon, siteName, followCount } = {} } = authorProfile || {}; + + const isDefaultLayout = layout === 'default'; + const iconSrc = author?.avatar_URL || siteIcon || feedIcon; + /* eslint-disable react/jsx-no-target-blank */ return ( ); /* eslint-enable react/jsx-no-target-blank */ @@ -68,6 +127,11 @@ const ReaderFullPostHeader = ( { post, authorProfile } ) => { ReaderFullPostHeader.propTypes = { post: PropTypes.object.isRequired, children: PropTypes.node, + layout: PropTypes.oneOf( [ 'default', 'recent' ] ), +}; + +ReaderFullPostHeader.defaultProps = { + layout: 'default', }; export default ReaderFullPostHeader; diff --git a/client/blocks/reader-full-post/index.jsx b/client/blocks/reader-full-post/index.jsx index 2c5ec76bd84981..322e51b5b79f5b 100644 --- a/client/blocks/reader-full-post/index.jsx +++ b/client/blocks/reader-full-post/index.jsx @@ -93,6 +93,7 @@ export class FullPostView extends Component { referralStream: PropTypes.string, isWPForTeamsItem: PropTypes.bool, hasOrganization: PropTypes.bool, + layout: PropTypes.oneOf( [ 'default', 'recent' ] ), }; hasScrolledToCommentAnchor = false; @@ -463,9 +464,10 @@ export class FullPostView extends Component { return ; } + const isDefaultLayout = this.props.layout !== 'recent'; const siteName = getSiteName( { site, post } ); const classes = { 'reader-full-post': true }; - const showRelatedPosts = post && ! post.is_external && post.site_ID; + const showRelatedPosts = post && ! post.is_external && post.site_ID && isDefaultLayout; const relatedPostsFromOtherSitesTitle = translate( 'More on {{wpLink}}WordPress.com{{/wpLink}}', { @@ -489,7 +491,6 @@ export class FullPostView extends Component { const commentCount = get( post, 'discussion.comment_count' ); const postKey = { blogId, feedId, postId }; const contentWidth = readerContentWidth(); - const feedIcon = feed ? feed.site_icon ?? get( feed, 'image' ) : null; /*eslint-disable react/no-danger */ @@ -510,7 +511,7 @@ export class FullPostView extends Component { ) } { referral && ! referralPost && } { ! post || ( isLoading && ) } - + { isDefaultLayout && }
-
- { isLoading && } - { ! isLoading && post.author && ( - - ) } -
- { userCan( 'edit_post', post ) && ( - - ) } - - { shouldShowComments( post ) && ( - - ) } - - { shouldShowLikes( post ) && ( - + { isLoading && } + { ! isLoading && post.author && ( + ) } +
+ { userCan( 'edit_post', post ) && ( + + ) } + + { shouldShowComments( post ) && ( + + ) } + + { shouldShowLikes( post ) && ( + + ) } - { isEligibleForUnseen( { isWPForTeamsItem, hasOrganization } ) && - canBeMarkedAsSeen( { post } ) && - this.renderMarkAsSenButton() } + { isEligibleForUnseen( { isWPForTeamsItem, hasOrganization } ) && + canBeMarkedAsSeen( { post } ) && + this.renderMarkAsSenButton() } +
-
+ ) }
660px" ) { + @include breakpoint-deprecated( '>660px' ) { display: none; } .external-link .gridicons-external { - fill: var(--color-neutral-40); + fill: var( --color-neutral-40 ); top: 0; } .external-link { - color: var(--color-neutral-40); + color: var( --color-neutral-40 ); display: block; height: 18px; margin-left: -3px; @@ -81,28 +85,28 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po top: -1px; &:hover { - color: var(--color-accent); + color: var( --color-accent ); .gridicons-external { - fill: var(--color-accent); + fill: var( --color-accent ); } } } .reader-full-post__visit-site-label { - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { display: none; } } } .reader-full-post__sidebar { - width: var(--reader-full-post-sidebar-width); + width: var( --reader-full-post-sidebar-width ); text-align: center; margin-top: 55px; // hide the sidebar when it cannot fit to the side of the content. - @media (max-width: $reader-full-post-desktop-min) { + @media ( max-width: $reader-full-post-desktop-min ) { display: none; } } @@ -117,28 +121,28 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po margin-left: -4px; user-select: none; - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { top: 12px; } } .reader-full-post__story { - max-width: var(--reader-full-post-story-max-width); - padding: 32px var(--reader-full-post-story-padding); + max-width: var( --reader-full-post-story-max-width ); + padding: 32px var( --reader-full-post-story-padding ); margin: 0 auto; - @media (min-width: $reader-full-post-desktop-min) { + @media ( min-width: $reader-full-post-desktop-min ) { margin: 0; } - @include breakpoint-deprecated( "<480px" ) { + @include breakpoint-deprecated( '<480px' ) { font-size: $font-body; line-height: 24px; } } .reader-full-post__story-content { - color: var(--color-neutral-70); - font-size: rem(17px); + color: var( --color-neutral-70 ); + font-size: rem( 17px ); line-height: 28px; figure.wp-block-table { @@ -175,13 +179,13 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po button.is-compact.is-borderless { margin: 2px; - @media (max-width: 781px) { + @media ( max-width: 781px ) { padding-top: 12px; padding-bottom: 12px; } } - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { left: 14px; } } @@ -190,7 +194,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po display: inline-flex; flex-direction: column; - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { margin-bottom: 35px; padding-right: 10px; position: relative; @@ -201,7 +205,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po height: 40px; } - &:not(.is-placeholder)::after { + &:not( .is-placeholder )::after { @include long-content-fade( $size: 15% ); } } @@ -210,7 +214,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po margin-top: 0; } - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { flex-direction: row; margin-top: 20px; @@ -250,14 +254,14 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po height: 32px !important; width: 32px !important; line-height: 32px !important; - font-size: rem(32px) !important; + font-size: rem( 32px ) !important; .gridicon { - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { height: 32px !important; width: 32px !important; line-height: 32px !important; - font-size: rem(32px) !important; + font-size: rem( 32px ) !important; } } } @@ -269,7 +273,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po margin-right: 5px; &::after { - content: ","; + content: ','; font-weight: 400; } } @@ -278,7 +282,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po flex: 1 0 0; display: inline; - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { margin-top: 0; } } @@ -290,13 +294,16 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po .author-compact-profile__follow .follow-button { position: fixed; - left: calc(100% - 270px); + left: calc( 100% - 270px ); top: 5px; - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { background: transparent; position: fixed; - z-index: z-index(".reader-full-post__sidebar-comment-like", ".author-compact-profile__follow .follow-button"); + z-index: z-index( + '.reader-full-post__sidebar-comment-like', + '.author-compact-profile__follow .follow-button' + ); .gridicon { height: 24px; @@ -311,7 +318,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po } .author-compact-profile__follow .follow-button__label { - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { display: none; } } @@ -319,14 +326,14 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po } .reader-full-post .reader-full-post__sidebar { - color: var(--color-text-subtle); + color: var( --color-text-subtle ); a.reader-author-link, a.author-compact-profile__site-link { - color: var(--color-primary); + color: var( --color-primary ); &:hover { - color: var(--color-primary-light); + color: var( --color-primary-light ); } } } @@ -340,7 +347,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po .reader-share__button-label, .comment-button__label, .like-button__label { - font-size: rem(17px); + font-size: rem( 17px ); } } @@ -351,11 +358,11 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po justify-content: center; margin-top: 10px; - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { align-items: flex-start; - background: var(--studio-white); + background: var( --studio-white ); display: flex; - border-bottom: 1px solid var(--color-neutral-0); + border-bottom: 1px solid var( --color-neutral-0 ); flex-direction: row; height: 47px; justify-content: flex-end; @@ -363,24 +370,24 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po right: 60px; top: 0; width: 100%; - z-index: z-index("root", ".reader-full-post__sidebar-comment-like"); + z-index: z-index( 'root', '.reader-full-post__sidebar-comment-like' ); } .comment-button.is-active, .comment-button:hover { svg.reader-comment path { - stroke: var(--color-link); + stroke: var( --color-link ); } } .like-button:hover, .like-button.is-active, .like-button.is-liked { svg.reader-star path { - stroke: var(--color-link); + stroke: var( --color-link ); } } .like-button.is-liked { svg.reader-star path { - fill: var(--color-link); + fill: var( --color-link ); } } } @@ -391,7 +398,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po gap: 5px; justify-content: center; - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { top: 9px; } @@ -411,7 +418,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po gap: 5px; justify-content: center; - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { top: 9px; } @@ -428,7 +435,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po .reader-full-post .has-author-link.has-author-icon { margin-top: 25px; - @include breakpoint-deprecated( ">660px" ) { + @include breakpoint-deprecated( '>660px' ) { margin-top: 5px; } @@ -446,7 +453,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po } .reader-full-post .has-author-link .reader-author-link { - @include breakpoint-deprecated( ">660px" ) { + @include breakpoint-deprecated( '>660px' ) { margin-top: 4px; } } @@ -462,7 +469,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po .reader-full-post__header-title { clear: none; - color: var(--color-neutral-70); + color: var( --color-neutral-70 ); font-family: $sans; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -471,25 +478,23 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po line-height: 34px; max-width: 750px; - - @include breakpoint-deprecated( ">960px" ) { + @include breakpoint-deprecated( '>960px' ) { font-size: $font-headline-small; line-height: 46px; } - @include breakpoint-deprecated( "480px-960px" ) { + @include breakpoint-deprecated( '480px-960px' ) { font-size: $font-title-large; line-height: 40px; } - .reader-full-post__header-title-link { display: block; } .reader-full-post__header-title-link, .reader-full-post__header-title-link:hover { - color: var(--color-neutral-70); + color: var( --color-neutral-70 ); } } @@ -500,10 +505,11 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po max-width: 750px; } +.reader-full-post__header-follow-count, .reader-full-post__header-date { line-height: 1.6; - @include breakpoint-deprecated( "<480px" ) { + @include breakpoint-deprecated( '<480px' ) { line-height: 1.4; } } @@ -512,21 +518,23 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po margin-right: 25px; } +.reader-full-post__header-site-name-link, +.reader-full-post__header-site-name-link:visited, .reader-full-post__header-date-link, .reader-full-post__header-date-link:visited { - color: var(--color-text-subtle); + color: var( --color-text-subtle ); &:hover { - color: var(--color-primary-light); + color: var( --color-primary-light ); } } .reader-full-post .reader-post-card__tag { - background-color: var(--color-surface); + background-color: var( --color-surface ); } .reader-full-post .reader-post-card__tag-link { - color: var(--color-neutral-80); + color: var( --color-neutral-80 ); /* stylelint-disable-next-line declaration-property-unit-allowed-list */ font-size: 12px; margin-top: 2px; @@ -535,7 +543,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po } .reader-full-post .reader-post-card__tag-link:hover { - color: var(--color-primary); + color: var( --color-primary ); } .reader-full-post__header { @@ -547,18 +555,18 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po margin: 28px 0 26px; position: relative; &::after { - content: ""; + content: ''; position: absolute; pointer-events: none; top: 0; left: 0; bottom: 0; right: 0; - box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 0 0 1px rgba( 0, 0, 0, 0.1 ); border-radius: 5px; /* stylelint-disable-line scales/radii */ } img { - box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 0 0 1px rgba( 0, 0, 0, 0.1 ); border-radius: 4px; position: relative; } @@ -566,12 +574,12 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po .reader-full-post__unavailable { .back-button__label { - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { display: inherit; } } .reader-full-post__content { - @include breakpoint-deprecated( "<660px" ) { + @include breakpoint-deprecated( '<660px' ) { margin-top: 2em; } } @@ -603,11 +611,11 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po &:focus, &:active { .gridicon { - fill: var(--color-link); + fill: var( --color-link ); } .post-edit-button__label { - color: var(--color-link); + color: var( --color-link ); } } } @@ -629,41 +637,86 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po button.is-active, .like-button.is-liked { svg.reader-external path { - fill: var(--color-link); + fill: var( --color-link ); } svg.reader-comment path, svg.reader-share path { - stroke: var(--color-link); + stroke: var( --color-link ); } svg.reader-star path { - stroke: var(--color-link); + stroke: var( --color-link ); } } .like-button.is-liked { svg.reader-star path { - fill: var(--color-link); + fill: var( --color-link ); } } } .wp-block-latest-posts__post-date::before { - content: " - "; + content: ' - '; } .is-reader-full-post { - background: var(--color-surface); + background: var( --color-surface ); } .reader-full-post { + .reader-full-post__header--recent { + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto auto; + align-items: start; + + .reader-full-post__header-site-icon { + grid-column: 1; + margin-right: 30px; + grid-row: 1 / 3; + align-self: center; + width: 80px; + + .reader-full-post__site-icon { + border-radius: 50%; + height: 80px; + width: 80px; + + &.is-missing-icon { + color: var( --color-neutral-70 ); + } + } + } + .reader-full-post__header-title { + grid-column: 2; + grid-row: 1; + } + + .reader-full-post__header-meta { + display: block; + grid-column: 2; + grid-row: 2; + + span { + line-height: 1.6; + margin-right: 20px; + color: var( --color-text-subtle ); + display: inline-block; + } + .reader-full-post__header-date { + margin-right: 0; + } + } + } + div.reader-full-post__author-block { .author-compact-profile { display: grid; grid-template-columns: auto 1fr; grid-template-areas: - "avatar links" - "avatar follow"; + 'avatar links' + 'avatar follow'; column-gap: 5px; row-gap: 5px; margin: 20px 0 0 0; @@ -671,19 +724,19 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po text-overflow: clip; white-space: nowrap; - &:not(.has-author-icon).has-author-link { + &:not( .has-author-icon ).has-author-link { grid-template-areas: - "links" - "follow"; + 'links' + 'follow'; } - &:not(.has-author-link):not(.has-author-icon) { + &:not( .has-author-link ):not( .has-author-icon ) { .reader-avatar { margin: 5px 0 0 0; } } - @media (min-width: 1300px) { + @media ( min-width: 1300px ) { display: none; } @@ -696,12 +749,12 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po position: relative; top: 2px; - @include breakpoint-deprecated( "<480px" ) { + @include breakpoint-deprecated( '<480px' ) { position: relative; top: 5px; } - &:not(.has-site-and-author-icon) img { + &:not( .has-site-and-author-icon ) img { margin-left: -17px; top: 7px; } @@ -722,7 +775,7 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po height: 32px !important; width: 32px !important; line-height: 32px !important; - font-size: rem(32px) !important; + font-size: rem( 32px ) !important; margin: 0 2px; } } @@ -740,12 +793,12 @@ $reader-full-post-desktop-min: $reader-full-post-sidebar-width + $reader-full-po } .reader-author-link { - @include breakpoint-deprecated( "<480px" ) { + @include breakpoint-deprecated( '<480px' ) { display: none; } &::after { - content: ", "; + content: ', '; font-weight: 400; white-space: pre; } diff --git a/client/blocks/site-address-changer/index.jsx b/client/blocks/site-address-changer/index.jsx index 04460cfd625a4b..9e840826502c67 100644 --- a/client/blocks/site-address-changer/index.jsx +++ b/client/blocks/site-address-changer/index.jsx @@ -553,10 +553,10 @@ export class SiteAddressChanger extends Component { return ( { 0 === this.state.step && this.renderNewAddressForm() } { 1 === this.state.step && this.renderConfirmationForm() } diff --git a/client/blocks/site-address-changer/style.scss b/client/blocks/site-address-changer/style.scss index 9c91d29b0e090f..fde0d657ad59be 100644 --- a/client/blocks/site-address-changer/style.scss +++ b/client/blocks/site-address-changer/style.scss @@ -1,21 +1,6 @@ @import "@automattic/typography/styles/variables"; @import "@wordpress/base-styles/colors.native"; -.dialog__content.site-address-changer { - padding: 48px 48px 0; - max-width: 800px; - -} - -.dialog__action-buttons { - border: 0; - padding: 35px 48px 48px; - - &::before { - display: none; - } -} - .site-address-changer__dialog { text-align: left; diff --git a/client/components/dataviews/style.scss b/client/components/dataviews/style.scss index cf79bbc407f16f..414671b95aae7e 100644 --- a/client/components/dataviews/style.scss +++ b/client/components/dataviews/style.scss @@ -153,6 +153,11 @@ } } } + + // Color overrides for focus states of Action button + .components-button:focus:not(:disabled) { + --wp-components-color-accent: var(--color-primary-light); + } } // TODO: remove when updating to dataviews@4.3.0. @@ -223,6 +228,10 @@ // Follow-up issue: https://github.com/Automattic/dotcom-forge/issues/9554 body.is-section-plugins .plugin-management-wrapper { --wp-components-color-accent: var(--color-accent); + + .dataviews-loading { + padding: 15px 0 0 0; + } } body.is-section-plugins .plugin-management-wrapper, .is-section-jetpack-cloud-plugin-management { @@ -232,4 +241,4 @@ body.is-section-plugins .plugin-management-wrapper, padding-left: 0; } } -} +} \ No newline at end of file diff --git a/client/components/email-verification/index.js b/client/components/email-verification/index.js index 8479d6a0860343..97b9721a3b1d3c 100644 --- a/client/components/email-verification/index.js +++ b/client/components/email-verification/index.js @@ -12,6 +12,7 @@ export default function emailVerification( context, next ) { const showNewEmailNotice = '1' === context.query.new_email_result; if ( showVerifiedNotice ) { + context.page.replace( removeQueryArgs( context.canonicalPath, 'verified' ) ); sendVerificationSignal(); setTimeout( () => { const message = i18n.translate( 'Email confirmed!' ); diff --git a/client/data/site-migration/landing/logger.ts b/client/data/site-migration/landing/logger.ts new file mode 100644 index 00000000000000..7580edcf2cd010 --- /dev/null +++ b/client/data/site-migration/landing/logger.ts @@ -0,0 +1,38 @@ +import config from '@automattic/calypso-config'; +import debug from 'debug'; +import { logToLogstash } from 'calypso/lib/logstash'; +import type { SiteId } from 'calypso/types'; + +const debugLog = debug( 'calypso:data:site-migration' ); + +function safeLogToLogstash( params: Parameters< typeof logToLogstash >[ 0 ] ): void { + if ( process.env.NODE_ENV === 'test' ) { + return; + } + + try { + logToLogstash( params ); + } catch ( error ) { + // Fail silently + } +} + +interface LogParams { + message: string; + siteId?: SiteId | null; + extra?: Parameters< typeof logToLogstash >[ 0 ][ 'extra' ]; +} + +export const log = ( { message, siteId, extra }: LogParams ) => { + debugLog( message, siteId ); + safeLogToLogstash( { + feature: 'calypso_client', + tags: [ 'site-migration' ], + message, + site_id: siteId || undefined, + extra, + properties: { + env: config( 'env_id' ), + }, + } ); +}; diff --git a/client/data/site-migration/landing/types.ts b/client/data/site-migration/landing/types.ts new file mode 100644 index 00000000000000..64025e7583e772 --- /dev/null +++ b/client/data/site-migration/landing/types.ts @@ -0,0 +1,8 @@ +export enum MigrationStatus { + // Do it for me + PENDING_DIFM = 'migration-pending-difm', + STARTED_DIFM = 'migration-started-difm', + // Do it yourself + PENDING_DIY = 'migration-pending-diy', + STARTED_DIY = 'migration-started-diy', +} diff --git a/client/data/site-migration/landing/use-migration-cancellation/index.ts b/client/data/site-migration/landing/use-migration-cancellation/index.ts new file mode 100644 index 00000000000000..9107260725d3dc --- /dev/null +++ b/client/data/site-migration/landing/use-migration-cancellation/index.ts @@ -0,0 +1,48 @@ +import { isEnabled } from '@automattic/calypso-config'; +import { useMutation } from '@tanstack/react-query'; +import wp from 'calypso/lib/wp'; +import { SiteId } from 'calypso/types'; +import { log } from '../logger'; + +const request = async ( { siteId }: { siteId: SiteId } ): Promise< Response > => { + if ( ! isEnabled( 'automated-migration/pending-status' ) ) { + return { status: 'skipped' }; + } + + await wp.req.post( { + path: `/sites/${ siteId }/site-migration-status-sticker`, + apiNamespace: 'wpcom/v2', + method: 'DELETE', + body: { + type: 'pending', + }, + } ); + + return { status: 'success' }; +}; + +interface Response { + status: 'success' | 'skipped'; +} + +export const useMigrationCancellation = ( siteId: SiteId | undefined ) => { + return useMutation< Response, Error >( { + mutationKey: [ 'migration-cancellation', siteId ], + mutationFn: () => { + if ( ! siteId ) { + throw new Error( 'Site ID is required' ); + } + + return request( { siteId } ); + }, + onError: ( error ) => { + log( { + message: 'Error cancelling the migration', + siteId, + extra: { + error: error.message, + }, + } ); + }, + } ); +}; diff --git a/client/data/site-migration/landing/use-migration-cancellation/test/index.tsx b/client/data/site-migration/landing/use-migration-cancellation/test/index.tsx new file mode 100644 index 00000000000000..1aa2547a14f1dd --- /dev/null +++ b/client/data/site-migration/landing/use-migration-cancellation/test/index.tsx @@ -0,0 +1,48 @@ +/** + * @jest-environment jsdom + */ + +import { isEnabled } from '@automattic/calypso-config'; +import { waitFor } from '@testing-library/react'; +import { when } from 'jest-when'; +import nock from 'nock'; +import { useMigrationCancellation } from 'calypso/data/site-migration/landing/use-migration-cancellation'; +import { renderHookWithProvider } from 'calypso/test-helpers/testing-library'; + +jest.mock( '@automattic/calypso-config' ); + +describe( 'useMigrationCancellation', () => { + beforeEach( () => { + when( isEnabled ).calledWith( 'automated-migration/pending-status' ).mockReturnValue( true ); + nock.disableNetConnect(); + } ); + + it( 'cancels the migration', async () => { + nock( 'https://public-api.wordpress.com' ) + .delete( '/wpcom/v2/sites/123/site-migration-status-sticker', { + type: 'pending', + } ) + .reply( 200, { success: true } ); + + const { result } = renderHookWithProvider( () => useMigrationCancellation( 123 ) ); + + result.current.mutate(); + + await waitFor( () => { + expect( nock.isDone() ).toBe( true ); + expect( result.current.data ).toEqual( { status: 'success' } ); + } ); + } ); + + it( 'skips the migration cancellation when the feature is off', async () => { + when( isEnabled ).calledWith( 'automated-migration/pending-status' ).mockReturnValue( false ); + + const { result } = renderHookWithProvider( () => useMigrationCancellation( 123 ) ); + + result.current.mutate(); + + await waitFor( () => { + expect( result.current.data ).toEqual( { status: 'skipped' } ); + } ); + } ); +} ); diff --git a/client/data/site-migration/landing/use-update-migration-status/index.ts b/client/data/site-migration/landing/use-update-migration-status/index.ts new file mode 100644 index 00000000000000..560fde02b21f9b --- /dev/null +++ b/client/data/site-migration/landing/use-update-migration-status/index.ts @@ -0,0 +1,65 @@ +import { isEnabled } from '@automattic/calypso-config'; +import { useMutation } from '@tanstack/react-query'; +import wp from 'calypso/lib/wp'; +import { SiteId } from 'calypso/types'; +import { log } from '../logger'; +import { MigrationStatus } from '../types'; + +const shouldSkipStatus = ( status: MigrationStatus ) => { + const isPendingStatusEnabled = isEnabled( 'automated-migration/pending-status' ); + const isPendingStatus = status.includes( 'pending' ); + + return isPendingStatus && ! isPendingStatusEnabled; +}; + +const request = async ( { + siteId, + status, +}: { + siteId: SiteId; + status: MigrationStatus; +} ): Promise< Response > => { + if ( shouldSkipStatus( status ) ) { + return { status: 'skipped' }; + } + + await wp.req.post( { + path: `/sites/${ siteId }/site-migration-status-sticker`, + apiNamespace: 'wpcom/v2', + body: { + status_sticker: status, + }, + } ); + + return { status: 'success' }; +}; + +interface Response { + status: 'success' | 'skipped'; +} + +interface Variables { + status: MigrationStatus; +} + +export const useUpdateMigrationStatus = ( siteId: SiteId | undefined ) => { + return useMutation< Response, Error, Variables >( { + mutationKey: [ 'migration-status', siteId ], + mutationFn: ( { status } ) => { + if ( ! siteId ) { + throw new Error( 'Site ID is required' ); + } + + return request( { siteId, status } ); + }, + onError: ( error ) => { + log( { + message: 'Error updating migration status', + siteId, + extra: { + error: error.message, + }, + } ); + }, + } ); +}; diff --git a/client/data/site-migration/landing/use-update-migration-status/test/index.tsx b/client/data/site-migration/landing/use-update-migration-status/test/index.tsx new file mode 100644 index 00000000000000..2a8653dcacf260 --- /dev/null +++ b/client/data/site-migration/landing/use-update-migration-status/test/index.tsx @@ -0,0 +1,49 @@ +/** + * @jest-environment jsdom + */ + +import { isEnabled } from '@automattic/calypso-config'; +import { waitFor } from '@testing-library/react'; +import { when } from 'jest-when'; +import nock from 'nock'; +import { MigrationStatus } from 'calypso/data/site-migration/landing/types'; +import { useUpdateMigrationStatus } from 'calypso/data/site-migration/landing/use-update-migration-status'; +import { renderHookWithProvider } from 'calypso/test-helpers/testing-library'; + +jest.mock( '@automattic/calypso-config' ); + +describe( 'useUpdateMigrationStatus', () => { + beforeEach( () => { + when( isEnabled ).calledWith( 'automated-migration/pending-status' ).mockReturnValue( true ); + nock.disableNetConnect(); + } ); + + it( 'updates the migration status to started', async () => { + nock( 'https://public-api.wordpress.com' ) + .post( '/wpcom/v2/sites/123/site-migration-status-sticker', { + status_sticker: MigrationStatus.STARTED_DIFM, + } ) + .reply( 200, { success: true } ); + + const { result } = renderHookWithProvider( () => useUpdateMigrationStatus( 123 ) ); + + result.current.mutate( { status: MigrationStatus.STARTED_DIFM } ); + + await waitFor( () => { + expect( nock.isDone() ).toBe( true ); + expect( result.current.data ).toEqual( { status: 'success' } ); + } ); + } ); + + it( 'skips the status update when the status contains migration-pending- and the feature is off', async () => { + when( isEnabled ).calledWith( 'automated-migration/pending-status' ).mockReturnValue( false ); + + const { result } = renderHookWithProvider( () => useUpdateMigrationStatus( 123 ) ); + + result.current.mutate( { status: MigrationStatus.PENDING_DIFM } ); + + await waitFor( () => { + expect( result.current.data ).toEqual( { status: 'skipped' } ); + } ); + } ); +} ); diff --git a/client/data/site-migration/use-update-migration-status.ts b/client/data/site-migration/use-update-migration-status.ts deleted file mode 100644 index cdc9aa9fc50e02..00000000000000 --- a/client/data/site-migration/use-update-migration-status.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useCallback } from 'react'; -import wp from 'calypso/lib/wp'; -import { SiteId } from 'calypso/types'; - -interface MigrationStatusMutationOptions { - targetBlogId: SiteId; - statusSticker: string; -} - -export const useUpdateMigrationStatus = () => { - const updateStatusMutation = useMutation( { - mutationFn: ( { targetBlogId, statusSticker }: MigrationStatusMutationOptions ) => - wp.req.post( { - path: `/sites/${ targetBlogId }/site-migration-status-sticker`, - apiNamespace: 'wpcom/v2', - body: { - status_sticker: statusSticker, - }, - } ), - } ); - - const { - mutate: updateMigrationStatusMutate, - mutateAsync: updateMigrationStatusMutateAsync, - ...updateStatusMutationRest - } = updateStatusMutation; - - const updateMigrationStatus = useCallback( - ( targetBlogId: SiteId, statusSticker: string ) => - updateMigrationStatusMutate( { targetBlogId, statusSticker } ), - [ updateMigrationStatusMutate ] - ); - - const updateMigrationStatusAsync = useCallback( - ( targetBlogId: SiteId, statusSticker: string ) => - updateMigrationStatusMutateAsync( { targetBlogId, statusSticker } ), - [ updateMigrationStatusMutateAsync ] - ); - - return { updateMigrationStatus, updateMigrationStatusAsync, updateStatusMutationRest }; -}; diff --git a/client/hosting/server-settings/main.tsx b/client/hosting/server-settings/main.tsx index d2b8092191413a..d4a51750a75fe4 100644 --- a/client/hosting/server-settings/main.tsx +++ b/client/hosting/server-settings/main.tsx @@ -285,12 +285,14 @@ const ServerSettings = ( { fetchUpdatedData }: ServerSettingsProps ) => { }; /* We want to show the upsell banner for the following cases: - * 1. The site does not have the Atomic feature. - * 2. The site is Atomic, is not transferring, and doesn't have advanced hosting features. + * 1. The site is on an eCommerce trial. + * 2. The site does not have the Atomic feature. + * 3. The site is Atomic, is not transferring, and doesn't have advanced hosting features. * Otherwise, we show the activation notice, which may be empty. */ const shouldShowUpgradeBanner = - ! hasAtomicFeature || ( ! hasTransfer && ! hasSftpFeature && ! isWpcomStagingSite ); + ( ! isLoadingSftpData || isECommerceTrial ) && + ( ! hasAtomicFeature || ( ! hasTransfer && ! hasSftpFeature && ! isWpcomStagingSite ) ); const banner = shouldShowUpgradeBanner ? getUpgradeBanner() : getAtomicActivationNotice(); return ( diff --git a/client/hosting/server-settings/test/index.js b/client/hosting/server-settings/test/index.js index ee66b58d63dfc5..20102cb9ebd453 100644 --- a/client/hosting/server-settings/test/index.js +++ b/client/hosting/server-settings/test/index.js @@ -67,6 +67,7 @@ const createTestStore = ( { atomicHosting: { [ TEST_SITE_ID ]: { isLoadingSftpUsers: false, + isLoadingSshAccess: false, }, }, automatedTransfer: { diff --git a/client/jetpack-cloud/sections/jetpack-social/connections.jsx b/client/jetpack-cloud/sections/jetpack-social/connections.jsx index 93e8ebbafea925..b63cfe87a6106d 100644 --- a/client/jetpack-cloud/sections/jetpack-social/connections.jsx +++ b/client/jetpack-cloud/sections/jetpack-social/connections.jsx @@ -8,12 +8,12 @@ import QueryKeyringServices from 'calypso/components/data/query-keyring-services import QueryPublicizeConnections from 'calypso/components/data/query-publicize-connections'; import FormattedHeader from 'calypso/components/formatted-header'; import Main from 'calypso/components/main'; -import SharingServicesGroup from 'calypso/my-sites/marketing/connections/services-group'; +import SharingServicesGroup from 'calypso/sites/marketing/connections/services-group'; import { useSelector } from 'calypso/state'; import { isSimpleSite } from 'calypso/state/sites/selectors'; import { getSelectedSiteId } from 'calypso/state/ui/selectors'; -import 'calypso/my-sites/marketing/style.scss'; +import 'calypso/sites/marketing/style.scss'; import './style.scss'; export const Connections = () => { diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/importer-migrate-message/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/importer-migrate-message/index.tsx index 1c34c21d5baf74..a9adf5e3f3ff02 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/importer-migrate-message/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/importer-migrate-message/index.tsx @@ -8,9 +8,9 @@ import { Icon, globe, group, shield, backup, scheduled } from '@wordpress/icons' import { createElement, useEffect } from 'react'; import FormattedHeader from 'calypso/components/formatted-header'; import { LoadingEllipsis } from 'calypso/components/loading-ellipsis'; -import { useUpdateMigrationStatus } from 'calypso/data/site-migration/use-update-migration-status'; +import { MigrationStatus } from 'calypso/data/site-migration/landing/types'; +import { useUpdateMigrationStatus } from 'calypso/data/site-migration/landing/use-update-migration-status'; import { Step } from 'calypso/landing/stepper/declarative-flow/internals/types'; -import './style.scss'; import { useQuery } from 'calypso/landing/stepper/hooks/use-query'; import { useSite } from 'calypso/landing/stepper/hooks/use-site'; import { useSiteSlugParam } from 'calypso/landing/stepper/hooks/use-site-slug-param'; @@ -19,6 +19,7 @@ import { UserData } from 'calypso/lib/user/user'; import { useSelector } from 'calypso/state'; import { getCurrentUser } from 'calypso/state/current-user/selectors'; import { useSubmitMigrationTicket } from './hooks/use-submit-migration-ticket'; +import './style.scss'; interface WhatToExpectProps { icon: JSX.Element; @@ -52,13 +53,13 @@ const ImporterMigrateMessage: Step = ( { navigation } ) => { }, } ); - const { updateMigrationStatus } = useUpdateMigrationStatus(); const site = useSite(); const siteId = site?.ID; + const { mutate: updateMigrationStatus } = useUpdateMigrationStatus( siteId ); useEffect( () => { if ( siteId ) { - updateMigrationStatus( siteId, 'migration-started-difm' ); + updateMigrationStatus( { status: MigrationStatus.STARTED_DIFM } ); } }, [ siteId, updateMigrationStatus ] ); diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/index.tsx index 73714a728bb647..d42b11f924316d 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/index.tsx @@ -1,8 +1,12 @@ import { StepContainer } from '@automattic/onboarding'; import { useTranslate } from 'i18n-calypso'; +import { useEffect } from 'react'; import { UrlData } from 'calypso/blocks/import/types'; import DocumentHead from 'calypso/components/data/document-head'; import FormattedHeader from 'calypso/components/formatted-header'; +import { MigrationStatus } from 'calypso/data/site-migration/landing/types'; +import { useUpdateMigrationStatus } from 'calypso/data/site-migration/landing/use-update-migration-status'; +import { useSiteIdParam } from 'calypso/landing/stepper/hooks/use-site-id-param'; import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; import { CredentialsForm } from './components/credentials-form'; import type { Step } from '../../types'; @@ -26,6 +30,9 @@ const getAction = ( siteInfo?: UrlData ) => { const SiteMigrationCredentials: Step = function ( { navigation } ) { const translate = useTranslate(); + const siteId = parseInt( useSiteIdParam() ?? '' ); + + const { mutate: updateMigrationStatus } = useUpdateMigrationStatus( siteId ); const handleSubmit = ( siteInfo?: UrlData | undefined ) => { const action = getAction( siteInfo ); @@ -38,6 +45,12 @@ const SiteMigrationCredentials: Step = function ( { navigation } ) { } ); }; + useEffect( () => { + if ( siteId ) { + updateMigrationStatus( { status: MigrationStatus.PENDING_DIFM } ); + } + }, [ siteId, updateMigrationStatus ] ); + return ( <> diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/test/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/test/index.tsx index 8e192c1c7657f8..fa8aef441ab99d 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/test/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/test/index.tsx @@ -6,6 +6,7 @@ import userEvent from '@testing-library/user-event'; import nock from 'nock'; import React from 'react'; import wpcomRequest from 'wpcom-proxy-request'; +import { MigrationStatus } from 'calypso/data/site-migration/landing/types'; import { useSiteSlugParam } from 'calypso/landing/stepper/hooks/use-site-slug-param'; import wp from 'calypso/lib/wp'; import SiteMigrationCredentials from '..'; @@ -15,6 +16,7 @@ import { RenderStepOptions, mockStepProps, renderStep } from '../../test/helpers jest.mock( 'calypso/lib/wp', () => ( { req: { get: jest.fn(), + post: jest.fn(), }, } ) ); @@ -218,6 +220,19 @@ describe( 'SiteMigrationCredentials', () => { } ); } ); + it( 'sets a migration as pending automatically', async () => { + render(); + + await waitFor( () => { + expect( wp.req.post ).toHaveBeenCalledWith( + expect.objectContaining( { + path: '/sites/123/site-migration-status-sticker', + body: { status_sticker: MigrationStatus.PENDING_DIFM }, + } ) + ); + } ); + } ); + it( 'skips the credential creation when the user does not fill the fields', async () => { const submit = jest.fn(); render( { navigation: { submit } } ); diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-how-to-migrate/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-how-to-migrate/index.tsx index 4faad7eb108f1c..23e096f8d55dc6 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-how-to-migrate/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-how-to-migrate/index.tsx @@ -1,9 +1,10 @@ import { useHasEnTranslation } from '@automattic/i18n-utils'; import { StepContainer } from '@automattic/onboarding'; import { useTranslate } from 'i18n-calypso'; -import { FC, useMemo } from 'react'; +import { FC, useCallback, useMemo } from 'react'; import DocumentHead from 'calypso/components/data/document-head'; import FormattedHeader from 'calypso/components/formatted-header'; +import { useMigrationCancellation } from 'calypso/data/site-migration/landing/use-migration-cancellation'; import { useAnalyzeUrlQuery } from 'calypso/data/site-profiler/use-analyze-url-query'; import { useHostingProviderQuery } from 'calypso/data/site-profiler/use-hosting-provider-query'; import { HOW_TO_MIGRATE_OPTIONS } from 'calypso/landing/stepper/constants'; @@ -26,6 +27,9 @@ const SiteMigrationHowToMigrate: FC< Props > = ( props ) => { const translate = useTranslate(); const importSiteQueryParam = useQuery().get( 'from' ) || ''; + const site = useSite(); + const { mutate: cancelMigration } = useMigrationCancellation( site?.ID ); + usePresalesChat( 'wpcom' ); const hasEnTranslation = useHasEnTranslation(); @@ -70,7 +74,6 @@ const SiteMigrationHowToMigrate: FC< Props > = ( props ) => { urlData ); - const site = useSite(); const handleClick = async ( value: string ) => { const canInstallPlugins = site?.plan?.features?.active.find( ( feature ) => feature === 'install-plugins' @@ -110,6 +113,11 @@ const SiteMigrationHowToMigrate: FC< Props > = ( props ) => { } ) : ''; + const goBack = useCallback( () => { + cancelMigration(); + navigation.goBack?.(); + }, [ cancelMigration, navigation ] ); + return ( <> @@ -128,7 +136,7 @@ const SiteMigrationHowToMigrate: FC< Props > = ( props ) => { } stepContent={ stepContent } recordTracksEvent={ recordTracksEvent } - goBack={ navigation.goBack } + goBack={ goBack } /> ); diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-how-to-migrate/use-pending-migration-status.ts b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-how-to-migrate/use-pending-migration-status.ts deleted file mode 100644 index abf1c886ee785b..00000000000000 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-how-to-migrate/use-pending-migration-status.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useEffect } from 'react'; -import { useUpdateMigrationStatus } from 'calypso/data/site-migration/use-update-migration-status'; -import { HOW_TO_MIGRATE_OPTIONS } from 'calypso/landing/stepper/constants'; -import { useSite } from 'calypso/landing/stepper/hooks/use-site'; -import type { NavigationControls } from '../../types'; - -interface PendingMigrationStatusProps { - onSubmit?: Pick< NavigationControls, 'submit' >[ 'submit' ]; -} - -const usePendingMigrationStatus = ( { onSubmit }: PendingMigrationStatusProps ) => { - const site = useSite(); - const siteId = site?.ID; - - const canInstallPlugins = site?.plan?.features?.active.find( - ( feature ) => feature === 'install-plugins' - ) - ? true - : false; - - const { - updateMigrationStatus, - updateMigrationStatusAsync, - updateStatusMutationRest: { - isIdle: isMigrationStatusUpdateIdle, - isPending: isMigrationStatusUpdatePending, - }, - } = useUpdateMigrationStatus(); - - const isLoading = isMigrationStatusUpdateIdle || isMigrationStatusUpdatePending; - - // Register pending migration status when loading the step. - useEffect( () => { - if ( siteId ) { - updateMigrationStatus( siteId, 'migration-pending' ); - } - }, [ siteId, updateMigrationStatus ] ); - - const setPendingMigration = async ( how: string ) => { - const destination = canInstallPlugins ? 'migrate' : 'upgrade'; - if ( siteId ) { - const parsedHow = how === HOW_TO_MIGRATE_OPTIONS.DO_IT_MYSELF ? 'diy' : how; - await updateMigrationStatusAsync( siteId, `migration-pending-${ parsedHow }` ); - } - - if ( onSubmit ) { - return onSubmit( { how, destination } ); - } - }; - - return { setPendingMigration, isLoading }; -}; - -export default usePendingMigrationStatus; diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-import-or-migrate/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-import-or-migrate/index.tsx index 8efa094ce99ebf..a251aba2a562e2 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-import-or-migrate/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-import-or-migrate/index.tsx @@ -6,6 +6,7 @@ import { getQueryArg } from '@wordpress/url'; import { useTranslate } from 'i18n-calypso'; import DocumentHead from 'calypso/components/data/document-head'; import FormattedHeader from 'calypso/components/formatted-header'; +import { useMigrationCancellation } from 'calypso/data/site-migration/landing/use-migration-cancellation'; import { useMigrationStickerMutation } from 'calypso/data/site-migration/use-migration-sticker'; import { useHostingProviderUrlDetails } from 'calypso/data/site-profiler/use-hosting-provider-url-details'; import { useSite } from 'calypso/landing/stepper/hooks/use-site'; @@ -19,6 +20,7 @@ const SiteMigrationImportOrMigrate: Step = function ( { navigation } ) { const site = useSite(); const importSiteQueryParam = getQueryArg( window.location.href, 'from' )?.toString() || ''; const { deleteMigrationSticker } = useMigrationStickerMutation(); + const { mutate: cancelMigration } = useMigrationCancellation( site?.ID ); const options = [ { @@ -56,7 +58,10 @@ const SiteMigrationImportOrMigrate: Step = function ( { navigation } ) { } if ( destination === 'import' && site && site.ID ) { + //TODO: This is a temporary solution to delete the migration sticker and the migration flow. + // We should refactor this to use a single endpoint to handle both operations. deleteMigrationSticker( site.ID ); + cancelMigration(); } return navigation.submit?.( { destination } ); diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-instructions/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-instructions/index.tsx index 882a4517e2534b..55f2605eb6e461 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-instructions/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-instructions/index.tsx @@ -1,10 +1,12 @@ +import { isEnabled } from '@automattic/calypso-config'; import { captureException } from '@automattic/calypso-sentry'; import { CircularProgressBar } from '@automattic/components'; import { LaunchpadContainer } from '@automattic/launchpad'; import { StepContainer } from '@automattic/onboarding'; import { useCallback, useEffect } from 'react'; +import { MigrationStatus } from 'calypso/data/site-migration/landing/types'; +import { useUpdateMigrationStatus } from 'calypso/data/site-migration/landing/use-update-migration-status'; import { useMigrationStickerMutation } from 'calypso/data/site-migration/use-migration-sticker'; -import { useUpdateMigrationStatus } from 'calypso/data/site-migration/use-update-migration-status'; import { useHostingProviderUrlDetails } from 'calypso/data/site-profiler/use-hosting-provider-url-details'; import { usePrepareSiteForMigration } from 'calypso/landing/stepper/hooks/use-prepare-site-for-migration'; import { useQuery } from 'calypso/landing/stepper/hooks/use-query'; @@ -82,15 +84,22 @@ const SiteMigrationInstructions: Step = function ( { navigation, flow } ) { const queryParams = useQuery(); const fromUrl = queryParams.get( 'from' ) ?? ''; - const { updateMigrationStatus } = useUpdateMigrationStatus(); + const { mutate: updateMigrationStatus } = useUpdateMigrationStatus( siteId ); + useEffect( () => { if ( siteId ) { - updateMigrationStatus( siteId, 'migration-started-diy' ); + //TODO: We can stop to set the status to STARTED_DIY when the feature is enabled. + const status = isEnabled( 'automated-migration/pending-status' ) + ? MigrationStatus.PENDING_DIY + : MigrationStatus.STARTED_DIY; + + updateMigrationStatus( { status } ); } }, [ siteId, updateMigrationStatus ] ); // Delete migration sticker. const { deleteMigrationSticker } = useMigrationStickerMutation(); + useEffect( () => { if ( siteId ) { deleteMigrationSticker( siteId ); @@ -104,6 +113,7 @@ const SiteMigrationInstructions: Step = function ( { navigation, flow } ) { error: preparationError, migrationKey, } = usePrepareSiteForMigration( siteId ); + const migrationKeyStatus = detailedStatus.migrationKey; // Register events and logs. @@ -132,6 +142,7 @@ const SiteMigrationInstructions: Step = function ( { navigation, flow } ) { window.removeEventListener( 'beforeunload', preventUnload ); }; }, [ preparationCompleted, preventUnload ] ); + // Hosting details. const { data: hostingDetails } = useHostingProviderUrlDetails( fromUrl ); const showHostingBadge = ! hostingDetails.is_unknown && ! hostingDetails.is_a8c; diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-instructions/test/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-instructions/test/index.tsx index 200723d5a685ef..60ec0a5c464210 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-instructions/test/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-instructions/test/index.tsx @@ -1,14 +1,17 @@ /** * @jest-environment jsdom */ +import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { MigrationStatus } from 'calypso/data/site-migration/landing/types'; import { useMigrationStickerMutation } from 'calypso/data/site-migration/use-migration-sticker'; import { useHostingProviderUrlDetails } from 'calypso/data/site-profiler/use-hosting-provider-url-details'; import { usePrepareSiteForMigration } from 'calypso/landing/stepper/hooks/use-prepare-site-for-migration'; import { useQuery } from 'calypso/landing/stepper/hooks/use-query'; import { useSite } from 'calypso/landing/stepper/hooks/use-site'; import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; +import wp from 'calypso/lib/wp'; import SiteMigrationInstructions from '..'; import { StepProps } from '../../../types'; import { mockStepProps, renderStep } from '../../test/helpers'; @@ -21,6 +24,7 @@ jest.mock( 'calypso/data/site-migration/use-migration-sticker' ); jest.mock( 'calypso/data/site-profiler/use-hosting-provider-url-details' ); jest.mock( '../site-preview' ); jest.mock( 'calypso/lib/analytics/tracks' ); +jest.mock( 'calypso/lib/wp' ); const mockGetQuery = ( from ) => { ( useQuery as jest.Mock ).mockReturnValue( { @@ -214,4 +218,17 @@ describe( 'SiteMigrationInstructions', () => { expect( skeleton!.classList.contains( 'migration-key-skeleton--animate' ) ).toBeFalsy(); } ); + + it( 'sets a migration as pending automatically', async () => { + render(); + + await waitFor( () => { + expect( wp.req.post ).toHaveBeenCalledWith( + expect.objectContaining( { + path: '/sites/123/site-migration-status-sticker', + body: { status_sticker: MigrationStatus.PENDING_DIY }, + } ) + ); + } ); + } ); } ); diff --git a/client/my-sites/checkout/checkout-thank-you/index.tsx b/client/my-sites/checkout/checkout-thank-you/index.tsx index 2f661143a0e459..712c5cef05b335 100644 --- a/client/my-sites/checkout/checkout-thank-you/index.tsx +++ b/client/my-sites/checkout/checkout-thank-you/index.tsx @@ -496,7 +496,7 @@ export class CheckoutThankYou extends Component< }; render() { - const { translate, email, receiptId, selectedFeature } = this.props; + const { translate, email, selectedFeature } = this.props; const purchases = getPurchases( this.props ).filter( ( purchase ) => ! isCredits( purchase ) ); let wasJetpackPlanPurchased = false; let wasEcommercePlanPurchased = false; @@ -552,7 +552,6 @@ export class CheckoutThankYou extends Component< pageContent = ( ); diff --git a/client/my-sites/checkout/checkout-thank-you/redesign-v2/pages/domain-only.tsx b/client/my-sites/checkout/checkout-thank-you/redesign-v2/pages/domain-only.tsx index 2559ec86e8b56e..5912ca511ef14e 100644 --- a/client/my-sites/checkout/checkout-thank-you/redesign-v2/pages/domain-only.tsx +++ b/client/my-sites/checkout/checkout-thank-you/redesign-v2/pages/domain-only.tsx @@ -1,12 +1,6 @@ -import { Button, SelectControl } from '@wordpress/components'; import { useTranslate } from 'i18n-calypso'; -import { useState } from 'react'; -import emailImage from 'calypso/assets/images/thank-you-upsell/email.jpg'; import QuerySites from 'calypso/components/data/query-sites'; import ThankYouV2 from 'calypso/components/thank-you-v2'; -import { ThankYouUpsellProps } from 'calypso/components/thank-you-v2/upsell'; -import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; -import { getProfessionalEmailCheckoutUpsellPath } from 'calypso/my-sites/email/paths'; import { useSelector } from 'calypso/state'; import { getSite } from 'calypso/state/sites/selectors'; import { getDomainPurchaseTypeAndPredicate } from '../../utils'; @@ -14,60 +8,13 @@ import ThankYouDomainProduct from '../products/domain-product'; import getDomainFooterDetails from './content/get-domain-footer-details'; import type { ReceiptPurchase } from 'calypso/state/receipts/types'; -function UpsellActions( { - domainNames, - receiptId, - siteSlug, -}: { - domainNames: string[]; - receiptId: number; - siteSlug?: string; -} ) { - const translate = useTranslate(); - const [ selectedDomainName, setSelectedDomainName ] = useState( domainNames[ 0 ] ); - - const domainNameOptions = domainNames.map( ( domainName ) => ( { - label: domainName, - value: domainName, - } ) ); - - const addEmailButtonHref = - siteSlug && getProfessionalEmailCheckoutUpsellPath( siteSlug, selectedDomainName, receiptId ); - const addEmailButtonProps = addEmailButtonHref - ? { href: addEmailButtonHref } - : { disabled: true }; - - return ( - <> - { domainNames.length > 1 ? ( - setSelectedDomainName( value ) } - /> - ) : null } - - - - ); -} - interface DomainOnlyThankYouProps { purchases: ReceiptPurchase[]; - receiptId: number; isGravatarDomain: boolean; } export default function DomainOnlyThankYou( { purchases, - receiptId, isGravatarDomain, }: DomainOnlyThankYouProps ) { const translate = useTranslate(); @@ -76,29 +23,6 @@ export default function DomainOnlyThankYou( { const domainNames = domainPurchases.map( ( purchase ) => purchase?.meta ); const domainOnlySite = useSelector( ( state ) => getSite( state, domainPurchases[ 0 ]?.blogId ) ); - const upsellProps: ThankYouUpsellProps = { - title: translate( 'Professional email' ), - description: ( - <> - { translate( - 'Establish credibility and build trust by using a custom email address.{{br /}}Studies show that 85% of people trust custom domain email addresses more than generic ones.', - { - comment: 'Upsell for Professional Email on checkout thank you page', - components: { br:
}, - } - ) } - - ), - image: emailImage, - actions: ( - - ), - }; - const products = domainPurchases.map( ( purchase ) => { return ( diff --git a/client/my-sites/importer/author-mapping-pane.jsx b/client/my-sites/importer/author-mapping-pane.jsx index e6e2b4373c021f..a0ce52a6be56b7 100644 --- a/client/my-sites/importer/author-mapping-pane.jsx +++ b/client/my-sites/importer/author-mapping-pane.jsx @@ -48,7 +48,7 @@ class AuthorMappingPane extends PureComponent { 'There is one author on your %(sourceType)s site. ' + "Because you're the only author on {{b}}%(destinationSiteTitle)s{{/b}}, " + 'all imported content will be assigned to you. ' + - 'Click Start import to proceed.', + 'Click {{em}}Import{{/em}} to proceed.', { args: { sourceType: sourceType, @@ -56,6 +56,7 @@ class AuthorMappingPane extends PureComponent { }, components: { b: , + em: , }, } ); @@ -64,7 +65,7 @@ class AuthorMappingPane extends PureComponent { 'There are multiple authors on your %(sourceType)s site. ' + "Because you're the only author on {{b}}%(destinationSiteTitle)s{{/b}}, " + 'all imported content will be assigned to you. ' + - 'Click {{em}}Start import{{/em}} to proceed.', + 'Click {{em}}Import{{/em}} to proceed.', { args: { sourceType: sourceType, @@ -80,7 +81,7 @@ class AuthorMappingPane extends PureComponent { return this.props.translate( 'There are multiple authors on your site. ' + 'Please reassign the authors of the imported items to an existing ' + - 'user on {{b}}%(destinationSiteTitle)s{{/b}}, then click {{em}}Start import{{/em}}.', + 'user on {{b}}%(destinationSiteTitle)s{{/b}}, then click {{em}}Import{{/em}}.', { args: { sourceType: 'WordPress', @@ -96,7 +97,7 @@ class AuthorMappingPane extends PureComponent { return this.props.translate( 'There are multiple authors on your %(sourceType)s site. ' + 'Please reassign the authors of the imported items to an existing ' + - 'user on {{b}}%(destinationSiteTitle)s{{/b}}, then click {{em}}Start import{{/em}}.', + 'user on {{b}}%(destinationSiteTitle)s{{/b}}, then click {{em}}Import{{/em}}.', { args: { sourceType: 'WordPress', @@ -158,7 +159,7 @@ class AuthorMappingPane extends PureComponent { } ) } - { this.props.translate( 'Start import' ) } + { this.props.translate( 'Import' ) } diff --git a/client/my-sites/importer/newsletter/content-upload/author-mapping-pane.jsx b/client/my-sites/importer/newsletter/content-upload/author-mapping-pane.jsx index c70d47f7ffb444..f50264c18791c6 100644 --- a/client/my-sites/importer/newsletter/content-upload/author-mapping-pane.jsx +++ b/client/my-sites/importer/newsletter/content-upload/author-mapping-pane.jsx @@ -121,7 +121,7 @@ class AuthorMappingPane extends PureComponent { 'There is one author on your %(sourceType)s site. ' + "Because you're the only author on {{b}}%(destinationSiteTitle)s{{/b}}, " + 'all imported content will be assigned to you. ' + - 'Click Start import to proceed.', + 'Click {{em}}Import{{/em}} to proceed.', { args: { sourceType: sourceType, @@ -129,6 +129,7 @@ class AuthorMappingPane extends PureComponent { }, components: { b: , + em: , }, } ); @@ -137,7 +138,7 @@ class AuthorMappingPane extends PureComponent { 'There are multiple authors on your %(sourceType)s site. ' + "Because you're the only author on {{b}}%(destinationSiteTitle)s{{/b}}, " + 'all imported content will be assigned to you. ' + - 'Click {{em}}Start import{{/em}} to proceed.', + 'Click {{em}}Import{{/em}} to proceed.', { args: { sourceType: sourceType, @@ -153,7 +154,7 @@ class AuthorMappingPane extends PureComponent { return this.props.translate( 'There are multiple authors on your site. ' + 'Please reassign the authors of the imported items to an existing ' + - 'user on {{b}}%(destinationSiteTitle)s{{/b}}, then click {{em}}Start import{{/em}}.', + 'user on {{b}}%(destinationSiteTitle)s{{/b}}, then click {{em}}Import{{/em}}.', { args: { sourceType: 'WordPress', @@ -169,7 +170,7 @@ class AuthorMappingPane extends PureComponent { return this.props.translate( 'There are multiple authors on your %(sourceType)s site. ' + 'Please reassign the authors of the imported items to an existing ' + - 'user on {{b}}%(destinationSiteTitle)s{{/b}}, then click {{em}}Start import{{/em}}.', + 'user on {{b}}%(destinationSiteTitle)s{{/b}}, then click {{em}}Import{{/em}}.', { args: { sourceType: 'WordPress', diff --git a/client/my-sites/marketing/controller.js b/client/my-sites/marketing/controller.js index ed2d2a406604da..12faff4725b319 100644 --- a/client/my-sites/marketing/controller.js +++ b/client/my-sites/marketing/controller.js @@ -1,6 +1,7 @@ import page from '@automattic/calypso-router'; import { translate } from 'i18n-calypso'; import { createElement } from 'react'; +import SharingConnections from 'calypso/sites/marketing/connections/connections'; import MarketingTools from 'calypso/sites/marketing/tools'; import { errorNotice } from 'calypso/state/notices/actions'; import { fetchPreferences } from 'calypso/state/preferences/actions'; @@ -12,7 +13,6 @@ import { requestSite } from 'calypso/state/sites/actions'; import { getSiteSlug } from 'calypso/state/sites/selectors'; import { getSelectedSiteId } from 'calypso/state/ui/selectors'; import SharingButtons from './buttons/buttons'; -import SharingConnections from './connections/connections'; import Sharing from './main'; import Traffic from './traffic/'; diff --git a/client/my-sites/marketing/main.jsx b/client/my-sites/marketing/main.jsx index 723fd604321475..865c9899a6c3c6 100644 --- a/client/my-sites/marketing/main.jsx +++ b/client/my-sites/marketing/main.jsx @@ -23,7 +23,7 @@ import { getSiteAdminUrl, } from 'calypso/state/sites/selectors'; import { getSelectedSiteId } from 'calypso/state/ui/selectors'; -import './style.scss'; +import 'calypso/sites/marketing/style.scss'; export const Sharing = ( { contentComponent, diff --git a/client/my-sites/media-library/content.jsx b/client/my-sites/media-library/content.jsx index 653c4ea72b42bb..3819a3a4279a2b 100644 --- a/client/my-sites/media-library/content.jsx +++ b/client/my-sites/media-library/content.jsx @@ -21,8 +21,8 @@ import { MEDIA_IMAGE_THUMBNAIL, SCALE_TOUCH_GRID, } from 'calypso/lib/media/constants'; -import InlineConnection from 'calypso/my-sites/marketing/connections/inline-connection'; import GooglePhotosPickerButton from 'calypso/my-sites/media-library/google-photos-picker-button'; +import InlineConnection from 'calypso/sites/marketing/connections/inline-connection'; import { pauseGuidedTour, resumeGuidedTour } from 'calypso/state/guided-tours/actions'; import { getGuidedTourState } from 'calypso/state/guided-tours/selectors'; import { clearMediaErrors, changeMediaSource } from 'calypso/state/media/actions'; diff --git a/client/my-sites/media-library/test/content.jsx b/client/my-sites/media-library/test/content.jsx index df11ed45c52477..a49c8f568ac7be 100644 --- a/client/my-sites/media-library/test/content.jsx +++ b/client/my-sites/media-library/test/content.jsx @@ -11,7 +11,7 @@ import { renderWithProvider } from 'calypso/test-helpers/testing-library'; const render = ( el, options ) => renderWithProvider( el, { ...options, reducers: { ui: uiReducer, media: mediaReducer } } ); -jest.mock( 'calypso/my-sites/marketing/connections/inline-connection', () => () => null ); +jest.mock( 'calypso/sites/marketing/connections/inline-connection', () => () => null ); const googleConnection = { service: 'google_photos', diff --git a/client/my-sites/plugins/main.jsx b/client/my-sites/plugins/main.jsx index 51515393f526ba..240c8820cbd941 100644 --- a/client/my-sites/plugins/main.jsx +++ b/client/my-sites/plugins/main.jsx @@ -10,7 +10,7 @@ import { subscribeIsWithinBreakpoint, isWithinBreakpoint } from '@automattic/vie import { Icon, upload } from '@wordpress/icons'; import clsx from 'clsx'; import { localize } from 'i18n-calypso'; -import { capitalize, flow, isEmpty } from 'lodash'; +import { filter as capitalize, flow, isEmpty } from 'lodash'; import { Component } from 'react'; import { connect } from 'react-redux'; import DocumentHead from 'calypso/components/data/document-head'; @@ -373,13 +373,11 @@ export class PluginsMain extends Component { header={ this.props.translate( 'Manage Plugins' ) } plugins={ this.getCurrentPlugins() } isPlaceholder={ this.shouldShowPluginListPlaceholders() } - isLoading={ this.props.requestingPluginsForSites } + isLoading={ this.props.requestingPluginsForSites || this.props.isLoadingSites } isJetpackCloud={ this.props.isJetpackCloud } searchTerm={ this.props.search } filter={ this.props.filter } requestPluginsError={ this.props.requestPluginsError } - activePlugins={ this.props.activePlugins } - inactivePlugins={ this.props.inactivePlugins } onSearch={ this.props.doSearch } /> ); @@ -624,17 +622,9 @@ export default flow( const selectedSite = getSelectedSite( state ); const selectedSiteId = getSelectedSiteId( state ); const siteIds = siteObjectsToSiteIds( sites ) ?? []; - const pluginsWithUpdates = getPlugins( state, siteIds, 'updates' ); - const activePlugins = getPlugins( state, siteIds, 'active' ); - const inactivePlugins = getPlugins( state, siteIds, 'inactive' ); + const isLoadingSites = isRequestingSites( state ); const allPlugins = getPlugins( state, siteIds, 'all' ); - const pluginsWithUpdatesAndStatuses = getPluginsWithUpdateStatuses( - state, - allPlugins, - pluginsWithUpdates, - inactivePlugins, - activePlugins - ); + const pluginsWithUpdatesAndStatuses = getPluginsWithUpdateStatuses( state, allPlugins ); const jetpackNonAtomic = isJetpackSite( state, selectedSiteId ) && ! isAtomicSite( state, selectedSiteId ); @@ -654,6 +644,7 @@ export default flow( sites, selectedSite, selectedSiteId, + isLoadingSites, selectedSiteSlug: getSelectedSiteSlug( state ), selectedSiteIsJetpack: selectedSite && isJetpackSite( state, selectedSiteId ), siteIds, @@ -667,10 +658,6 @@ export default flow( currentPluginsOnVisibleSites: newBulkPluginManagement ? [] : getPlugins( state, siteObjectsToSiteIds( getVisibleSites( sites ) ) ?? [], filter ), - pluginsWithUpdates, - pluginUpdateCount: pluginsWithUpdates && pluginsWithUpdates.length, - activePlugins, - inactivePlugins, allPluginsCount: allPlugins && allPlugins.length, requestingPluginsForSites: isRequestingForSites( state, siteIds ) || isRequestingForAllSites( state ), @@ -685,6 +672,9 @@ export default flow( breadcrumbs, requestPluginsError: requestPluginsError( state ), newBulkPluginManagement, + pluginUpdateCount: newBulkPluginManagement + ? 0 + : getPlugins( state, siteIds, 'updates' )?.length, }; }, { diff --git a/client/my-sites/plugins/plugins-list/style-compatibilty.scss b/client/my-sites/plugins/plugins-list/style-compatibilty.scss index 5e22b47c5344ba..56aa387c3a4019 100644 --- a/client/my-sites/plugins/plugins-list/style-compatibilty.scss +++ b/client/my-sites/plugins/plugins-list/style-compatibilty.scss @@ -1,3 +1,5 @@ +// ########### This file will be removed after we release the new Bulk Plugins Managament with DataViews ########### + .plugins-list { margin-bottom: 16px; } @@ -55,3 +57,12 @@ padding: 0; } +.is-section-jetpack-cloud-plugin-management { + .layout__content { + padding: 79px 32px 32px calc(var(--sidebar-width-max) + 32px); + } + + .plugins__top-container-jc { + padding-left: 50px; + } +} diff --git a/client/my-sites/plugins/plugins-list/style.scss b/client/my-sites/plugins/plugins-list/style.scss index 30fcb93d9e8f21..e02e2c699dd7e7 100644 --- a/client/my-sites/plugins/plugins-list/style.scss +++ b/client/my-sites/plugins/plugins-list/style.scss @@ -17,3 +17,36 @@ body.is-section-plugins .plugin-management-wrapper, } } } + +.is-section-jetpack-cloud-plugin-management { + --wp-admin-theme-color: var(--studio-jetpack-green-50); + + .select-partner-key { + padding: 0 40px; + } + + .dataviews-wrapper { + button { + border: none; + background-color: transparent; + + &:hover { + background-color: var(--color-background-alt); + } + } + + thead .dataviews-view-table__row button { + &:focus { + box-shadow: none; + } + } + } + + .layout__content { + padding: 79px 0 32px calc(var(--sidebar-width-max)); + } + + .plugins__top-container-jc { + padding-left: 80px; + } +} diff --git a/client/my-sites/plugins/plugins-list/use-actions.tsx b/client/my-sites/plugins/plugins-list/use-actions.tsx index a330a1052ffb9a..ce82e1a8f00f23 100644 --- a/client/my-sites/plugins/plugins-list/use-actions.tsx +++ b/client/my-sites/plugins/plugins-list/use-actions.tsx @@ -49,7 +49,7 @@ export function useActions( callback: ( plugins: Array< Plugin > ) => { bulkActionDialog( PluginActions.ENABLE_AUTOUPDATES, plugins ); }, - label: translate( 'Enable Autoupdate' ), + label: translate( 'Enable auto-updates' ), isExternalLink: true, isEnabled: true, supportsBulk: true, @@ -60,7 +60,7 @@ export function useActions( callback: ( plugins: Array< Plugin > ) => { bulkActionDialog( PluginActions.DISABLE_AUTOUPDATES, plugins ); }, - label: translate( 'Disable Autoupdate' ), + label: translate( 'Disable auto-updates' ), isExternalLink: true, isEnabled: true, supportsBulk: true, diff --git a/client/my-sites/site-settings/site-admin-interface/index.js b/client/my-sites/site-settings/site-admin-interface/index.js index 4e243609c50955..800ec45e3ab38d 100644 --- a/client/my-sites/site-settings/site-admin-interface/index.js +++ b/client/my-sites/site-settings/site-admin-interface/index.js @@ -162,7 +162,7 @@ const SiteAdminInterface = ( { siteId, siteSlug, isHosting = false } ) => { > { translate( - 'Set the admin interface style for all users. {{supportLink}}Learn more{{/supportLink}}.', + 'Set the admin interface style for all users. {{supportLink}}Learn more{{/supportLink}}', { components: { supportLink: ( diff --git a/client/my-sites/stats/stats-chart-tabs/style.scss b/client/my-sites/stats/stats-chart-tabs/style.scss index 6d8f3902c4c897..007a9d151c0e36 100644 --- a/client/my-sites/stats/stats-chart-tabs/style.scss +++ b/client/my-sites/stats/stats-chart-tabs/style.scss @@ -229,6 +229,7 @@ ul.module-tabs { } .is-chart-tabs { + .chart__bars { justify-content: space-between; } @@ -239,6 +240,39 @@ ul.module-tabs { max-width: 156px; } + /* styles border around chart for new date filtering design */ + &.is-date-filtering-enabled { + background-color: var(--studio-white); + border: 1px solid var(--studio-gray-5); + border-radius: 4px; + padding: 24px; + margin: 32px 0; + + /* styling for chart header with new date filtering design */ + .stats-chart-tabs__header { + display: flex; + flex-direction: row; + gap: 20px; /* adds space between the divs */ + } + + .chart__legend{ + margin-left: auto; /* makes all items take equal width */ + } + + .chart__y-axis-marker { + border-top-color: #DCDCDE; + } + } + + /* styling for the chart header component */ + .stats-chart-tabs__header { + + .stats-chart-tabs__header-title { + font-size: $font-body-large; + font-weight: 500; + } + } + } .stats-chart-tabs__header { diff --git a/client/my-sites/themes/theme-showcase.jsx b/client/my-sites/themes/theme-showcase.jsx index 6b284cdb8af56b..97a39ab981c6a9 100644 --- a/client/my-sites/themes/theme-showcase.jsx +++ b/client/my-sites/themes/theme-showcase.jsx @@ -5,7 +5,7 @@ import page from '@automattic/calypso-router'; import { SelectDropdown } from '@automattic/components'; import { isAssemblerSupported } from '@automattic/design-picker'; import clsx from 'clsx'; -import { localize, translate } from 'i18n-calypso'; +import { localize } from 'i18n-calypso'; import { compact, pickBy } from 'lodash'; import PropTypes from 'prop-types'; import { createRef, Component } from 'react'; @@ -72,31 +72,6 @@ const optionShape = PropTypes.shape( { action: PropTypes.func, } ); -const staticFilters = { - MYTHEMES: { - key: STATIC_FILTERS.MYTHEMES, - get text() { - return translate( 'My Themes' ); - }, - }, - RECOMMENDED: { - key: STATIC_FILTERS.RECOMMENDED, - get text() { - return translate( 'Recommended' ); - }, - }, - ALL: { - key: STATIC_FILTERS.ALL, - get text() { - return translate( 'All' ); - }, - }, -}; - -const defaultStaticFilter = Object.values( staticFilters ).find( - ( staticFilter ) => staticFilter.key === DEFAULT_STATIC_FILTER -); - class ThemeShowcase extends Component { state = { isDesignThemeModalVisible: false, @@ -177,7 +152,28 @@ class ThemeShowcase extends Component { isThemeDiscoveryEnabled = () => config.isEnabled( 'themes/discovery' ); - getDefaultStaticFilter = () => defaultStaticFilter; + getStaticFilters() { + const { translate } = this.props; + return { + MYTHEMES: { + key: STATIC_FILTERS.MYTHEMES, + text: translate( 'My Themes' ), + }, + RECOMMENDED: { + key: STATIC_FILTERS.RECOMMENDED, + text: translate( 'Recommended' ), + }, + ALL: { + key: STATIC_FILTERS.ALL, + text: translate( 'All' ), + }, + }; + } + + getDefaultStaticFilter = () => + Object.values( this.getStaticFilters() ).find( + ( staticFilter ) => staticFilter.key === DEFAULT_STATIC_FILTER + ); isStaticFilter = ( tabFilter ) => isStaticFilter( tabFilter.key ); @@ -189,6 +185,8 @@ class ThemeShowcase extends Component { }; getTabFilters = () => { + const { translate } = this.props; + const staticFilters = this.getStaticFilters(); if ( this.props.siteId && ! this.props.areSiteFeaturesLoaded ) { return null; } @@ -214,7 +212,7 @@ class ThemeShowcase extends Component { }; getTiers = () => { - const { themeTiers } = this.props; + const { themeTiers, translate } = this.props; const tiers = Object.keys( themeTiers ).reduce( ( availableTiers, tier ) => { if ( ! THEME_TIERS[ tier ]?.isFilterable ) { @@ -239,6 +237,7 @@ class ThemeShowcase extends Component { getSelectedTabFilter = () => { const filter = this.props.filter ?? ''; const filterArray = filter.split( '+' ); + const staticFilters = this.getStaticFilters(); const matches = Object.values( this.subjectTermTable ).filter( ( value ) => filterArray.includes( value ) ); @@ -286,6 +285,7 @@ class ThemeShowcase extends Component { doSearch = ( searchBoxContent ) => { const filterRegex = /([\w-]*):([\w-]*)/g; const { filterToTermTable, subjectStringFilter, isSearchV2 } = this.props; + const staticFilters = this.getStaticFilters(); const filters = `${ searchBoxContent } ${ isSearchV2 ? subjectStringFilter : '' }`.match( filterRegex ) || []; @@ -339,6 +339,7 @@ class ThemeShowcase extends Component { const category = tier !== 'all' && ! this.props.category ? '' : this.props.category; const showCollection = this.isThemeDiscoveryEnabled() && ! this.props.filterString && ! category && tier !== 'all'; + const staticFilters = this.getStaticFilters(); const url = this.constructUrl( { tier, @@ -359,6 +360,7 @@ class ThemeShowcase extends Component { recordTracksEvent( 'calypso_themeshowcase_filter_category_click', { category: tabFilter.key } ); trackClick( 'section nav filter', tabFilter ); + const staticFilters = this.getStaticFilters(); const { filter = '', filterToTermTable } = this.props; const subjectFilters = Object.values( this.subjectTermTable ); const filterWithoutSubjects = filter @@ -561,6 +563,7 @@ class ThemeShowcase extends Component { renderThemes = ( themeProps ) => { const tabKey = this.getSelectedTabFilter().key; + const staticFilters = this.getStaticFilters(); switch ( tabKey ) { case staticFilters.MYTHEMES?.key: @@ -631,9 +634,11 @@ class ThemeShowcase extends Component { isSiteWooExpress, isCollectionView, lastNonEditorRoute, + translate, } = this.props; const tier = this.props.tier || 'all'; const canonicalUrl = 'https://wordpress.com' + pathName; + const staticFilters = this.getStaticFilters(); const themeProps = { forceWpOrgSearch: true, diff --git a/client/package.json b/client/package.json index a57cd7089dc141..9d8c85446172bf 100644 --- a/client/package.json +++ b/client/package.json @@ -216,7 +216,7 @@ "utility-types": "^3.10.0", "uuid": "^9.0.1", "valid-url": "^1.0.9", - "webpack": "^5.94.0", + "webpack": "^5.95.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-dev-middleware": "^5.3.4", "webpack-hot-middleware": "^2.26.1", diff --git a/client/performance-profiler/components/insights-section/index.tsx b/client/performance-profiler/components/insights-section/index.tsx index 141f53349c6381..bd243338eb6af1 100644 --- a/client/performance-profiler/components/insights-section/index.tsx +++ b/client/performance-profiler/components/insights-section/index.tsx @@ -1,7 +1,4 @@ import { SelectDropdown } from '@automattic/components'; -import { useDesktopBreakpoint } from '@automattic/viewport-react'; -import styled from '@emotion/styled'; -import clsx from 'clsx'; import { useTranslate } from 'i18n-calypso'; import { ForwardedRef, forwardRef, useCallback, useEffect, useState } from 'react'; import { @@ -29,36 +26,9 @@ type InsightsSectionProps = { onRecommendationsFilterChange?: ( filter: string ) => void; }; -const AIBadge = styled.span` - padding: 0 8px; - margin-left: 8px; - margin-top: 2px; - width: fit-content; - height: fit-content; - border-radius: 4px; - float: right; - font-size: 12px; - line-height: 20px; - color: var( --studio-gray-100 ); - background: linear-gradient( - 0deg, - rgba( 255, 255, 255, 0.95 ) 0%, - rgba( 255, 255, 255, 0.95 ) 100% - ), - linear-gradient( 90deg, #4458e4 0%, #069e08 100% ); - - &.is-mobile { - float: none; - display: block; - margin-left: 0; - margin-top: 8px; - } -`; - export const InsightsSection = forwardRef( ( props: InsightsSectionProps, ref: ForwardedRef< HTMLDivElement > ) => { const translate = useTranslate(); - const isMobile = ! useDesktopBreakpoint(); const { audits, fullPageScreenshot, isWpcom, hash, filter, onRecommendationsFilterChange } = props; const [ selectedFilter, setSelectedFilter ] = useState( filter ?? 'all' ); @@ -93,12 +63,7 @@ export const InsightsSection = forwardRef(
-

- { translate( 'Personalized Recommendations' ) } - - { translate( 'Generated with AI' ) } - -

+

{ translate( 'Personalized Recommendations' ) }

{ getSubtitleText( selectedFilter, filteredAudits.length, translate ) }

diff --git a/client/performance-profiler/utils/metrics.ts b/client/performance-profiler/utils/metrics.ts index a4f962d0b494c1..ae5954421dcf89 100644 --- a/client/performance-profiler/utils/metrics.ts +++ b/client/performance-profiler/utils/metrics.ts @@ -169,14 +169,10 @@ export const displayValue = ( metric: Metrics, value: number ): string => { return ''; } - if ( [ 'lcp', 'fcp', 'ttfb' ].includes( metric ) ) { + if ( [ 'lcp', 'fcp', 'ttfb', 'inp', 'fid', 'tbt' ].includes( metric ) ) { return `${ max2Decimals( value / 1000 ) }s`; } - if ( [ 'inp', 'fid', 'tbt' ].includes( metric ) ) { - return `${ max2Decimals( value ) }ms`; - } - return `${ max2Decimals( value ) }`; }; diff --git a/client/reader/recent/index.tsx b/client/reader/recent/index.tsx index 982ab08be34229..11d0ede64e7609 100644 --- a/client/reader/recent/index.tsx +++ b/client/reader/recent/index.tsx @@ -1,10 +1,12 @@ +import { WIDE_BREAKPOINT } from '@automattic/viewport'; +import { useBreakpoint } from '@automattic/viewport-react'; import { DataViews, filterSortAndPaginate, SupportedLayouts, View } from '@wordpress/dataviews'; import { translate } from 'i18n-calypso'; import { useState, useEffect, useCallback, useMemo } from 'react'; import { useSelector, shallowEqual, useDispatch } from 'react-redux'; import { AnyAction } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; -import { FullPostView } from 'calypso/blocks/reader-full-post'; +import AsyncLoad from 'calypso/components/async-load'; import FormattedHeader from 'calypso/components/formatted-header'; import { getPostByKey } from 'calypso/state/reader/posts/selectors'; import { requestPage } from 'calypso/state/reader/streams/actions'; @@ -18,31 +20,31 @@ import './style.scss'; const Recent = () => { const dispatch = useDispatch< ThunkDispatch< AppState, void, AnyAction > >(); const [ selectedItem, setSelectedItem ] = useState< ReaderPost | null >( null ); + const isWide = useBreakpoint( WIDE_BREAKPOINT ); const [ view, setView ] = useState< View >( { type: 'table', fields: [ 'seen', 'post' ], } ); - const { data, posts } = useSelector( ( state: AppState ) => { - const streamData = state.reader?.streams?.recent; - const postsMap: Record< string, PostItem > = {}; + const data = useSelector( ( state: AppState ) => state.reader?.streams?.recent ); - // Create a map of posts for all items - streamData?.items?.forEach( ( item: ReaderPost ) => { + const posts = useSelector( ( state: AppState ) => { + const items = data?.items; + if ( ! items ) { + return {}; + } + + return items.reduce( ( acc: Record< string, PostItem >, item: ReaderPost ) => { const post = getPostByKey( state, { - feedId: +item.blogId, - postId: +item.postId, + feedId: item.blogId, + postId: item.postId, } ); if ( post ) { - postsMap[ `${ item.blogId }-${ item.postId }` ] = post; + acc[ `${ item.blogId }-${ item.postId }` ] = post; } - } ); - - return { - data: streamData, - posts: postsMap, - }; + return acc; + }, {} ); }, shallowEqual ); const getPostFromItem = useCallback( @@ -84,7 +86,7 @@ const Recent = () => { enableHiding: false, }, ], - [ getPostFromItem ] + [ getPostFromItem, setSelectedItem ] ); const defaultLayouts = [ @@ -109,16 +111,16 @@ const Recent = () => { fetchData(); }, [ fetchData ] ); - // Set the first item as selected if no item is selected. + // Set the first item as selected if no item is selected and screen is wide. useEffect( () => { - if ( data?.items?.length > 0 && ! selectedItem ) { + if ( isWide && data?.items?.length > 0 && ! selectedItem ) { setSelectedItem( data.items[ 0 ] ); } - }, [ data?.items, selectedItem ] ); + }, [ isWide, data?.items, selectedItem ] ); return (
-
+
@@ -138,12 +140,14 @@ const Recent = () => { />
-
+
{ selectedItem && getPostFromItem( selectedItem ) && ( - setSelectedItem( null ) } + layout="recent" /> ) }
diff --git a/client/reader/recent/recent-seen-field.tsx b/client/reader/recent/recent-seen-field.tsx index 178e821ebf7afd..0c7dd295848690 100644 --- a/client/reader/recent/recent-seen-field.tsx +++ b/client/reader/recent/recent-seen-field.tsx @@ -11,12 +11,7 @@ interface RecentSeenFieldProps { const RecentSeenField: React.FC< RecentSeenFieldProps > = ( { item, post, setSelectedItem } ) => { return ( ); }; diff --git a/client/reader/recent/style.scss b/client/reader/recent/style.scss index ce775663b006da..70cd8826fe6752 100644 --- a/client/reader/recent/style.scss +++ b/client/reader/recent/style.scss @@ -1,19 +1,55 @@ +@import "@wordpress/base-styles/breakpoints"; + .is-section-reader .recent-feed { - display: flex; + @media ( min-width: $break-wide ) { + display: flex; + background: var( --studio-gray-0 ); + gap: 24px; + } + + %column-shared { + background: var( --color-surface ); + border-radius: 8px; /* stylelint-disable-line scales/radii */ + box-shadow: 0 0 17.4px 0 rgba( 0, 0, 0, 0.05 ); + height: 100%; + overflow-y: auto; + overflow-x: hidden; + } &__list-column { flex: 1; - max-width: 320px; + width: 100%; + @extend %column-shared; + + &.has-overlay { + display: none; + + @media ( min-width: $break-wide ) { + display: block; + } + } + + @media ( min-width: $break-wide ) { + max-width: 320px; + } &-header { - padding: 16px 12px 0; + padding: 0 12px; border-bottom: 1px solid var( --studio-gray-0 ); margin-bottom: 16px; + + header { + margin: 16px 0; + } + + @media ( min-width: $break-wide ) { + padding: 16px 12px 0; + } } .dataviews__view-actions { + box-sizing: border-box; padding: 0 12px; - max-width: 300px; } table { @@ -40,6 +76,10 @@ .recent-seen-field { padding: 0; + + img { + border-radius: 50%; + } } .recent-post-field { @@ -50,21 +90,30 @@ padding: 0; width: 100%; - &__title-text, - &__site-name { + %text-shared { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; max-width: 200px; + + @media ( min-width: $break-wide ) { + max-width: 125px; + } + + @media ( min-width: 1540px ) { + max-width: 200px; + } } &__title-text { font-weight: 700; + @extend %text-shared; } &__site-name { font-weight: 400; font-size: rem( 11px ); + @extend %text-shared; } &__featured-image { @@ -75,12 +124,23 @@ } &__post-column { - flex: 3; + display: none; + @extend %column-shared; + + &.overlay { + display: block; + } .reader-full-post__sidebar, .reader-full-post__author-block, .back-button { display: none; } + + @media ( min-width: $break-wide ) { + display: block; + position: relative; + flex: 3; + } } } diff --git a/client/reader/recent/types.ts b/client/reader/recent/types.ts index 48bb64dc1287b3..1aef5e455719d9 100644 --- a/client/reader/recent/types.ts +++ b/client/reader/recent/types.ts @@ -1,5 +1,3 @@ -import { Author } from 'calypso/state/comments/actions'; - export interface ReaderPost { site_name: string; postId: number; @@ -9,7 +7,10 @@ export interface ReaderPost { export interface PostItem { title: string; featured_image: string; - site_icon: string; - feed_icon: string; - author: Author; + site_icon: { + img: string; + }; + author: { + avatar_URL: string; + }; } diff --git a/client/reader/sidebar/index.jsx b/client/reader/sidebar/index.jsx index b4f4e38712ab7d..faee472c6c34b4 100644 --- a/client/reader/sidebar/index.jsx +++ b/client/reader/sidebar/index.jsx @@ -195,13 +195,13 @@ export class ReaderSidebar extends Component { { isEnabled( 'reader/recent-feed-overhaul' ) ? ( - +
  • + +
  • ) : ( void; + path: string; className: string; translate: ( key: string ) => string; }; const SITE_DISPLAY_CUTOFF = 8; +const RECENT_PATH_REGEX = /^\/read\/?(?:\?|$)/; const ReaderSidebarRecent = ( { translate, isOpen, onClick, + path, className, }: Props ): React.JSX.Element => { const [ showAllSites, setShowAllSites ] = useState( false ); - const sites = useSelector< Site, Site[] >( getReaderFollowedSites ); + const sites = useSelector< AppState, Site[] >( getReaderFollowedSites ); + const selectedSiteId = useSelector< AppState, number >( + ( state ) => state.readerUi.sidebar.selectedRecentSite + ); const sitesToShow = showAllSites ? sites : sites.slice( 0, SITE_DISPLAY_CUTOFF ); const totalUnseenCount = sites.reduce( ( total, site ) => total + site.unseen_count, 0 ); @@ -59,42 +63,61 @@ const ReaderSidebarRecent = ( { }; return ( -
  • - } - disableFlyout - className={ className } - count={ undefined } - icon={ null } - materialIcon={ null } - materialIconStyle={ null } - > + } + disableFlyout + className={ clsx( 'reader-sidebar-recent', className, { + 'sidebar__menu--selected': ! isOpen && RECENT_PATH_REGEX.test( path ), + } ) } + count={ undefined } + icon={ null } + materialIcon={ null } + materialIconStyle={ null } + > +
  • + +
  • + { sitesToShow.map( ( site ) => ( +
  • + +
  • + ) ) } + { sites.length > SITE_DISPLAY_CUTOFF && (
  • -
  • - { sitesToShow.map( ( site ) => ( -
  • - -
  • - ) ) } - { sites.length > SITE_DISPLAY_CUTOFF && ( -
  • - -
  • - ) } - - + ) } + ); }; diff --git a/client/reader/sidebar/reader-sidebar-recent/style.scss b/client/reader/sidebar/reader-sidebar-recent/style.scss new file mode 100644 index 00000000000000..69d363bc9536f5 --- /dev/null +++ b/client/reader/sidebar/reader-sidebar-recent/style.scss @@ -0,0 +1,64 @@ +.reader-sidebar-recent { + $recent-font-size: 13px; + $recent-line-height: 1.5; + $recent-item-padding-x: 18px; + $recent-item-padding-y: 6px; + $recent-site-icon-size: 16px; + $recent-item-gap: 8px; + + &__item { + width: 100%; + cursor: pointer; + text-align: left; + display: grid; + grid-template-columns: $recent-site-icon-size 1fr auto; + gap: $recent-item-gap; + align-items: center; + padding: $recent-item-padding-y $recent-item-padding-x; + font-size: $recent-font-size; + line-height: $recent-line-height; + + &:hover { + background-color: var( --color-sidebar-menu-hover-background ); + } + + &--selected { + font-weight: 700; + } + + &--without-icon { + padding-left: $recent-item-padding-x + $recent-site-icon-size + $recent-item-gap; // 15px + 16px + 8px; + grid-template-columns: 1fr auto; + } + } + + &__site { + &-name { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + &-icon { + border-radius: 50%; + width: $recent-site-icon-size; + height: $recent-site-icon-size; + } + + &-count { + font-size: $recent-font-size; + font-weight: 400; + margin-right: 6px; // To get alignment with menu chevron icon; bleh + } + } + + &__view-more { + color: var( --color-link ); + text-decoration: underline; + padding-bottom: $recent-item-padding-y * 2; + } + + &.sidebar__menu--selected:not( :hover ) path { + fill: var( --color-sidebar-gridicon-selected-fill ) !important; + } +} diff --git a/client/reader/sidebar/style.scss b/client/reader/sidebar/style.scss index b392366ea49158..a9c7cd8ec6f712 100644 --- a/client/reader/sidebar/style.scss +++ b/client/reader/sidebar/style.scss @@ -203,6 +203,7 @@ body.is-section-reader { margin-right: 8px; } + /* @holdercp TODO: This can be removed once calypso/reader/stream/reader-list-followed-sites is removed */ .sidebar-streams__following-load-more { color: var(--color-link); cursor: pointer; diff --git a/client/reader/stream/style.scss b/client/reader/stream/style.scss index 0a6ce8151e4656..4ff0fa49d6b8ea 100644 --- a/client/reader/stream/style.scss +++ b/client/reader/stream/style.scss @@ -742,6 +742,7 @@ line-height: 20px; } } + /* @holdercp TODO: This can be removed once calypso/reader/stream/reader-list-followed-sites is removed */ .sidebar-streams__following-load-more { color: var(--color-neutral-100); font-size: $font-body-small; diff --git a/client/reader/style.scss b/client/reader/style.scss index f28a75bd0ef17b..be3cfe4944b156 100644 --- a/client/reader/style.scss +++ b/client/reader/style.scss @@ -7,7 +7,7 @@ body.is-section-reader { padding: 24px; border-block-end: 1px solid var(--studio-gray-0); } - .layout__primary > div { + .layout__primary > div:not(:has(.recent-feed)) { background: var(--color-surface); border-radius: 8px; /* stylelint-disable-line scales/radii */ box-shadow: 0 0 17.4px 0 rgba(0, 0, 0, 0.05); diff --git a/client/sites/components/dotcom-style.scss b/client/sites/components/dotcom-style.scss index 3ca597dddadd1f..dfeb1b33d30c16 100644 --- a/client/sites/components/dotcom-style.scss +++ b/client/sites/components/dotcom-style.scss @@ -327,15 +327,8 @@ margin-right: 12px; } + // DataView overrides: Selected and hover states on the site list. ul.dataviews-view-list { - .components-h-stack { - gap: 0; - } - - li { - cursor: pointer; - } - li.is-selected, li.is-selected:hover { background-color: #ebf2fc; @@ -560,14 +553,10 @@ } .dataviews-wrapper { - width: 100%; - .dataviews-view-list { - flex: 1; - max-height: none; - - // Ideally instead of reusing the site name full field + // DataView overrides: Ideally instead of reusing the site name full field // We should be setting a media field and a primary field. + // Or update Core to hide them when not specified. .dataviews-view-list__media-wrapper { display: none; } @@ -617,3 +606,51 @@ } } } + +// DataView overrides: +// Using the List view for the fly-out panel requires hiding certain elements +// to properly fit the site list when the panel is open. +.main.a4a-layout.sites-dashboard { + @media (min-width: $break-large) { + ul.dataviews-view-list { + // This override could potentially be removed if we’re able to pass the Actions column via data.actions + .components-h-stack, + .dataviews-view-list__item { + width: 100%; + } + + /* This styling is a bit of a hack: To make the site name clickable area larger, will need + * to hide the other fields when in preview mode. + */ + .dataviews-view-list__field { + // Hide the field except first (Site name) and last (actions menu) + &:not(:first-child):not(:last-child) { + display: none; + } + + // Force the site name to take up the full width + &:first-child { + flex-grow: 1; + + .sites-dataviews__site { + width: 100%; + justify-content: flex-start; + } + } + } + + .sites-overview__stats-trend, + .sites-overview__column-action-button, + .sites-overview__row-status, + .toggle-activate-monitoring__toggle-button, + .sites-dataviews__favorite-btn-wrapper, + .sites-overview__boost-score { + display: none; + } + + .site-actions__actions-large-screen { + display: block; + } + } + } +} diff --git a/client/sites/components/site-preview-pane/constants.ts b/client/sites/components/site-preview-pane/constants.ts index 4d2abab333b181..ed8fed1aabbe12 100644 --- a/client/sites/components/site-preview-pane/constants.ts +++ b/client/sites/components/site-preview-pane/constants.ts @@ -9,6 +9,7 @@ export const DOTCOM_STAGING_SITE = 'dotcom-staging-site'; export const DOTCOM_SITE_PERFORMANCE = 'dotcom-site-performance'; export const MARKETING_TOOLS = 'marketing-tools'; +export const MARKETING_CONNECTIONS = 'marketing-connections'; export const TOOLS_STAGING_SITE = 'tools-staging-site'; export const TOOLS_DEPLOYMENTS = 'tools-deployments'; @@ -37,6 +38,7 @@ export const FEATURE_TO_ROUTE_MAP: { [ feature: string ]: string } = { // New Information Architecture [ MARKETING_TOOLS ]: 'sites/marketing/tools/:site', + [ MARKETING_CONNECTIONS ]: 'sites/marketing/connections/:site', [ TOOLS_STAGING_SITE ]: 'sites/tools/staging-site/:site', [ TOOLS_DEPLOYMENTS ]: 'sites/tools/deployments/:site', [ TOOLS_MONITORING ]: 'sites/tools/monitoring/:site', diff --git a/client/sites/components/site-preview-pane/dotcom-preview-pane.tsx b/client/sites/components/site-preview-pane/dotcom-preview-pane.tsx index e6b5bd44f02157..ad717525e28e48 100644 --- a/client/sites/components/site-preview-pane/dotcom-preview-pane.tsx +++ b/client/sites/components/site-preview-pane/dotcom-preview-pane.tsx @@ -23,6 +23,7 @@ import { DOTCOM_HOSTING_FEATURES, DOTCOM_STAGING_SITE, MARKETING_TOOLS, + MARKETING_CONNECTIONS, SETTINGS_SITE, SETTINGS_ADMINISTRATION, SETTINGS_AGENCY, @@ -122,7 +123,7 @@ const DotcomPreviewPane = ( { { label: __( 'Marketing' ), enabled: config.isEnabled( 'untangling/hosting-menu' ), - featureIds: [ MARKETING_TOOLS ], + featureIds: [ MARKETING_TOOLS, MARKETING_CONNECTIONS ], }, { label: __( 'Advanced Tools' ), diff --git a/client/sites/components/style.scss b/client/sites/components/style.scss index ef92a005e157f7..6709adfbc3bfca 100644 --- a/client/sites/components/style.scss +++ b/client/sites/components/style.scss @@ -210,58 +210,6 @@ align-items: center; height: 28px; } - - ul.dataviews-view-list { - list-style: none; - margin: 0; - padding: 0; - overflow-y: auto; - max-height: calc(100vh - 300px); /* 300px is the size of all content above the dataview in list style, which includes our CTA elements, the pagination bottom bar, and an additional 20px margin. */ - - .dataviews-view-list__fields { - display: flex; - justify-content: space-between; - } - - .components-h-stack, - .dataviews-view-list__item { - width: 100%; - } - - /* This styling is a bit of a hack: To make the site name clickable area larger, will need - * to hide the other fields when in preview mode. - */ - .dataviews-view-list__field { - // Hide the field except first (Site name) and last (actions menu) - &:not(:first-child):not(:last-child) { - display: none; - } - - // Force the site name to take up the full width - &:first-child { - flex-grow: 1; - - .sites-dataviews__site { - width: 100%; - justify-content: flex-start; - } - } - } - - .sites-overview__stats-trend, - .sites-overview__column-action-button, - .sites-overview__row-status, - .toggle-activate-monitoring__toggle-button, - .sites-dataviews__favorite-btn-wrapper, - .sites-overview__boost-score { - display: none; - } - - .site-actions__actions-large-screen { - display: block; - } - - } } } @@ -349,12 +297,6 @@ white-space: nowrap; } - .dataviews-wrapper { - .components-button:focus:not(:disabled) { - box-shadow: 0 0 0 2px var(--color-primary-light); - } - } - .sites-overview { height: 100%; width: 400px; @@ -951,5 +893,4 @@ .components-input-control__container > div.components-input-control__backdrop { border-color: var(--color-border-subtle); } -} - +} \ No newline at end of file diff --git a/client/my-sites/marketing/connections/README.md b/client/sites/marketing/connections/README.md similarity index 100% rename from client/my-sites/marketing/connections/README.md rename to client/sites/marketing/connections/README.md diff --git a/client/my-sites/marketing/connections/account-dialog-account.jsx b/client/sites/marketing/connections/account-dialog-account.jsx similarity index 100% rename from client/my-sites/marketing/connections/account-dialog-account.jsx rename to client/sites/marketing/connections/account-dialog-account.jsx diff --git a/client/my-sites/marketing/connections/account-dialog-account.scss b/client/sites/marketing/connections/account-dialog-account.scss similarity index 100% rename from client/my-sites/marketing/connections/account-dialog-account.scss rename to client/sites/marketing/connections/account-dialog-account.scss diff --git a/client/my-sites/marketing/connections/account-dialog.jsx b/client/sites/marketing/connections/account-dialog.jsx similarity index 100% rename from client/my-sites/marketing/connections/account-dialog.jsx rename to client/sites/marketing/connections/account-dialog.jsx diff --git a/client/my-sites/marketing/connections/account-dialog.scss b/client/sites/marketing/connections/account-dialog.scss similarity index 100% rename from client/my-sites/marketing/connections/account-dialog.scss rename to client/sites/marketing/connections/account-dialog.scss diff --git a/client/my-sites/marketing/connections/bluesky.tsx b/client/sites/marketing/connections/bluesky.tsx similarity index 100% rename from client/my-sites/marketing/connections/bluesky.tsx rename to client/sites/marketing/connections/bluesky.tsx diff --git a/client/my-sites/marketing/connections/connection.jsx b/client/sites/marketing/connections/connection.jsx similarity index 100% rename from client/my-sites/marketing/connections/connection.jsx rename to client/sites/marketing/connections/connection.jsx diff --git a/client/my-sites/marketing/connections/connections.jsx b/client/sites/marketing/connections/connections.jsx similarity index 100% rename from client/my-sites/marketing/connections/connections.jsx rename to client/sites/marketing/connections/connections.jsx diff --git a/client/my-sites/marketing/connections/google-photos-migration.jsx b/client/sites/marketing/connections/google-photos-migration.jsx similarity index 100% rename from client/my-sites/marketing/connections/google-photos-migration.jsx rename to client/sites/marketing/connections/google-photos-migration.jsx diff --git a/client/my-sites/marketing/connections/google-plus-deprecation.jsx b/client/sites/marketing/connections/google-plus-deprecation.jsx similarity index 100% rename from client/my-sites/marketing/connections/google-plus-deprecation.jsx rename to client/sites/marketing/connections/google-plus-deprecation.jsx diff --git a/client/sites/marketing/connections/index.tsx b/client/sites/marketing/connections/index.tsx new file mode 100644 index 00000000000000..b329478fefdf29 --- /dev/null +++ b/client/sites/marketing/connections/index.tsx @@ -0,0 +1,34 @@ +import { useTranslate } from 'i18n-calypso'; +import InlineSupportLink from 'calypso/components/inline-support-link'; +import NavigationHeader from 'calypso/components/navigation-header'; +import SharingConnections from './connections'; + +import '../style.scss'; + +export default function MarketingConnections( { + siteId, + isP2Hub, +}: { + siteId: number; + isP2Hub: boolean; +} ) { + const translate = useTranslate(); + return ( +
    + + ), + }, + } + ) } + /> + +
    + ); +} diff --git a/client/my-sites/marketing/connections/inline-connection-action.jsx b/client/sites/marketing/connections/inline-connection-action.jsx similarity index 100% rename from client/my-sites/marketing/connections/inline-connection-action.jsx rename to client/sites/marketing/connections/inline-connection-action.jsx diff --git a/client/my-sites/marketing/connections/inline-connection.jsx b/client/sites/marketing/connections/inline-connection.jsx similarity index 89% rename from client/my-sites/marketing/connections/inline-connection.jsx rename to client/sites/marketing/connections/inline-connection.jsx index 674785e9a5b020..d6a238de1ddb75 100644 --- a/client/my-sites/marketing/connections/inline-connection.jsx +++ b/client/sites/marketing/connections/inline-connection.jsx @@ -3,8 +3,8 @@ import { Component } from 'react'; import { connect } from 'react-redux'; import QueryKeyringServices from 'calypso/components/data/query-keyring-services'; import QueryPublicizeConnections from 'calypso/components/data/query-publicize-connections'; -import InlineConnectionAction from 'calypso/my-sites/marketing/connections/inline-connection-action'; import { getKeyringServiceByName } from 'calypso/state/sharing/services/selectors'; +import InlineConnectionAction from './inline-connection-action'; class InlineConnection extends Component { static propTypes = { diff --git a/client/my-sites/marketing/connections/mailchimp-settings.jsx b/client/sites/marketing/connections/mailchimp-settings.jsx similarity index 100% rename from client/my-sites/marketing/connections/mailchimp-settings.jsx rename to client/sites/marketing/connections/mailchimp-settings.jsx diff --git a/client/my-sites/marketing/connections/mastodon.tsx b/client/sites/marketing/connections/mastodon.tsx similarity index 100% rename from client/my-sites/marketing/connections/mastodon.tsx rename to client/sites/marketing/connections/mastodon.tsx diff --git a/client/my-sites/marketing/connections/service-action.jsx b/client/sites/marketing/connections/service-action.jsx similarity index 96% rename from client/my-sites/marketing/connections/service-action.jsx rename to client/sites/marketing/connections/service-action.jsx index 7b242483acd126..ed7b3211232b09 100644 --- a/client/my-sites/marketing/connections/service-action.jsx +++ b/client/sites/marketing/connections/service-action.jsx @@ -72,7 +72,11 @@ const SharingServiceAction = ( { } // See: https://developers.google.com/photos/library/guides/ux-guidelines - if ( 'google_photos' === service.ID && ! path?.startsWith( '/marketing/connections/' ) ) { + if ( + 'google_photos' === service.ID && + ! path?.startsWith( '/marketing/connections/' ) && + ! path?.startsWith( '/sites/marketing/connections/' ) + ) { return ( - -
    - - ); + const FeedbackButtons = () => { + const feedbackButtonsText = shouldUseHelpCenterExperience + ? __( 'Was this helpful?' ) + : __( 'Did you find the answer to your question?' ); + return ( + <> +

    { feedbackButtonsText }

    +
    + + +
    + + ); + }; const feedbackFormUrl = addQueryArgs( 'https://wordpressdotcom.survey.fm/helpcenter-articles-feedback', @@ -71,18 +76,23 @@ const HelpCenterFeedbackForm = ( { } ); - const FeedbackTextArea = () => ( - <> -

    { __( 'How we can improve?' ) }

    - - - ); + const FeedbackTextArea = () => { + if ( shouldUseHelpCenterExperience ) { + return

    { __( 'Great! Thanks.' ) }

    ; + } + return ( + <> +

    { __( 'How we can improve?' ) }

    + + + ); + }; return (
    @@ -90,7 +100,6 @@ const HelpCenterFeedbackForm = ( { { startedFeedback !== null && answerValue === 1 && } { startedFeedback !== null && answerValue === 2 && site && ( void } ) => { const isHelpCenterHome = key === 'default'; const headerText = useMemo( () => { + if ( pathname.startsWith( '/odie' ) ) { + return config.isEnabled( 'help-center-experience' ) + ? __( 'Support Assistant', __i18n_text_domain__ ) + : __( 'Wapuu', __i18n_text_domain__ ); + } switch ( pathname ) { - case '/odie': case '/contact-form': - return __( 'Wapuu', __i18n_text_domain__ ); + return config.isEnabled( 'help-center-experience' ) + ? __( 'Support Assistant', __i18n_text_domain__ ) + : __( 'Wapuu', __i18n_text_domain__ ); case '/chat-history': return __( 'History', __i18n_text_domain__ ); default: @@ -149,8 +155,15 @@ const ContentMinimized = ( { } /> } /> - - + + { unreadCount > 0 && ( { formattedUnreadCount } diff --git a/packages/help-center/src/components/help-center-recent-conversations.tsx b/packages/help-center/src/components/help-center-recent-conversations.tsx index a77ad9f302660b..6e8af44513f407 100644 --- a/packages/help-center/src/components/help-center-recent-conversations.tsx +++ b/packages/help-center/src/components/help-center-recent-conversations.tsx @@ -1,4 +1,5 @@ import { HelpCenterSelect } from '@automattic/data-stores'; +import { useGetSupportInteractions } from '@automattic/odie-client/src/data'; import { useSmooch } from '@automattic/zendesk-client'; import { useSelect, useDispatch as useDataStoreDispatch } from '@wordpress/data'; import { sprintf } from '@wordpress/i18n'; @@ -6,7 +7,7 @@ import { useI18n } from '@wordpress/react-i18n'; import React, { useEffect, useState } from 'react'; import { HELP_CENTER_STORE } from '../stores'; import { HelpCenterSupportChatMessage } from './help-center-support-chat-message'; -import { calculateUnread } from './utils'; +import { calculateUnread, getConversationsFromSupportInteractions } from './utils'; import type { ZendeskConversation } from '@automattic/odie-client'; import './help-center-recent-conversations.scss'; @@ -26,6 +27,7 @@ const HelpCenterRecentConversations: React.FC = () => { const [ conversations, setConversations ] = useState< ZendeskConversation[] >( [] ); const [ unreadConversationsCount, setUnreadConversationsCount ] = useState( 0 ); const [ unreadMessagesCount, setUnreadMessagesCount ] = useState( 0 ); + const { data: supportInteractions } = useGetSupportInteractions( 'zendesk', 100, 'resolved' ); const { isChatLoaded } = useSelect( ( select ) => { const store = select( HELP_CENTER_STORE ) as HelpCenterSelect; return { isChatLoaded: store.getIsChatLoaded() }; @@ -34,15 +36,19 @@ const HelpCenterRecentConversations: React.FC = () => { const { setUnreadCount } = useDataStoreDispatch( HELP_CENTER_STORE ); useEffect( () => { - if ( isChatLoaded && getConversations ) { - const conversations = getConversations() as ZendeskConversation[]; + if ( isChatLoaded && getConversations && supportInteractions ) { + const allConversations = getConversations(); + const conversations = getConversationsFromSupportInteractions( + allConversations, + supportInteractions + ); const { unreadConversations, unreadMessages } = calculateUnread( conversations ); setUnreadConversationsCount( unreadConversations ); setUnreadMessagesCount( unreadMessages ); setConversations( conversations ); setUnreadCount( unreadConversations ); } - }, [ isChatLoaded, getConversations ] ); + }, [ isChatLoaded, getConversations, setUnreadCount, supportInteractions ] ); if ( ! conversations.length ) { return null; @@ -53,8 +59,7 @@ const HelpCenterRecentConversations: React.FC = () => { ); const lastConversation = lastUnreadConversation || conversations[ 0 ]; const lastMessage = lastConversation?.messages[ lastConversation?.messages.length - 1 ]; - const navigateTo = - unreadConversationsCount === 1 ? `/odie/${ lastConversation.id }` : '/chat-history'; + const navigateTo = unreadConversationsCount === 1 ? '/odie' : '/chat-history'; const chatMessage = { ...lastMessage, @@ -83,6 +88,7 @@ const HelpCenterRecentConversations: React.FC = () => { message={ chatMessage } isUnread={ unreadMessagesCount > 0 } navigateTo={ navigateTo } + supportInteractionId={ lastConversation.metadata?.supportInteractionId } /> ) : null }
    diff --git a/packages/help-center/src/components/help-center-smooch.tsx b/packages/help-center/src/components/help-center-smooch.tsx index 8bb8ef20d7aa46..a5964182c9809f 100644 --- a/packages/help-center/src/components/help-center-smooch.tsx +++ b/packages/help-center/src/components/help-center-smooch.tsx @@ -1,6 +1,5 @@ import { recordTracksEvent } from '@automattic/calypso-analytics'; import { HelpCenterSelect } from '@automattic/data-stores'; -import { ZendeskConversation } from '@automattic/odie-client'; import { useSmooch, useLoadZendeskMessaging, @@ -10,7 +9,7 @@ import { useSelect, useDispatch as useDataStoreDispatch } from '@wordpress/data' import { useEffect, useRef } from '@wordpress/element'; import { useChatStatus } from '../hooks'; import { HELP_CENTER_STORE } from '../stores'; -import { calculateUnread } from './utils'; +import { calculateUnread, getClientId } from './utils'; const HelpCenterSmooch: React.FC = () => { const { data: authData } = useAuthenticateZendeskMessaging( true, 'messenger' ); @@ -29,7 +28,8 @@ const HelpCenterSmooch: React.FC = () => { isHelpCenterShown && isEligibleForChat, isEligibleForChat ); - const { setIsChatLoaded, setUnreadCount } = useDataStoreDispatch( HELP_CENTER_STORE ); + const { setIsChatLoaded, setUnreadCount, setZendeskClientId } = + useDataStoreDispatch( HELP_CENTER_STORE ); const { initSmooch, destroy, getConversations, renderSmooch } = useSmooch(); // Initialize Smooch which communicates with Zendesk @@ -60,16 +60,16 @@ const HelpCenterSmooch: React.FC = () => { return () => { destroy(); }; - }, [ isMessagingScriptLoaded, authData ] ); + }, [ isMessagingScriptLoaded, authData, initSmooch, setIsChatLoaded, renderSmooch, destroy ] ); useEffect( () => { if ( isChatLoaded && getConversations ) { - const { unreadConversations } = calculateUnread( - getConversations() as ZendeskConversation[] - ); + const conversations = getConversations(); + const { unreadConversations } = calculateUnread( conversations ); setUnreadCount( unreadConversations ); + setZendeskClientId( getClientId( conversations ) ); } - }, [ isChatLoaded, getConversations, setUnreadCount ] ); + }, [ isChatLoaded, getConversations, setUnreadCount, setZendeskClientId ] ); return
    ; }; diff --git a/packages/help-center/src/components/help-center-support-chat-message.scss b/packages/help-center/src/components/help-center-support-chat-message.scss index fba7d3dcf7c687..aea91e243fa3bf 100644 --- a/packages/help-center/src/components/help-center-support-chat-message.scss +++ b/packages/help-center/src/components/help-center-support-chat-message.scss @@ -108,4 +108,11 @@ fill: #787C82; } } +.help-center-chat-history__no-results { + display: flex; + flex-direction: row; + justify-content: center; + border-top: solid 1px #C3C4C7; + padding-top: 20px; +} diff --git a/packages/help-center/src/components/help-center-support-chat-message.tsx b/packages/help-center/src/components/help-center-support-chat-message.tsx index 99daafe223a703..d65f4f08712603 100644 --- a/packages/help-center/src/components/help-center-support-chat-message.tsx +++ b/packages/help-center/src/components/help-center-support-chat-message.tsx @@ -3,11 +3,14 @@ import { Gravatar } from '@automattic/components'; import { getRelativeTimeString, useLocale } from '@automattic/i18n-utils'; import { type ZendeskMessage } from '@automattic/odie-client'; import { HumanAvatar } from '@automattic/odie-client/src/assets'; +import { useGetSupportInteractionById } from '@automattic/odie-client/src/data/use-get-support-interaction-by-id'; +import { useDispatch as useDataStoreDispatch } from '@wordpress/data'; import { chevronRight, Icon } from '@wordpress/icons'; import { useI18n } from '@wordpress/react-i18n'; import clsx from 'clsx'; import { Link } from 'react-router-dom'; import { useHelpCenterContext } from '../contexts/HelpCenterContext'; +import { HELP_CENTER_STORE } from '../stores'; import './help-center-support-chat-message.scss'; @@ -24,6 +27,7 @@ export const HelpCenterSupportChatMessage = ( { badgeCount = 0, isUnread = false, navigateTo = '', + supportInteractionId, }: { message: ZendeskMessage; badgeCount?: number; @@ -31,6 +35,7 @@ export const HelpCenterSupportChatMessage = ( { isUnread: boolean; navigateTo: string; altText?: string; + supportInteractionId: string | null; } ) => { const { __ } = useI18n(); const locale = useLocale(); @@ -38,6 +43,8 @@ export const HelpCenterSupportChatMessage = ( { const { displayName, received, text, altText } = message; const helpCenterContext = useHelpCenterContext(); const sectionName = helpCenterContext.sectionName; + const { data: supportInteraction } = useGetSupportInteractionById( supportInteractionId ); + const { setCurrentSupportInteraction } = useDataStoreDispatch( HELP_CENTER_STORE ); const renderAvatar = () => { if ( message.role === 'business' ) { @@ -55,7 +62,12 @@ export const HelpCenterSupportChatMessage = ( { return ( trackContactButtonClicked( sectionName ) } + onClick={ () => { + trackContactButtonClicked( sectionName ); + if ( supportInteraction ) { + setCurrentSupportInteraction( supportInteraction ); + } + } } className={ clsx( 'help-center-support-chat__conversation-container', { 'is-unread-message': isUnread, } ) } diff --git a/packages/help-center/src/components/utils.tsx b/packages/help-center/src/components/utils.tsx index d866246d664b02..795b961d0f05ab 100644 --- a/packages/help-center/src/components/utils.tsx +++ b/packages/help-center/src/components/utils.tsx @@ -1,6 +1,6 @@ import { recordTracksEvent } from '@automattic/calypso-analytics'; import type { ContactOption } from '../types'; -import type { ZendeskConversation } from '@automattic/odie-client'; +import type { ZendeskConversation, SupportInteraction } from '@automattic/odie-client'; export const generateContactOnClickEvent = ( contactOption: ContactOption, @@ -22,7 +22,7 @@ export const getLastMessage = ( { conversation }: { conversation: ZendeskConvers : null; }; -export const getFilteredConversations = ( { +export const getSortedRecentAndArchivedConversations = ( { conversations, }: { conversations: ZendeskConversation[]; @@ -99,3 +99,19 @@ export const calculateUnread = ( conversations: ZendeskConversation[] ) => { return { unreadConversations, unreadMessages }; }; + +export const getClientId = ( conversations: ZendeskConversation[] ): string => + conversations + .flatMap( ( conversation ) => conversation.messages ) + .find( ( message ) => message.source.type === 'web' && message.source.id )?.source.id || ''; + +export const getConversationsFromSupportInteractions = ( + conversations: ZendeskConversation[], + supportInteractions: SupportInteraction[] +) => { + return conversations.filter( ( conversation ) => + supportInteractions.some( + ( interaction ) => interaction.uuid === conversation.metadata?.supportInteractionId + ) + ); +}; diff --git a/packages/help-center/src/hooks/use-reset-support-interaction.ts b/packages/help-center/src/hooks/use-reset-support-interaction.ts new file mode 100644 index 00000000000000..a781e3f1f8e46d --- /dev/null +++ b/packages/help-center/src/hooks/use-reset-support-interaction.ts @@ -0,0 +1,29 @@ +import { HelpCenterSelect } from '@automattic/data-stores'; +import { HELP_CENTER_STORE } from '@automattic/help-center/src/stores'; +import { useManageSupportInteraction } from '@automattic/odie-client/src/data'; +import { useQueryClient } from '@tanstack/react-query'; +import { useSelect, useDispatch } from '@wordpress/data'; + +export const useResetSupportInteraction = () => { + const { currentSupportInteraction } = useSelect( ( select ) => { + const store = select( HELP_CENTER_STORE ) as HelpCenterSelect; + return { + currentSupportInteraction: store.getCurrentSupportInteraction(), + }; + }, [] ); + const { setCurrentSupportInteraction } = useDispatch( HELP_CENTER_STORE ); + const { resolveInteraction } = useManageSupportInteraction(); + const queryClient = useQueryClient(); + + const reset = async () => { + if ( currentSupportInteraction ) { + resolveInteraction( { interactionId: currentSupportInteraction.uuid } ); + await queryClient.invalidateQueries( { + queryKey: [ 'support-interactions', 'get-interactions', 'help-center' ], + } ); + setCurrentSupportInteraction( null ); + } + }; + + return reset; +}; diff --git a/packages/i18n-utils/src/locales.ts b/packages/i18n-utils/src/locales.ts index ff66f706e995ff..bdbfe5fb72888b 100644 --- a/packages/i18n-utils/src/locales.ts +++ b/packages/i18n-utils/src/locales.ts @@ -135,3 +135,5 @@ export const jetpackComLocales: Locale[] = [ 'zh-cn', 'zh-tw', ]; + +export const wpLoginLocales: Locale[] = [ ...magnificentNonEnLocales, 'ro', 'vi' ]; diff --git a/packages/i18n-utils/src/localize-url.tsx b/packages/i18n-utils/src/localize-url.tsx index 0b87a39d0dc910..c268f39ef2ddba 100644 --- a/packages/i18n-utils/src/localize-url.tsx +++ b/packages/i18n-utils/src/localize-url.tsx @@ -13,6 +13,7 @@ import { magnificentNonEnLocales, localesForPricePlans, jetpackComLocales, + wpLoginLocales, Locale, } from './locales'; @@ -128,7 +129,7 @@ export const urlLocalizationMapping: UrlLocalizationMapping = { 'wordpress.com/pricing/': prefixLocalizedUrlPath( localesForPricePlans ), 'wordpress.com/tos/': prefixLocalizedUrlPath( magnificentNonEnLocales ), 'wordpress.com/wp-admin/': setLocalizedUrlHost( 'wordpress.com', magnificentNonEnLocales ), - 'wordpress.com/wp-login.php': setLocalizedUrlHost( 'wordpress.com', magnificentNonEnLocales ), + 'wordpress.com/wp-login.php': setLocalizedUrlHost( 'wordpress.com', wpLoginLocales ), 'jetpack.com': prefixLocalizedUrlPath( jetpackComLocales ), 'cloud.jetpack.com': prefixLocalizedUrlPath( jetpackComLocales ), 'en.support.wordpress.com': setLocalizedWpComPath( '/support', supportSiteLocales ), diff --git a/packages/jetpack-ai-calypso/package.json b/packages/jetpack-ai-calypso/package.json index bf50232a62445a..ecf00d72d8d740 100644 --- a/packages/jetpack-ai-calypso/package.json +++ b/packages/jetpack-ai-calypso/package.json @@ -51,7 +51,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "typescript": "^5.3.3", - "webpack": "^5.94.0" + "webpack": "^5.95.0" }, "peerDependencies": { "@babel/runtime": "^7.25.7", diff --git a/packages/languages/package.json b/packages/languages/package.json index 159fe2b624f471..e7c108a7bbca32 100644 --- a/packages/languages/package.json +++ b/packages/languages/package.json @@ -40,6 +40,6 @@ "react-dom": "^18.3.1", "redux": "^4.2.1", "typescript": "^5.3.3", - "webpack": "^5.94.0" + "webpack": "^5.95.0" } } diff --git a/packages/launchpad-navigator/package.json b/packages/launchpad-navigator/package.json index ca8b2af3dd27a8..1b0ccca0c80191 100644 --- a/packages/launchpad-navigator/package.json +++ b/packages/launchpad-navigator/package.json @@ -47,7 +47,7 @@ "react-dom": "^18.3.1", "redux": "^4.2.1", "typescript": "^5.3.3", - "webpack": "^5.94.0" + "webpack": "^5.95.0" }, "peerDependencies": { "@wordpress/data": "^10.8.0", diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json index 452637a6d77212..a19b62c69c22b4 100644 --- a/packages/launchpad/package.json +++ b/packages/launchpad/package.json @@ -61,7 +61,7 @@ "react-dom": "^18.3.1", "redux": "^4.2.1", "typescript": "^5.3.3", - "webpack": "^5.94.0" + "webpack": "^5.95.0" }, "peerDependencies": { "@wordpress/data": "^10.8.0", diff --git a/packages/odie-client/README.md b/packages/odie-client/README.md index 57ed1365c4dd6d..df83581641069b 100644 --- a/packages/odie-client/README.md +++ b/packages/odie-client/README.md @@ -40,35 +40,6 @@ const MyApp = () => ( - `extraContactOptions?: ReactNode` - Show extra options for exiting the chat. - `children?: ReactNode` - Child components within the provider. -## Odie Storage - -Odie stores user's chat ID in Calypso's user preferences. So that the chat continuity works across Calypso, wp-admin, and wp-admin in Atomic sites. - -### Types - -```tsx -type OdieStorageKey = 'chat_id' | 'last_chat_id'; -``` - -### Methods - -```tsx -import { - useGetOdieStorage, - useSetOdieStorage, -} from '@automattic/odie-client'; - -// Usage examples -function Examples() { - const updateChatId = useSetOdieStorage( 'chat_id' ) - updateChatId( 'new_chat_id' ); - - const chatId = useGetOdieStorage( 'chat_id' ); -} -``` - -_Note: Setting `chat_id` fetches a new chat from the server and also sets `last_chat_id`. Clearing `chat_id` does not clear `last_chat_id`._ - ## Context API ### Context properties @@ -82,17 +53,9 @@ const defaultContextInterfaceValues = { clearChat: noop, // Function to clear the current chat. isLoading: false, // Flag for general loading state. isMinimized: false, // Flag to check if the chat is minimized. - isNudging: false, // Flag to check if a nudge action is occurring. - isVisible: false, // Flag to check if the chat is visible. - lastNudge: null, // Information about the last nudge action. - sendNudge: noop, // Function to trigger a nudge action. - setChat: noop, // Function to set the current chat. setMessageLikedStatus: noop, // Function to set the liked status of a message. - setIsNudging: noop, // Function to set the nudge state. - setIsVisible: noop, // Function to set the visibility of the chat. setIsLoading: noop, // Function to set the general loading state. trackEvent: noop, // Function to track events. - updateMessage: noop, // Function to update a message in the chat. }; ``` diff --git a/packages/odie-client/src/components/message/direct-escalation-link.tsx b/packages/odie-client/src/components/message/direct-escalation-link.tsx index 528b6bbafbe216..c7cdccfb1b0bf2 100644 --- a/packages/odie-client/src/components/message/direct-escalation-link.tsx +++ b/packages/odie-client/src/components/message/direct-escalation-link.tsx @@ -2,8 +2,8 @@ import { useCallback } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useNavigate } from 'react-router-dom'; import { useOdieAssistantContext } from '../../context'; -import { useCreateZendeskConversation } from '../../query/use-create-zendesk-conversation'; -import { getHelpCenterZendeskConversationStarted } from '../../utils/storage-utils'; +import { useCreateZendeskConversation } from '../../hooks'; +import { getHelpCenterZendeskConversationStarted } from '../../utils'; export const DirectEscalationLink = ( { messageId }: { messageId: number | undefined } ) => { const conversationStarted = Boolean( getHelpCenterZendeskConversationStarted() ); @@ -11,6 +11,10 @@ export const DirectEscalationLink = ( { messageId }: { messageId: number | undef const { shouldUseHelpCenterExperience, trackEvent, isUserEligibleForPaidSupport } = useOdieAssistantContext(); const navigate = useNavigate(); + + const disclaimerText = shouldUseHelpCenterExperience + ? __( 'Feeling stuck?', __i18n_text_domain__ ) + : __( 'Did you find the answer to your question?', __i18n_text_domain__ ); const handleClick = useCallback( () => { trackEvent( 'chat_message_direct_escalation_link_click', { message_id: messageId, @@ -41,7 +45,7 @@ export const DirectEscalationLink = ( { messageId }: { messageId: number | undef return (
    - { __( 'Feeling stuck?', __i18n_text_domain__ ) }{ ' ' } + { disclaimerText }{ ' ' }
    - + { __( - 'Would you like to contact our support team? Select an option below:', + 'Let’s get the information you need. Would you like to contact our support team?', __i18n_text_domain__ ) } @@ -48,12 +41,7 @@ export const DislikeFeedbackMessage = () => { { botName }
    - + { __( 'I’m sorry my last response didn’t meet your expectations! Here’s some other ways to get more in-depth help:', __i18n_text_domain__ diff --git a/packages/odie-client/src/components/message/error-message.tsx b/packages/odie-client/src/components/message/error-message.tsx index 09586345769d58..87682a6601bcaa 100644 --- a/packages/odie-client/src/components/message/error-message.tsx +++ b/packages/odie-client/src/components/message/error-message.tsx @@ -2,7 +2,7 @@ import Markdown from 'react-markdown'; import { useOdieAssistantContext } from '../../context'; import CustomALink from './custom-a-link'; import { uriTransformer } from './uri-transformer'; -import type { Message } from '../../types/'; +import type { Message } from '../../types'; export const ErrorMessage = ( { message }: { message: Message } ) => { const { extraContactOptions } = useOdieAssistantContext(); diff --git a/packages/odie-client/src/components/message/get-support.tsx b/packages/odie-client/src/components/message/get-support.tsx index 76a3fd2062f01a..a9a77514095028 100644 --- a/packages/odie-client/src/components/message/get-support.tsx +++ b/packages/odie-client/src/components/message/get-support.tsx @@ -1,6 +1,6 @@ import { __ } from '@wordpress/i18n'; import { useOdieAssistantContext } from '../../context'; -import { useCreateZendeskConversation } from '../../query/use-create-zendesk-conversation'; +import { useCreateZendeskConversation } from '../../hooks'; import './get-support.scss'; diff --git a/packages/odie-client/src/components/message/index.tsx b/packages/odie-client/src/components/message/index.tsx index 156bada8512337..1d0c3deb6da7a5 100644 --- a/packages/odie-client/src/components/message/index.tsx +++ b/packages/odie-client/src/components/message/index.tsx @@ -13,7 +13,7 @@ import WapuuAvatarSquared from '../../assets/wapuu-squared-avatar.svg'; import { useOdieAssistantContext } from '../../context'; import Button from '../button'; import { MessageContent } from './message-content'; -import type { CurrentUser, Message } from '../../types/'; +import type { CurrentUser, Message } from '../../types'; export type ChatMessageProps = { message: Message; diff --git a/packages/odie-client/src/components/message/jump-to-recent.tsx b/packages/odie-client/src/components/message/jump-to-recent.tsx index 1ece941dc83ae2..fa7c354eceea68 100644 --- a/packages/odie-client/src/components/message/jump-to-recent.tsx +++ b/packages/odie-client/src/components/message/jump-to-recent.tsx @@ -9,7 +9,7 @@ export const JumpToRecent = ( { }: { containerReference: RefObject< HTMLDivElement >; } ) => { - const { trackEvent, isMinimized, chat, chatStatus } = useOdieAssistantContext(); + const { trackEvent, isMinimized, chat } = useOdieAssistantContext(); const lastMessageRef = useRef< Element | null >( null ); const [ isLastMessageVisible, setIsLastMessageVisible ] = useState( false ); @@ -27,7 +27,7 @@ export const JumpToRecent = ( { ! containerReference.current || isMinimized || chat.messages.length < 2 || - chatStatus !== 'loaded' + chat.status !== 'loaded' ) { return; } @@ -52,7 +52,7 @@ export const JumpToRecent = ( { }; }, [ chat.messages.length ] ); - if ( isMinimized || chat.messages.length < 2 || chatStatus !== 'loaded' ) { + if ( isMinimized || chat.messages.length < 2 || chat.status !== 'loaded' ) { return null; } diff --git a/packages/odie-client/src/components/message/message-content.tsx b/packages/odie-client/src/components/message/message-content.tsx index 3c842d6e31bee4..aefb01f03861e1 100644 --- a/packages/odie-client/src/components/message/message-content.tsx +++ b/packages/odie-client/src/components/message/message-content.tsx @@ -1,7 +1,7 @@ import clsx from 'clsx'; import Markdown from 'react-markdown'; import { useOdieAssistantContext } from '../../context'; -import { Message } from '../../types/'; +import { Message } from '../../types'; import ChatWithSupportLabel from '../chat-with-support'; import CustomALink from './custom-a-link'; import DislikeFeedbackMessage from './dislike-feedback-message'; diff --git a/packages/odie-client/src/components/message/messages-container.tsx b/packages/odie-client/src/components/message/messages-container.tsx index 5354b44ebaaf83..77d0b35fc3f198 100644 --- a/packages/odie-client/src/components/message/messages-container.tsx +++ b/packages/odie-client/src/components/message/messages-container.tsx @@ -2,13 +2,13 @@ import { getShortDateString } from '@automattic/i18n-utils'; import { useRef } from 'react'; import { ThumbsDown } from '../../assets/thumbs-down'; import { useOdieAssistantContext } from '../../context'; -import useAutoScroll from '../../useAutoScroll'; -import { useZendeskMessageListener } from '../../utils'; +import { useAutoScroll, useZendeskMessageListener } from '../../hooks'; +import { getOdieInitialMessage } from '../../utils'; import { DislikeFeedbackMessage } from './dislike-feedback-message'; import { JumpToRecent } from './jump-to-recent'; import { ThinkingPlaceholder } from './thinking-placeholder'; import ChatMessage from '.'; -import type { Chat, CurrentUser } from '../../types/'; +import type { Chat, CurrentUser } from '../../types'; const DislikeThumb = () => { return ( @@ -30,7 +30,8 @@ interface ChatMessagesProps { } export const MessagesContainer = ( { currentUser }: ChatMessagesProps ) => { - const { chat, chatStatus, shouldUseHelpCenterExperience } = useOdieAssistantContext(); + const { chat, shouldUseHelpCenterExperience, botNameSlug } = useOdieAssistantContext(); + const messagesContainerRef = useRef< HTMLDivElement >( null ); useZendeskMessageListener(); useAutoScroll( messagesContainerRef ); @@ -44,6 +45,13 @@ export const MessagesContainer = ( { currentUser }: ChatMessagesProps ) => { <>
    { shouldUseHelpCenterExperience && } + { chat.messages.map( ( message, index ) => ( { /> ) ) } - { chatStatus === 'dislike' && shouldUseHelpCenterExperience && } - { [ 'sending', 'dislike' ].includes( chatStatus ) && ( + { chat.status === 'dislike' && shouldUseHelpCenterExperience && } + { [ 'sending', 'dislike', 'transfer' ].includes( chat.status ) && (
    - { chatStatus === 'sending' && } - { chatStatus === 'dislike' && } + { chat.status === 'sending' && } + { chat.status === 'dislike' && }
    ) }
    diff --git a/packages/odie-client/src/components/message/sources.tsx b/packages/odie-client/src/components/message/sources.tsx index 283395ad593309..9195a93beb13b8 100644 --- a/packages/odie-client/src/components/message/sources.tsx +++ b/packages/odie-client/src/components/message/sources.tsx @@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { useOdieAssistantContext } from '../../context'; import FoldableCard from '../foldable'; import SupportDocLink from '../support-link'; -import type { Message, Source } from '../../types/'; +import type { Message, Source } from '../../types'; export const Sources = ( { message }: { message: Message } ) => { const navigate = useNavigate(); diff --git a/packages/odie-client/src/components/message/style_redesign.scss b/packages/odie-client/src/components/message/style_redesign.scss index ee7b7b721778a9..e4d87216729d0e 100644 --- a/packages/odie-client/src/components/message/style_redesign.scss +++ b/packages/odie-client/src/components/message/style_redesign.scss @@ -67,14 +67,12 @@ $blueberry-color: #3858e9; } } } - h3 { - padding: 0 16px; - } &.odie-chatbox-message-bot { .odie-chatbox-message__content { background-color: var(--studio-gray-0); flex: 1; + padding: 16px; /* stylelint-disable-next-line scales/radii */ border-radius: 8px 8px 8px 0; max-width: 256px; @@ -126,6 +124,10 @@ $blueberry-color: #3858e9; overflow-x: hidden !important; } +.odie-chatbox-message p:first-of-type { + padding-top: 0; +} + .odie-chatbox-message p:last-of-type { margin-bottom: 0; } @@ -154,9 +156,8 @@ $blueberry-color: #3858e9; /* stylelint-disable-next-line scales/radii */ border-radius: 8px 8px 8px 0 !important; background-color:#F7F8FE !important; - + padding: 16px; } - } .odie-chatbox-message.odie-chatbox-message-bot.odie-chatbox-message-message { @@ -168,7 +169,8 @@ $blueberry-color: #3858e9; .odie-chatbox-message p { font-size: 0.875rem; - padding: 16px; + padding-bottom: 16px; + padding-top: 16px; margin-bottom: 0; } @@ -181,6 +183,7 @@ $blueberry-color: #3858e9; /* stylelint-disable-next-line scales/radii */ border-radius: 8px 8px 8px 0; max-width: 256px; + padding: 16px; p { color: var(--studio-gray-80); line-height: 20px; diff --git a/packages/odie-client/src/types.d.ts b/packages/odie-client/src/components/message/types.d.ts similarity index 100% rename from packages/odie-client/src/types.d.ts rename to packages/odie-client/src/components/message/types.d.ts diff --git a/packages/odie-client/src/components/message/uri-transformer.ts b/packages/odie-client/src/components/message/uri-transformer.ts index 5f4a3fd0abf5b8..d21d8ae08d5a2d 100644 --- a/packages/odie-client/src/components/message/uri-transformer.ts +++ b/packages/odie-client/src/components/message/uri-transformer.ts @@ -4,7 +4,7 @@ // protocols in the future, but for now, this is enough. That would REALLY simplify things for // us, because adding a new protocol would be as simple as adding it to the array above, // and extending the component custom-a-link.tsx to handle it. That's it. -const protocols = [ 'http', 'https', 'mailto', 'tel', 'prompt' ]; +const protocols = [ 'http', 'https', 'mailto', 'tel', 'prompt', 'blob' ]; const referralCodes: { [ key: string ]: string } = { https: 'odie', @@ -34,9 +34,11 @@ export function uriTransformer( uri: string ) { const protocol = protocols[ index ]; if ( colon === protocol.length && url.slice( 0, protocol.length ).toLowerCase() === protocol ) { - // Add referral code to the URL const urlObj = new URL( url ); - urlObj.searchParams.set( 'ref', referralCodes[ protocol ] ); + // Add referral code to the URL + if ( protocol !== 'blob' ) { + urlObj.searchParams.set( 'ref', referralCodes[ protocol ] ); + } return urlObj.toString(); } } diff --git a/packages/odie-client/src/components/message/user-message.tsx b/packages/odie-client/src/components/message/user-message.tsx index cea22ee1e78d0e..a739442a321cd4 100644 --- a/packages/odie-client/src/components/message/user-message.tsx +++ b/packages/odie-client/src/components/message/user-message.tsx @@ -8,7 +8,7 @@ import { DirectEscalationLink } from './direct-escalation-link'; import { GetSupport } from './get-support'; import { uriTransformer } from './uri-transformer'; import WasThisHelpfulButtons from './was-this-helpful-buttons'; -import type { Message } from '../../types/'; +import type { Message } from '../../types'; export const UserMessage = ( { message, diff --git a/packages/odie-client/src/components/message/was-this-helpful-buttons.tsx b/packages/odie-client/src/components/message/was-this-helpful-buttons.tsx index 6c5457e50b6268..093d979b37fad2 100644 --- a/packages/odie-client/src/components/message/was-this-helpful-buttons.tsx +++ b/packages/odie-client/src/components/message/was-this-helpful-buttons.tsx @@ -2,9 +2,9 @@ import { __ } from '@wordpress/i18n'; import clsx from 'clsx'; import { ODIE_THUMBS_DOWN_RATING_VALUE, ODIE_THUMBS_UP_RATING_VALUE } from '../../constants'; import { useOdieAssistantContext } from '../../context'; -import { useSendOdieFeedback } from '../../query/use-send-odie-feedback'; +import { useSendOdieFeedback } from '../../data'; import { ThumbsDownIcon, ThumbsUpIcon } from './thumbs-icons'; -import type { Message } from '../../types/'; +import type { Message } from '../../types'; const WasThisHelpfulButtons = ( { message, @@ -13,7 +13,8 @@ const WasThisHelpfulButtons = ( { message: Message; isDisliked?: boolean; } ) => { - const { setMessageLikedStatus, trackEvent, setChatStatus } = useOdieAssistantContext(); + const { setMessageLikedStatus, trackEvent, setChatStatus, shouldUseHelpCenterExperience } = + useOdieAssistantContext(); const { mutateAsync: sendOdieMessageFeedback } = useSendOdieFeedback(); const liked = message.liked === true; @@ -74,13 +75,17 @@ const WasThisHelpfulButtons = ( { 'odie-question-collapse': rated || isDisliked, } ); + const feedbackThankYouMessage = shouldUseHelpCenterExperience + ? __( 'We appreciate your feedback.', __i18n_text_domain__ ) + : __( 'Thanks!', __i18n_text_domain__ ); + return (
    { __( 'Was this helpful?', __i18n_text_domain__ ) } - { __( 'Thanks!', __i18n_text_domain__ ) } + { feedbackThankYouMessage }