Skip to content

Commit

Permalink
Stats: Add new floating feedback panel (#94129)
Browse files Browse the repository at this point in the history
* Add FeedbackPanel component

* Extract content UI to new component

* Reconnect button handler

* Fix typo

* Add shared content to floating panel

* Add shared interface for props

* Remove logging

* Move modal component up a level

* Factor out the card from the wrapper

* Rename top-level component

* Support feedback actions per button

* Update card-specific styles

* Add panel-specific styles

* Refactor shared styles

* Add close button to panel

* Add styles for close button

* Wire up the close button

* Add state for panel visibility

* Moving code around

* Fix panel positioning

* Update client/my-sites/stats/feedback/style.scss

Co-authored-by: Dognose <[email protected]>

* Update client/my-sites/stats/feedback/style.scss

Co-authored-by: Dognose <[email protected]>

* Update client/my-sites/stats/feedback/style.scss

Co-authored-by: Dognose <[email protected]>

* Update client/my-sites/stats/feedback/style.scss

Co-authored-by: Dognose <[email protected]>

* Update client/my-sites/stats/feedback/style.scss

Co-authored-by: Dognose <[email protected]>

* Update client/my-sites/stats/feedback/style.scss

Co-authored-by: Dognose <[email protected]>

* Update client/my-sites/stats/feedback/style.scss

Co-authored-by: Dognose <[email protected]>

* Apply suggestions from code review

Co-authored-by: Dognose <[email protected]>

* formatting

* minor margin adjustment

---------

Co-authored-by: Jasper Kang <[email protected]>
Co-authored-by: Dognose <[email protected]>
  • Loading branch information
3 people authored Sep 3, 2024
1 parent 341ba1a commit 49c2e2e
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 55 deletions.
106 changes: 86 additions & 20 deletions client/my-sites/stats/feedback/index.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,108 @@
import { Button } from '@wordpress/components';
import { close } from '@wordpress/icons';
import { useTranslate } from 'i18n-calypso';
import { useState } from 'react';
import FeedbackModal from './modal';

import './style.scss';

function StatsFeedbackCard() {
const FEEDBACK_ACTION_LEAVE_REVIEW = 'feedback-action-leave-review';
const FEEDBACK_ACTION_SEND_FEEDBACK = 'feedback-action-send-feedback';
const FEEDBACK_ACTION_DISMISS_FLOATING_PANEL = 'feedback-action-dismiss-floating-panel';

interface FeedbackProps {
clickHandler: ( action: string ) => void;
}

interface FeedbackPanelProps {
clickHandler: ( action: string ) => void;
isOpen: boolean;
}

function FeedbackContent( { clickHandler }: FeedbackProps ) {
const translate = useTranslate();
const [ isOpen, setIsOpen ] = useState( false );

const ctaText = translate( 'How do you rate your overall experience with Jetpack Stats?' );
const primaryButtonText = translate( 'Love it? Leave a review' );
const secondaryButtonText = translate( 'Not a fan? Help us improve' );

const handleClickWriteReview = () => {};
const handleLeaveReview = () => {
clickHandler( FEEDBACK_ACTION_LEAVE_REVIEW );
};

const handleClickSendFeedback = () => {
setIsOpen( true );
const handleSendFeedback = () => {
clickHandler( FEEDBACK_ACTION_SEND_FEEDBACK );
};

return (
<div className="stats-feedback-container">
<div className="stats-feedback-card">
<div className="stats-feedback-card__cta">{ ctaText }</div>
<div className="stats-feedback-card__actions">
<Button variant="secondary" onClick={ handleClickWriteReview }>
<span className="stats-feedback-card__emoji">😍</span>
{ primaryButtonText }
</Button>
<Button variant="secondary" onClick={ handleClickSendFeedback }>
<span className="stats-feedback-card__emoji">😠</span>
{ secondaryButtonText }
</Button>
</div>
<FeedbackModal isOpen={ isOpen } onClose={ () => setIsOpen( false ) } />
<div className="stats-feedback-content">
<div className="stats-feedback-content__cta">{ ctaText }</div>
<div className="stats-feedback-content__actions">
<Button variant="secondary" onClick={ handleLeaveReview }>
<span className="stats-feedback-content__emoji">😍</span>
{ primaryButtonText }
</Button>
<Button variant="secondary" onClick={ handleSendFeedback }>
<span className="stats-feedback-content__emoji">😠</span>
{ secondaryButtonText }
</Button>
</div>
</div>
);
}

export default StatsFeedbackCard;
function FeedbackPanel( { isOpen, clickHandler }: FeedbackPanelProps ) {
const translate = useTranslate();

const handleCloseButtonClicked = () => {
clickHandler( FEEDBACK_ACTION_DISMISS_FLOATING_PANEL );
};

if ( ! isOpen ) {
return null;
}

return (
<div className="stats-feedback-panel">
<Button
className="stats-feedback-panel__close-button"
onClick={ handleCloseButtonClicked }
icon={ close }
label={ translate( 'Close' ) }
/>
<FeedbackContent clickHandler={ clickHandler } />
</div>
);
}

function FeedbackCard( { clickHandler }: FeedbackProps ) {
return (
<div className="stats-feedback-card">
<FeedbackContent clickHandler={ clickHandler } />
</div>
);
}

function StatsFeedbackController() {
const [ isOpen, setIsOpen ] = useState( false );
const [ isFloatingPanelOpen, setIsFloatingPanelOpen ] = useState( true );

const handleButtonClick = ( action: string ) => {
if ( action === FEEDBACK_ACTION_SEND_FEEDBACK ) {
setIsOpen( true );
}
if ( action === FEEDBACK_ACTION_DISMISS_FLOATING_PANEL ) {
setIsFloatingPanelOpen( false );
}
};

return (
<div className="stats-feedback-container">
<FeedbackCard clickHandler={ handleButtonClick } />
<FeedbackPanel isOpen={ isFloatingPanelOpen } clickHandler={ handleButtonClick } />
<FeedbackModal isOpen={ isOpen } onClose={ () => setIsOpen( false ) } />
</div>
);
}

export default StatsFeedbackController;
107 changes: 75 additions & 32 deletions client/my-sites/stats/feedback/style.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
@import "@automattic/components/src/styles/typography";
@import "@wordpress/base-styles/breakpoints";

.stats-feedback-content {
font-family: $font-sf-pro-text;
font-size: $font-body-small;
font-weight: 400;
line-height: 21px;
letter-spacing: -0.24px;
color: var(--studio-gray-100);
}

.stats-feedback-content__actions {
display: flex;
flex-direction: column;

.components-button {
margin-bottom: 6px;
width: fit-content;
font-weight: 500;
font-size: $font-body-small;
border-radius: 4px;
}
}

.stats-feedback-content__cta {
margin: 0 16px 16px 0;
}

.stats-feedback-content__emoji {
font-size: larger;
margin-right: 6px;
}

.stats-feedback-card {
background: var(--studio-white);
border: 1px solid var(--studio-gray-5);
Expand All @@ -15,47 +46,59 @@
border-right: none;
}

display: flex;
flex-direction: column;
.stats-feedback-content {
display: flex;
flex-direction: column;

@media (min-width: $break-wide) {
flex-direction: row;
justify-content: space-between;
align-items: center;
@media (min-width: $break-wide) {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
}

font-family: $font-sf-pro-text;
font-size: $font-body-small;
font-weight: 400;
line-height: 21px;
letter-spacing: -0.24px;
}

.stats-feedback-card__cta {
@media (max-width: $break-wide) {
margin-bottom: 12px;
.stats-feedback-content__cta {
@media (min-width: $break-wide) {
margin-right: 12px;
margin-bottom: 0;
}
}
}

.stats-feedback-card__actions {
display: flex;
flex-direction: column;
.stats-feedback-content__actions {
@media (min-width: $break-wide) {
flex-direction: row;

.components-button {
margin: 6px 0;
width: fit-content;
.components-button {
margin-left: 6px;
margin-bottom: 0;
}
}
}
}

@media (min-width: $break-wide) {
flex-direction: row;
.stats-feedback-panel {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 10;

.components-button {
margin: 0 6px;
}
}
border: 1px solid var(--studio-gray-5);
border-radius: 8px; // stylelint-disable-line scales/radii
padding: 24px;
width: 300px;
box-sizing: border-box;
box-shadow: 0 10px 20px 0 #00000014;

background-color: var(--studio-white);
}

.stats-feedback-card__emoji {
font-size: larger;
margin-right: 6px;
.stats-feedback-panel__close-button {
position: absolute;
top: 10px;
right: 10px;

svg {
width: 14px;
height: 14px;
}
}
6 changes: 3 additions & 3 deletions client/my-sites/stats/site.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import StatsModuleSearch from './features/modules/stats-search';
import StatsModuleTopPosts from './features/modules/stats-top-posts';
import StatsModuleUTM, { StatsModuleUTMOverlay } from './features/modules/stats-utm';
import StatsModuleVideos from './features/modules/stats-videos';
import StatsFeedbackCard from './feedback';
import StatsFeedbackController from './feedback';
import HighlightsSection from './highlights-section';
import { shouldGateStats } from './hooks/use-should-gate-stats';
import MiniCarousel from './mini-carousel';
Expand Down Expand Up @@ -242,7 +242,7 @@ class StatsSite extends Component {
shouldForceDefaultDateRange,
} = this.props;
const isNewStateEnabled = config.isEnabled( 'stats/empty-module-traffic' );
const isFeedbackCardEnabled = config.isEnabled( 'stats/user-feedback' );
const isUserFeedbackEnabled = config.isEnabled( 'stats/user-feedback' );
let defaultPeriod = PAST_SEVEN_DAYS;

const shouldShowUpsells = isOdysseyStats && ! isAtomic;
Expand Down Expand Up @@ -810,7 +810,7 @@ class StatsSite extends Component {
<AsyncLoad require="calypso/my-sites/stats/jetpack-upsell-section" />
) }
<PromoCards isOdysseyStats={ isOdysseyStats } pageSlug="traffic" slug={ slug } />
{ isFeedbackCardEnabled && <StatsFeedbackCard /> }
{ isUserFeedbackEnabled && <StatsFeedbackController /> }
<JetpackColophon />
<AsyncLoad require="calypso/lib/analytics/track-resurrections" placeholder={ null } />
{ this.props.upsellModalView && <StatsUpsellModal siteId={ siteId } /> }
Expand Down

0 comments on commit 49c2e2e

Please sign in to comment.