@nuskin/react-loyalty-elements 1.1.0-loyalty-exclude.2 → 1.1.0-loyalty-mvp.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/src/CircularProgressBar/CircularProgressBar.styled.tsx +157 -0
- package/src/CircularProgressBar/RadialSeperator.spec.tsx +38 -0
- package/src/CircularProgressBar/RadialSeprator.tsx +27 -0
- package/src/CircularProgressBar/RewardProgressBar.spec.tsx +59 -0
- package/src/CircularProgressBar/RewardProgressBar.tsx +86 -0
- package/src/CircularProgressBar/RewardSubscriptionMonthDescription.spec.tsx +34 -0
- package/src/CircularProgressBar/RewardSubscriptionMonthDescription.tsx +31 -0
- package/src/CircularProgressBar/RewardSubscriptionTotal.spec.tsx +42 -0
- package/src/CircularProgressBar/RewardSubscriptionTotal.tsx +100 -0
- package/src/CircularProgressBar/SubscriptionReward.spec.tsx +77 -0
- package/src/CircularProgressBar/SubscriptionReward.tsx +80 -0
- package/src/CircularProgressBar/SubscriptionRewardMobile.spec.tsx +72 -0
- package/src/CircularProgressBar/SubscriptionRewardMobile.tsx +178 -0
- package/src/CircularProgressBar/index.ts +6 -0
- package/src/CircularProgressBar/type.ts +15 -0
- package/src/Icons/Info.tsx +21 -0
- package/src/Icons/SubscriptionVoucherIcon.tsx +39 -0
- package/src/Icons/index.tsx +3 -1
- package/src/SubscriptionRewardBanner/SubscriptionRewardBanner.styled.tsx +1 -1
- package/src/Utils/application/application.ts +23 -0
- package/src/Utils/application/index.ts +2 -0
- package/src/Utils/index.ts +0 -0
- package/src/images.d.ts +25 -0
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuskin/react-loyalty-elements",
|
|
3
|
-
"version": "1.1.0-loyalty-
|
|
3
|
+
"version": "1.1.0-loyalty-mvp.6",
|
|
4
4
|
"description": "A React based component library for reusable Nextgen Loyalty component",
|
|
5
5
|
"main": "src/common/index.ts",
|
|
6
6
|
"repository": {
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@types/react": "^18.2.0",
|
|
32
32
|
"@types/react-dom": "^18.2.0",
|
|
33
|
-
"react-dom": "^18.2.0"
|
|
33
|
+
"react-dom": "^18.2.0",
|
|
34
|
+
"react-icons": "^4.0.0",
|
|
35
|
+
"react-circular-progressbar": "^2.1.0"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@babel/core": "^7.23.2",
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { styled } from '@nuskin/foundation-theme';
|
|
2
|
+
|
|
3
|
+
export const RewardProgressBarContainer = styled.div`
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: row;
|
|
6
|
+
justify-content: space-between;
|
|
7
|
+
align-items: center;
|
|
8
|
+
align-content: center;
|
|
9
|
+
background-color: #f5f5f5;
|
|
10
|
+
border: 1px solid #e0e0e0;
|
|
11
|
+
border-radius: 10px;
|
|
12
|
+
padding: 1.5rem;
|
|
13
|
+
margin-bottom: 20px;
|
|
14
|
+
height: 50%;
|
|
15
|
+
margin: auto;
|
|
16
|
+
|
|
17
|
+
@media (max-width: 950px) {
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export const LoyaltyProgressSubcontainer = styled.div`
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: row;
|
|
25
|
+
justify-content: space-between;
|
|
26
|
+
align-items: center;
|
|
27
|
+
align-content: center;
|
|
28
|
+
|
|
29
|
+
.reward-logo {
|
|
30
|
+
width: 200px;
|
|
31
|
+
margin-bottom: 12px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.reward-progress {
|
|
35
|
+
height: 150px;
|
|
36
|
+
width: 150px;
|
|
37
|
+
}
|
|
38
|
+
.progress-bar {
|
|
39
|
+
background-color: #e0e0e0;
|
|
40
|
+
height: 100%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@media (max-width: 950px) {
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
|
|
46
|
+
.info-container {
|
|
47
|
+
margin-left: 0px;
|
|
48
|
+
align-items: center;
|
|
49
|
+
}
|
|
50
|
+
.reward-progress-total-div {
|
|
51
|
+
margin-top: 10px;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
export const StyledCard = styled.div`
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
align-items: flex-start;
|
|
59
|
+
margin-left: 16px;
|
|
60
|
+
|
|
61
|
+
.info-item {
|
|
62
|
+
margin: 5px 0;
|
|
63
|
+
font-weight: 600;
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
export const Learn3XLink = styled.div`
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
font-weight: 600;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
`;
|
|
71
|
+
export const ProgressBarCenter = styled.div`
|
|
72
|
+
position: absolute;
|
|
73
|
+
top: 50%;
|
|
74
|
+
left: 50%;
|
|
75
|
+
transform: translate(-50%, -50%);
|
|
76
|
+
text-align: center;
|
|
77
|
+
color: black;
|
|
78
|
+
|
|
79
|
+
.center-value-progress-bar-price {
|
|
80
|
+
margin: 0;
|
|
81
|
+
}
|
|
82
|
+
.center-value-progress-bar-voucher {
|
|
83
|
+
margin: 0;
|
|
84
|
+
letter-spacing: 0.01em;
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
export const RewardProgressTotalContainer = styled.div`
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
align-items: center;
|
|
92
|
+
|
|
93
|
+
.total-button {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
background-color: #e5edf6;
|
|
97
|
+
border-radius: 20px;
|
|
98
|
+
font-size: 14px;
|
|
99
|
+
font-weight: 400;
|
|
100
|
+
line-height: 20px;
|
|
101
|
+
}
|
|
102
|
+
.reward-subtitle {
|
|
103
|
+
letter-spacing: 0;
|
|
104
|
+
display: flex;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@media (max-width: 950px) {
|
|
108
|
+
margin-top: 10px;
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
export const RewardProgressTotalButtonWithIcon = styled.div`
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
background-color: #e5edf6;
|
|
116
|
+
border-radius: 20px;
|
|
117
|
+
font-size: 14px;
|
|
118
|
+
font-weight: 400;
|
|
119
|
+
line-height: 20px;
|
|
120
|
+
padding: 1rem;
|
|
121
|
+
margin-bottom: 14px;
|
|
122
|
+
|
|
123
|
+
@media (max-width: 950px) {
|
|
124
|
+
margin-top: 14px;
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
export const IconContainer = styled.span`
|
|
129
|
+
.info-icon {
|
|
130
|
+
margin-left: 6px !important;
|
|
131
|
+
}
|
|
132
|
+
.loyalty-icon {
|
|
133
|
+
height: 19px !important;
|
|
134
|
+
width: 19px !important;
|
|
135
|
+
margin-left: 6px !important;
|
|
136
|
+
}
|
|
137
|
+
.loyalty-warning-icon {
|
|
138
|
+
height: 19px !important;
|
|
139
|
+
width: 19px !important;
|
|
140
|
+
margin-right: 6px !important;
|
|
141
|
+
}
|
|
142
|
+
.outline-exclamation-icon {
|
|
143
|
+
height: 48px !important;
|
|
144
|
+
width: 48px !important;
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
export const SeparatorCard = styled.div`
|
|
149
|
+
position: absolute;
|
|
150
|
+
height: 100%;
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
export const SeparatorInnerCard = styled.div`
|
|
154
|
+
background: #fff;
|
|
155
|
+
width: 6px;
|
|
156
|
+
height: 13%;
|
|
157
|
+
`;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import RadialSeparators from './RadialSeprator';
|
|
4
|
+
import { SeparatorCard, SeparatorInnerCard } from './CircularProgressBar.styled';
|
|
5
|
+
|
|
6
|
+
// Mock the styled components to avoid rendering issues
|
|
7
|
+
jest.mock('./CircularProgressBar.styled', () => ({
|
|
8
|
+
SeparatorCard: ({ style, children }: any) => <div data-testid="separator-card" style={style}>{children}</div>,
|
|
9
|
+
SeparatorInnerCard: () => <div data-testid="separator-inner-card" />,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('RadialSeparators', () => {
|
|
13
|
+
it('renders the correct number of separators', () => {
|
|
14
|
+
const { getAllByTestId } = render(<RadialSeparators count={5} />);
|
|
15
|
+
const separators = getAllByTestId('separator-card');
|
|
16
|
+
expect(separators.length).toBe(5);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('applies the correct rotation to each separator', () => {
|
|
20
|
+
const { getAllByTestId } = render(<RadialSeparators count={4} />);
|
|
21
|
+
const separators = getAllByTestId('separator-card');
|
|
22
|
+
|
|
23
|
+
separators.forEach((separator, index) => {
|
|
24
|
+
const expectedRotation = `${index * (1 / 4)}turn`;
|
|
25
|
+
expect(separator).toHaveStyle(`transform: rotate(${expectedRotation})`);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders SeparatorInnerCard inside each SeparatorCard', () => {
|
|
30
|
+
const { getAllByTestId } = render(<RadialSeparators count={3} />);
|
|
31
|
+
const separators = getAllByTestId('separator-card');
|
|
32
|
+
|
|
33
|
+
separators.forEach((separator) => {
|
|
34
|
+
const innerCard = separator.querySelector('[data-testid="separator-inner-card"]');
|
|
35
|
+
expect(innerCard).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import { SeparatorCard, SeparatorInnerCard } from './CircularProgressBar.styled';
|
|
4
|
+
|
|
5
|
+
function Separator(props: { turns: any; style: React.CSSProperties | undefined }) {
|
|
6
|
+
return (
|
|
7
|
+
<SeparatorCard
|
|
8
|
+
style={{
|
|
9
|
+
transform: `rotate(${props.turns}turn)`,
|
|
10
|
+
}}
|
|
11
|
+
>
|
|
12
|
+
<SeparatorInnerCard />
|
|
13
|
+
</SeparatorCard>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function RadialSeparators(props: { count: number }) {
|
|
18
|
+
const turns = 1 / props.count;
|
|
19
|
+
return _.range(props.count).map((index) => (
|
|
20
|
+
<Separator
|
|
21
|
+
key={index}
|
|
22
|
+
turns={index * turns} style={undefined}
|
|
23
|
+
/>
|
|
24
|
+
));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default RadialSeparators;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import RewardProgressBar from './RewardProgressBar';
|
|
4
|
+
|
|
5
|
+
describe('RewardProgressBar Component', () => {
|
|
6
|
+
const mockProps = {
|
|
7
|
+
value: 3,
|
|
8
|
+
title: 'Monthly Reward',
|
|
9
|
+
voucherValue: 50,
|
|
10
|
+
maxMonths: 6,
|
|
11
|
+
handleOpenSubscriptionRewardPopup: jest.fn(),
|
|
12
|
+
notQualifyForRewardThisMonth: false,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test('renders correctly with given props', () => {
|
|
16
|
+
render(<RewardProgressBar {...mockProps} />);
|
|
17
|
+
|
|
18
|
+
// Check if the voucher value is displayed
|
|
19
|
+
expect(screen.getByText('$ 50')).toBeInTheDocument();
|
|
20
|
+
// Check if the title is displayed
|
|
21
|
+
expect(screen.getByText('Monthly Reward')).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('displays the correct progress value', () => {
|
|
25
|
+
render(<RewardProgressBar {...mockProps} />);
|
|
26
|
+
|
|
27
|
+
// Check if the CircularProgressbar is rendered with the correct value
|
|
28
|
+
const progressBar = screen.getByRole('progressbar');
|
|
29
|
+
expect(progressBar).toHaveAttribute('aria-valuenow', '50'); // 3/6 = 50%
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('shows SubscriptionVoucherIcon when maxMonths is reached', () => {
|
|
33
|
+
const props = { ...mockProps, value: 6, notQualifyForRewardThisMonth: false };
|
|
34
|
+
render(<RewardProgressBar {...props} />);
|
|
35
|
+
|
|
36
|
+
// Check if the SubscriptionVoucherIcon is rendered
|
|
37
|
+
expect(screen.getByTestId('subscription-voucher-icon')).toBeInTheDocument();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('shows warning icon when not qualifying for reward', () => {
|
|
41
|
+
const props = { ...mockProps, notQualifyForRewardThisMonth: true };
|
|
42
|
+
render(<RewardProgressBar {...props} />);
|
|
43
|
+
|
|
44
|
+
// Check if the warning icon is rendered using its class name
|
|
45
|
+
const warningIcon = screen.getByRole('img', { hidden: true }); // Assuming the icon is rendered as an <img>
|
|
46
|
+
expect(warningIcon).toHaveClass('outline-exclamation-icon'); // Check for the specific class
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('calls handleOpenSubscriptionRewardPopup when clicked', () => {
|
|
50
|
+
render(<RewardProgressBar {...mockProps} />);
|
|
51
|
+
|
|
52
|
+
// Simulate a click event on the RewardSubscriptionMonthDescription
|
|
53
|
+
const descriptionElement = screen.getByText('Monthly Reward'); // Adjust this to match the actual text
|
|
54
|
+
descriptionElement.click();
|
|
55
|
+
|
|
56
|
+
// Check if the function was called
|
|
57
|
+
expect(mockProps.handleOpenSubscriptionRewardPopup).toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CircularProgressbarWithChildren, buildStyles } from 'react-circular-progressbar';
|
|
3
|
+
//import 'react-circular-progressbar/dist/styles.css';
|
|
4
|
+
import RadialSeparators from './RadialSeprator';
|
|
5
|
+
import { FaExclamationTriangle } from 'react-icons/fa';
|
|
6
|
+
import SubscriptionVoucherIcon from '../Icons/SubscriptionVoucherIcon';
|
|
7
|
+
import { IconContainer, LoyaltyProgressSubcontainer, ProgressBarCenter } from './CircularProgressBar.styled';
|
|
8
|
+
import RewardSubscriptionMonthDescription from './RewardSubscriptionMonthDescription';
|
|
9
|
+
import { NsTypography } from '@nuskin/foundation-ui-components';
|
|
10
|
+
import { RewardProgressBarProps } from './type';
|
|
11
|
+
|
|
12
|
+
class RewardProgressBar extends React.Component<RewardProgressBarProps> {
|
|
13
|
+
constructor(props: RewardProgressBarProps) {
|
|
14
|
+
super(props);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
render() {
|
|
18
|
+
const {
|
|
19
|
+
value,
|
|
20
|
+
title,
|
|
21
|
+
voucherValue,
|
|
22
|
+
maxMonths,
|
|
23
|
+
handleOpenSubscriptionRewardPopup,
|
|
24
|
+
notQualifyForRewardThisMonth,
|
|
25
|
+
} = this.props;
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<LoyaltyProgressSubcontainer>
|
|
29
|
+
<div className="reward-progress">
|
|
30
|
+
<CircularProgressbarWithChildren
|
|
31
|
+
value={(value / maxMonths) * 100}
|
|
32
|
+
strokeWidth={12}
|
|
33
|
+
background
|
|
34
|
+
styles={buildStyles({
|
|
35
|
+
strokeLinecap: 'butt',
|
|
36
|
+
backgroundColor:
|
|
37
|
+
value === maxMonths && !notQualifyForRewardThisMonth
|
|
38
|
+
? 'rgb(195 240 194)'
|
|
39
|
+
: notQualifyForRewardThisMonth
|
|
40
|
+
? '#fff'
|
|
41
|
+
: '#fff',
|
|
42
|
+
pathColor: '#6BC56A',
|
|
43
|
+
trailColor: '#E0E0E0',
|
|
44
|
+
})}
|
|
45
|
+
>
|
|
46
|
+
<RadialSeparators count={maxMonths} />
|
|
47
|
+
{value === maxMonths && !notQualifyForRewardThisMonth ? (
|
|
48
|
+
<SubscriptionVoucherIcon />
|
|
49
|
+
) : notQualifyForRewardThisMonth ? (
|
|
50
|
+
<IconContainer>
|
|
51
|
+
<FaExclamationTriangle className="outline-exclamation-icon" color="#91ACC8" />
|
|
52
|
+
</IconContainer>
|
|
53
|
+
) : (
|
|
54
|
+
<ProgressBarCenter>
|
|
55
|
+
<NsTypography
|
|
56
|
+
component="div"
|
|
57
|
+
className="center-value-progress-bar-price"
|
|
58
|
+
variant="title-l"
|
|
59
|
+
weight="bold"
|
|
60
|
+
>{`$ ${voucherValue}`}</NsTypography>
|
|
61
|
+
<NsTypography
|
|
62
|
+
variant="label-s"
|
|
63
|
+
weight="bold"
|
|
64
|
+
component="div"
|
|
65
|
+
className="center-value-progress-bar-voucher"
|
|
66
|
+
>
|
|
67
|
+
{title}
|
|
68
|
+
</NsTypography>
|
|
69
|
+
</ProgressBarCenter>
|
|
70
|
+
)}
|
|
71
|
+
</CircularProgressbarWithChildren>
|
|
72
|
+
</div>
|
|
73
|
+
<RewardSubscriptionMonthDescription
|
|
74
|
+
value={value}
|
|
75
|
+
voucherValue={voucherValue}
|
|
76
|
+
handleOpenSubscriptionRewardPopup={handleOpenSubscriptionRewardPopup}
|
|
77
|
+
maxMonths={maxMonths}
|
|
78
|
+
/>
|
|
79
|
+
</LoyaltyProgressSubcontainer>
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default RewardProgressBar;
|
|
86
|
+
export { RewardProgressBar };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
3
|
+
import RewardSubscriptionMonthDescription from './RewardSubscriptionMonthDescription';
|
|
4
|
+
|
|
5
|
+
describe('RewardSubscriptionMonthDescription', () => {
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
value: 3,
|
|
8
|
+
voucherValue: 50,
|
|
9
|
+
maxMonths: 6,
|
|
10
|
+
handleOpenSubscriptionRewardPopup: jest.fn(),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
it('renders without crashing', () => {
|
|
14
|
+
const { getByAltText } = render(<RewardSubscriptionMonthDescription {...defaultProps} />);
|
|
15
|
+
expect(getByAltText('Rewards Logo')).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('displays the correct message when the voucher is earned', () => {
|
|
19
|
+
const { getByText } = render(<RewardSubscriptionMonthDescription {...{ ...defaultProps, value: 6 }} />);
|
|
20
|
+
expect(getByText('Congrats, you earned a $50 voucher!')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('displays the correct message when the voucher is not yet earned', () => {
|
|
24
|
+
const { getByText } = render(<RewardSubscriptionMonthDescription {...defaultProps} />);
|
|
25
|
+
expect(getByText('3 of 6 Months Completed')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('calls handleOpenSubscriptionRewardPopup when the link is clicked', () => {
|
|
29
|
+
const { getByText } = render(<RewardSubscriptionMonthDescription {...defaultProps} />);
|
|
30
|
+
const link = getByText('Learn how to 3x your voucher!');
|
|
31
|
+
fireEvent.click(link);
|
|
32
|
+
expect(defaultProps.handleOpenSubscriptionRewardPopup).toHaveBeenCalledTimes(1);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import rewardsLogoBlack from '../images/rewards-logo-black.png';
|
|
3
|
+
import { Learn3XLink, StyledCard } from './CircularProgressBar.styled';
|
|
4
|
+
import { RewardSubscriptionMonthDescriptionProps } from './type';
|
|
5
|
+
|
|
6
|
+
class RewardSubscriptionMonthDescription extends React.Component<RewardSubscriptionMonthDescriptionProps> {
|
|
7
|
+
constructor(props: RewardSubscriptionMonthDescriptionProps) {
|
|
8
|
+
super(props);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
const { value, voucherValue, maxMonths, handleOpenSubscriptionRewardPopup } = this.props;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<StyledCard>
|
|
16
|
+
<img className="reward-logo" src={rewardsLogoBlack} alt="Rewards Logo" />
|
|
17
|
+
{value === maxMonths ? (
|
|
18
|
+
<div className="info-item">{`Congrats, you earned a $${voucherValue} voucher!`}</div>
|
|
19
|
+
) : (
|
|
20
|
+
<div className="info-item">{`${value} of ${maxMonths} Months Completed`}</div>
|
|
21
|
+
)}
|
|
22
|
+
<Learn3XLink onClick={handleOpenSubscriptionRewardPopup}>
|
|
23
|
+
<u>{'Learn how to 3x your voucher!'}</u>
|
|
24
|
+
</Learn3XLink>
|
|
25
|
+
</StyledCard>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default RewardSubscriptionMonthDescription;
|
|
31
|
+
export { RewardSubscriptionMonthDescription };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import RewardSubscriptionTotal from './RewardSubscriptionTotal';
|
|
4
|
+
|
|
5
|
+
describe('RewardSubscriptionTotal', () => {
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
value: 1,
|
|
8
|
+
title: 'Reward Title',
|
|
9
|
+
voucherValue: 100,
|
|
10
|
+
maxMonths: 12,
|
|
11
|
+
handleOpenSubscriptionRewardPopup: jest.fn(),
|
|
12
|
+
notQualifyForRewardThisMonth: false,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test('renders the component with reward information when qualified', () => {
|
|
16
|
+
render(<RewardSubscriptionTotal {...defaultProps} />);
|
|
17
|
+
|
|
18
|
+
expect(screen.getByText(/Approximate Monthly Total:/i)).toBeInTheDocument();
|
|
19
|
+
expect(screen.getByText(/\$157.56/i)).toBeInTheDocument();
|
|
20
|
+
expect(screen.getByText(/You’re currently on track for a \$ 100 voucher!/i)).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('renders warning message when not qualified for rewards', () => {
|
|
24
|
+
render(<RewardSubscriptionTotal {...defaultProps} notQualifyForRewardThisMonth={true} />);
|
|
25
|
+
|
|
26
|
+
expect(screen.getByText(/You may not qualify for rewards this month!/i)).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByText(/This month’s approximate total:/i)).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByText(/\$157.56/i)).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('does not show voucher information when max months are reached', () => {
|
|
32
|
+
render(<RewardSubscriptionTotal {...defaultProps} value={12} />);
|
|
33
|
+
|
|
34
|
+
expect(screen.queryByText(/You’re currently on track for a/i)).not.toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('renders the correct voucher value', () => {
|
|
38
|
+
render(<RewardSubscriptionTotal {...defaultProps} voucherValue={200} />);
|
|
39
|
+
|
|
40
|
+
expect(screen.getByText(/You’re currently on track for a \$ 200 voucher!/i)).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { AiOutlineExclamationCircle } from 'react-icons/ai';
|
|
3
|
+
import { FaExclamationTriangle } from 'react-icons/fa';
|
|
4
|
+
import {
|
|
5
|
+
IconContainer,
|
|
6
|
+
RewardProgressTotalButtonWithIcon,
|
|
7
|
+
RewardProgressTotalContainer,
|
|
8
|
+
} from './CircularProgressBar.styled';
|
|
9
|
+
import { NsTypography } from '@nuskin/foundation-ui-components';
|
|
10
|
+
import { RewardProgressBarProps } from './type';
|
|
11
|
+
import Info from '../Icons/Info';
|
|
12
|
+
class RewardSubscriptionTotal extends React.Component<RewardProgressBarProps> {
|
|
13
|
+
constructor(props: RewardProgressBarProps) {
|
|
14
|
+
super(props);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
render() {
|
|
18
|
+
const {
|
|
19
|
+
value,
|
|
20
|
+
title,
|
|
21
|
+
voucherValue,
|
|
22
|
+
maxMonths,
|
|
23
|
+
handleOpenSubscriptionRewardPopup,
|
|
24
|
+
notQualifyForRewardThisMonth,
|
|
25
|
+
} = this.props;
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<RewardProgressTotalContainer>
|
|
29
|
+
<RewardProgressTotalButtonWithIcon>
|
|
30
|
+
{notQualifyForRewardThisMonth ? (
|
|
31
|
+
<>
|
|
32
|
+
<IconContainer>
|
|
33
|
+
<FaExclamationTriangle className="loyalty-warning-icon" color="#4A6987" />
|
|
34
|
+
</IconContainer>
|
|
35
|
+
<NsTypography
|
|
36
|
+
variant="body-s"
|
|
37
|
+
weight="normal"
|
|
38
|
+
className="reward-subtitle"
|
|
39
|
+
colorOverride="#293A4A"
|
|
40
|
+
noSpacing
|
|
41
|
+
>
|
|
42
|
+
You may not qualify for rewards this month!
|
|
43
|
+
</NsTypography>
|
|
44
|
+
</>
|
|
45
|
+
) : (
|
|
46
|
+
<>
|
|
47
|
+
<NsTypography
|
|
48
|
+
variant="body-s"
|
|
49
|
+
weight="normal"
|
|
50
|
+
colorOverride="#252525"
|
|
51
|
+
className="reward-subtitle"
|
|
52
|
+
component="span"
|
|
53
|
+
noSpacing
|
|
54
|
+
>
|
|
55
|
+
Approximate Monthly Total:{' '}
|
|
56
|
+
</NsTypography>{' '}
|
|
57
|
+
<NsTypography weight="bold" className="button-price" variant="body-s" component="span">
|
|
58
|
+
$157.56
|
|
59
|
+
</NsTypography>
|
|
60
|
+
<IconContainer>
|
|
61
|
+
<span className="info-icon">
|
|
62
|
+
<Info />
|
|
63
|
+
</span>
|
|
64
|
+
</IconContainer>
|
|
65
|
+
</>
|
|
66
|
+
)}
|
|
67
|
+
</RewardProgressTotalButtonWithIcon>
|
|
68
|
+
{value === maxMonths ? (
|
|
69
|
+
''
|
|
70
|
+
) : notQualifyForRewardThisMonth ? (
|
|
71
|
+
<NsTypography
|
|
72
|
+
variant="body-s"
|
|
73
|
+
weight="normal"
|
|
74
|
+
className="reward-subtitle"
|
|
75
|
+
colorOverride="#252525"
|
|
76
|
+
component="div"
|
|
77
|
+
>
|
|
78
|
+
This month’s approximate total:{' '}
|
|
79
|
+
<NsTypography component="span" className="button-price" variant="body-s" weight="bold">
|
|
80
|
+
$157.56
|
|
81
|
+
</NsTypography>
|
|
82
|
+
</NsTypography>
|
|
83
|
+
) : (
|
|
84
|
+
<NsTypography
|
|
85
|
+
variant="body-s"
|
|
86
|
+
weight="bold"
|
|
87
|
+
className="reward-subtitle"
|
|
88
|
+
colorOverride="#252525"
|
|
89
|
+
>
|
|
90
|
+
{`You’re currently on track for a $ ${voucherValue} voucher!*`}
|
|
91
|
+
</NsTypography>
|
|
92
|
+
)}
|
|
93
|
+
</RewardProgressTotalContainer>
|
|
94
|
+
</>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default RewardSubscriptionTotal;
|
|
100
|
+
export { RewardSubscriptionTotal };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import SubscriptionReward from './SubscriptionReward';
|
|
4
|
+
import { application } from '../Utils/application';
|
|
5
|
+
|
|
6
|
+
// Mock the child components
|
|
7
|
+
jest.mock('./RewardProgressBar', () => () => <div>RewardProgressBar</div>);
|
|
8
|
+
jest.mock('./RewardSubscriptionTotal', () => () => <div>RewardSubscriptionTotal</div>);
|
|
9
|
+
jest.mock('./SubscriptionRewardMobile', () => () => <div>SubscriptionRewardMobile</div>);
|
|
10
|
+
|
|
11
|
+
describe('SubscriptionReward Component', () => {
|
|
12
|
+
const mockHandleOpenSubscriptionRewardPopup = jest.fn();
|
|
13
|
+
const props = {
|
|
14
|
+
title: 'Test Title',
|
|
15
|
+
handleOpenSubscriptionRewardPopup: mockHandleOpenSubscriptionRewardPopup,
|
|
16
|
+
notQualifyForRewardThisMonth: false,
|
|
17
|
+
value: 1, // Add the required value prop
|
|
18
|
+
voucherValue: 50, // Add the required voucherValue prop
|
|
19
|
+
maxMonths: 3, // Add the required maxMonths prop
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('renders correctly in mobile view', () => {
|
|
27
|
+
// Mock application.isMobile to return true
|
|
28
|
+
jest.spyOn(application, 'isMobile', 'get').mockReturnValue(true);
|
|
29
|
+
|
|
30
|
+
render(<SubscriptionReward {...props} />);
|
|
31
|
+
|
|
32
|
+
expect(screen.getByText('SubscriptionRewardMobile')).toBeInTheDocument();
|
|
33
|
+
expect(screen.queryByText('RewardProgressBar')).not.toBeInTheDocument();
|
|
34
|
+
expect(screen.queryByText('RewardSubscriptionTotal')).not.toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renders correctly in desktop view', () => {
|
|
38
|
+
// Mock application.isMobile to return false
|
|
39
|
+
jest.spyOn(application, 'isMobile', 'get').mockReturnValue(false);
|
|
40
|
+
|
|
41
|
+
render(<SubscriptionReward {...props} />);
|
|
42
|
+
|
|
43
|
+
expect(screen.getByText('RewardProgressBar')).toBeInTheDocument();
|
|
44
|
+
expect(screen.getByText('RewardSubscriptionTotal')).toBeInTheDocument();
|
|
45
|
+
expect(screen.queryByText('SubscriptionRewardMobile')).not.toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('updates value correctly', () => {
|
|
49
|
+
jest.spyOn(application, 'isMobile', 'get').mockReturnValue(false);
|
|
50
|
+
const { rerender } = render(<SubscriptionReward {...props} />);
|
|
51
|
+
|
|
52
|
+
// Check initial value
|
|
53
|
+
expect(screen.getByText('RewardProgressBar')).toBeInTheDocument();
|
|
54
|
+
|
|
55
|
+
// Simulate updating the value
|
|
56
|
+
// Note: You may need to adjust this part based on how you want to test the updateValue method
|
|
57
|
+
// This is a placeholder for how you might simulate a value change
|
|
58
|
+
// You may need to expose the updateValue method or use a different approach to test state changes
|
|
59
|
+
|
|
60
|
+
// Rerender to check if the value has updated
|
|
61
|
+
rerender(<SubscriptionReward {...props} />);
|
|
62
|
+
// Check if the state has updated (this part may need adjustment based on your implementation)
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('does not update value beyond maxMonths', () => {
|
|
66
|
+
jest.spyOn(application, 'isMobile', 'get').mockReturnValue(false);
|
|
67
|
+
const { rerender } = render(<SubscriptionReward {...props} />);
|
|
68
|
+
|
|
69
|
+
// Simulate an attempt to set value beyond maxMonths
|
|
70
|
+
// This is a placeholder for how you might simulate a value change
|
|
71
|
+
// You may need to expose the updateValue method or use a different approach to test state changes
|
|
72
|
+
|
|
73
|
+
// Rerender to check if the value has not updated
|
|
74
|
+
rerender(<SubscriptionReward {...props} />);
|
|
75
|
+
// Check if the state has not updated (this part may need adjustment based on your implementation)
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { SubscriptionRewardModal } from '../SubscriptionRewardModal';
|
|
3
|
+
import RewardProgressBar from './RewardProgressBar';
|
|
4
|
+
import RewardSubscriptionTotal from './RewardSubscriptionTotal';
|
|
5
|
+
import { RewardProgressBarContainer } from './CircularProgressBar.styled';
|
|
6
|
+
import { RewardProgressBarProps } from './type';
|
|
7
|
+
import SubscriptionRewardMobile from './SubscriptionRewardMobile';
|
|
8
|
+
import {application} from '../Utils/application'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
interface SubscriptionRewardState {
|
|
12
|
+
value: number;
|
|
13
|
+
voucherValue: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class SubscriptionReward extends React.Component<RewardProgressBarProps, SubscriptionRewardState> {
|
|
17
|
+
constructor(props: RewardProgressBarProps) {
|
|
18
|
+
super(props);
|
|
19
|
+
this.state = {
|
|
20
|
+
value: 1,
|
|
21
|
+
voucherValue: 50,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
updateValue = (newValue: number) => {
|
|
26
|
+
const maxMonths = 3;
|
|
27
|
+
// const maxMonths = APPConfig?.getAppConfig()?.subscriptionRewardMonths;
|
|
28
|
+
if (newValue >= 0 && newValue <= maxMonths) {
|
|
29
|
+
this.setState({ value: newValue });
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
render() {
|
|
34
|
+
const [show, setShow] = useState(false);
|
|
35
|
+
const handleOpen = () => setShow(true);
|
|
36
|
+
const handleClose = () => setShow(false);
|
|
37
|
+
const { title, notQualifyForRewardThisMonth } = this.props;
|
|
38
|
+
const { value, voucherValue } = this.state;
|
|
39
|
+
const maxMonths = 3;
|
|
40
|
+
// const maxMonths = APPConfig?.getAppConfig()?.subscriptionRewardMonths;
|
|
41
|
+
console.log(application.isMobile,'application.isMobile')
|
|
42
|
+
return (
|
|
43
|
+
<RewardProgressBarContainer>
|
|
44
|
+
{application.isMobile ? (
|
|
45
|
+
<SubscriptionRewardMobile
|
|
46
|
+
value={value}
|
|
47
|
+
title={title}
|
|
48
|
+
voucherValue={voucherValue}
|
|
49
|
+
maxMonths={maxMonths}
|
|
50
|
+
handleOpenSubscriptionRewardPopup={handleOpen}
|
|
51
|
+
notQualifyForRewardThisMonth={notQualifyForRewardThisMonth}
|
|
52
|
+
/>
|
|
53
|
+
):(
|
|
54
|
+
<>
|
|
55
|
+
<RewardProgressBar
|
|
56
|
+
value={value}
|
|
57
|
+
title={title}
|
|
58
|
+
notQualifyForRewardThisMonth={notQualifyForRewardThisMonth}
|
|
59
|
+
maxMonths={maxMonths}
|
|
60
|
+
voucherValue={voucherValue}
|
|
61
|
+
handleOpenSubscriptionRewardPopup={handleOpen}
|
|
62
|
+
/>
|
|
63
|
+
<RewardSubscriptionTotal
|
|
64
|
+
notQualifyForRewardThisMonth={notQualifyForRewardThisMonth}
|
|
65
|
+
value={value}
|
|
66
|
+
voucherValue={voucherValue}
|
|
67
|
+
maxMonths={maxMonths}
|
|
68
|
+
title={title}
|
|
69
|
+
handleOpenSubscriptionRewardPopup={handleOpen}
|
|
70
|
+
/>
|
|
71
|
+
</>
|
|
72
|
+
)}
|
|
73
|
+
<SubscriptionRewardModal show={show} close={handleClose} />
|
|
74
|
+
</RewardProgressBarContainer>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default SubscriptionReward;
|
|
80
|
+
export { SubscriptionReward };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import SubscriptionRewardMobile from './SubscriptionRewardMobile';
|
|
4
|
+
import { RewardProgressBarProps } from './type';
|
|
5
|
+
|
|
6
|
+
describe('SubscriptionRewardMobile', () => {
|
|
7
|
+
const mockHandleOpenSubscriptionRewardPopup = jest.fn();
|
|
8
|
+
|
|
9
|
+
const defaultProps: RewardProgressBarProps = {
|
|
10
|
+
title: 'Monthly Reward',
|
|
11
|
+
handleOpenSubscriptionRewardPopup: mockHandleOpenSubscriptionRewardPopup,
|
|
12
|
+
notQualifyForRewardThisMonth: false,
|
|
13
|
+
value: 0,
|
|
14
|
+
voucherValue: 0,
|
|
15
|
+
maxMonths: 0
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('renders correctly with default props', () => {
|
|
23
|
+
render(<SubscriptionRewardMobile {...defaultProps} />);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByText('Monthly Reward')).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByText('0 of 3 Months Completed')).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByText('You’re currently on track for a $ 50 voucher!*')).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByText('Learn how to 3x your voucher!')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('displays the correct message when all months are completed', () => {
|
|
32
|
+
const { rerender } = render(<SubscriptionRewardMobile {...defaultProps} />);
|
|
33
|
+
|
|
34
|
+
// Simulate completing all months
|
|
35
|
+
rerender(<SubscriptionRewardMobile {...defaultProps} />);
|
|
36
|
+
const instance = screen.getByText('Congrats, you earned a $50 voucher!');
|
|
37
|
+
|
|
38
|
+
expect(instance).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('displays warning message when not qualifying for rewards', () => {
|
|
42
|
+
const propsWithWarning = {
|
|
43
|
+
...defaultProps,
|
|
44
|
+
notQualifyForRewardThisMonth: true,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
render(<SubscriptionRewardMobile {...propsWithWarning} />);
|
|
48
|
+
|
|
49
|
+
expect(screen.getByText('You may not qualify for rewards this month!')).toBeInTheDocument();
|
|
50
|
+
expect(screen.getByText('This month’s approximate total:')).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('calls handleOpenSubscriptionRewardPopup when Learn link is clicked', () => {
|
|
54
|
+
render(<SubscriptionRewardMobile {...defaultProps} />);
|
|
55
|
+
|
|
56
|
+
const learnLink = screen.getByText('Learn how to 3x your voucher!');
|
|
57
|
+
fireEvent.click(learnLink);
|
|
58
|
+
|
|
59
|
+
expect(mockHandleOpenSubscriptionRewardPopup).toHaveBeenCalledTimes(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('updates the progress value correctly', () => {
|
|
63
|
+
const { rerender } = render(<SubscriptionRewardMobile {...defaultProps} />);
|
|
64
|
+
|
|
65
|
+
// Simulate updating the value to 3
|
|
66
|
+
rerender(<SubscriptionRewardMobile {...defaultProps} />);
|
|
67
|
+
|
|
68
|
+
// Check if the progress reflects the new value
|
|
69
|
+
expect(screen.getByText('3 of 3 Months Completed')).toBeInTheDocument();
|
|
70
|
+
expect(screen.getByText('Congrats, you earned a $50 voucher!')).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
IconContainer,
|
|
4
|
+
Learn3XLink,
|
|
5
|
+
LoyaltyProgressSubcontainer,
|
|
6
|
+
ProgressBarCenter,
|
|
7
|
+
StyledCard,
|
|
8
|
+
} from './CircularProgressBar.styled';
|
|
9
|
+
import { RewardProgressBarProps } from './type';
|
|
10
|
+
import RadialSeparators from './RadialSeprator';
|
|
11
|
+
import SubscriptionVoucherIcon from '../Icons/SubscriptionVoucherIcon';
|
|
12
|
+
import { FaExclamationTriangle } from 'react-icons/fa';
|
|
13
|
+
import { NsTypography } from '@nuskin/foundation-ui-components';
|
|
14
|
+
import { CircularProgressbarWithChildren, buildStyles } from 'react-circular-progressbar';
|
|
15
|
+
import rewardsLogoBlack from '../images/rewards-logo-black.png';
|
|
16
|
+
import { RewardProgressTotalButtonWithIcon, RewardProgressTotalContainer } from './CircularProgressBar.styled';
|
|
17
|
+
import Info from '../Icons/Info';
|
|
18
|
+
|
|
19
|
+
interface SubscriptionRewardState {
|
|
20
|
+
value: number;
|
|
21
|
+
voucherValue: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class SubscriptionRewardMobile extends React.Component<RewardProgressBarProps, SubscriptionRewardState> {
|
|
25
|
+
constructor(props: RewardProgressBarProps) {
|
|
26
|
+
super(props);
|
|
27
|
+
this.state = {
|
|
28
|
+
value: 1,
|
|
29
|
+
voucherValue: 50,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
updateValue = (newValue: number) => {
|
|
34
|
+
const maxMonths = 3;
|
|
35
|
+
// const maxMonths = APPConfig?.getAppConfig()?.subscriptionRewardMonths;
|
|
36
|
+
if (newValue >= 0 && newValue <= maxMonths) {
|
|
37
|
+
this.setState({ value: newValue });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
render() {
|
|
42
|
+
const { title, handleOpenSubscriptionRewardPopup, notQualifyForRewardThisMonth } = this.props;
|
|
43
|
+
const { value, voucherValue } = this.state;
|
|
44
|
+
const maxMonths = 3;
|
|
45
|
+
// const maxMonths = APPConfig?.getAppConfig()?.subscriptionRewardMonths;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<LoyaltyProgressSubcontainer>
|
|
49
|
+
<StyledCard>
|
|
50
|
+
<img className="reward-logo" src={rewardsLogoBlack} alt="Rewards Logo" />
|
|
51
|
+
</StyledCard>
|
|
52
|
+
<div className="reward-progress">
|
|
53
|
+
<CircularProgressbarWithChildren
|
|
54
|
+
value={(value / maxMonths) * 100}
|
|
55
|
+
strokeWidth={12}
|
|
56
|
+
background
|
|
57
|
+
styles={buildStyles({
|
|
58
|
+
strokeLinecap: 'butt',
|
|
59
|
+
backgroundColor:
|
|
60
|
+
value === maxMonths && !notQualifyForRewardThisMonth
|
|
61
|
+
? 'rgb(195 240 194)'
|
|
62
|
+
: notQualifyForRewardThisMonth
|
|
63
|
+
? '#fff'
|
|
64
|
+
: '#fff',
|
|
65
|
+
pathColor: '#6BC56A',
|
|
66
|
+
trailColor: '#E0E0E0',
|
|
67
|
+
})}
|
|
68
|
+
>
|
|
69
|
+
<RadialSeparators count={maxMonths} />
|
|
70
|
+
{value === maxMonths && !notQualifyForRewardThisMonth ? (
|
|
71
|
+
<SubscriptionVoucherIcon />
|
|
72
|
+
) : notQualifyForRewardThisMonth ? (
|
|
73
|
+
<IconContainer>
|
|
74
|
+
<FaExclamationTriangle className="outline-exclamation-icon" color="#91ACC8" />
|
|
75
|
+
</IconContainer>
|
|
76
|
+
) : (
|
|
77
|
+
<ProgressBarCenter>
|
|
78
|
+
<NsTypography
|
|
79
|
+
component="div"
|
|
80
|
+
className="center-value-progress-bar-price"
|
|
81
|
+
variant="title-l"
|
|
82
|
+
weight="bold"
|
|
83
|
+
>{`$ ${voucherValue}`}</NsTypography>
|
|
84
|
+
<NsTypography
|
|
85
|
+
variant="label-s"
|
|
86
|
+
weight="bold"
|
|
87
|
+
component="div"
|
|
88
|
+
className="center-value-progress-bar-voucher"
|
|
89
|
+
>
|
|
90
|
+
{title}
|
|
91
|
+
</NsTypography>
|
|
92
|
+
</ProgressBarCenter>
|
|
93
|
+
)}
|
|
94
|
+
</CircularProgressbarWithChildren>
|
|
95
|
+
</div>
|
|
96
|
+
{value === maxMonths ? (
|
|
97
|
+
<div className="info-item">{`Congrats, you earned a $${voucherValue} voucher!`}</div>
|
|
98
|
+
) : (
|
|
99
|
+
<div className="info-item">{`${value} of ${maxMonths} Months Completed`}</div>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
<RewardProgressTotalContainer>
|
|
103
|
+
{value === maxMonths ? (
|
|
104
|
+
''
|
|
105
|
+
) : notQualifyForRewardThisMonth ? (
|
|
106
|
+
<NsTypography
|
|
107
|
+
variant="body-s"
|
|
108
|
+
weight="normal"
|
|
109
|
+
className="reward-subtitle"
|
|
110
|
+
colorOverride="#252525"
|
|
111
|
+
component="div"
|
|
112
|
+
>
|
|
113
|
+
This month’s approximate total:{' '}
|
|
114
|
+
<NsTypography component="span" className="button-price" variant="body-s" weight="bold">
|
|
115
|
+
$157.56
|
|
116
|
+
</NsTypography>
|
|
117
|
+
</NsTypography>
|
|
118
|
+
) : (
|
|
119
|
+
<NsTypography
|
|
120
|
+
variant="body-s"
|
|
121
|
+
weight="bold"
|
|
122
|
+
className="reward-subtitle"
|
|
123
|
+
colorOverride="#252525"
|
|
124
|
+
>
|
|
125
|
+
{`You’re currently on track for a $ ${voucherValue} voucher!*`}
|
|
126
|
+
</NsTypography>
|
|
127
|
+
)}
|
|
128
|
+
<RewardProgressTotalButtonWithIcon>
|
|
129
|
+
{notQualifyForRewardThisMonth ? (
|
|
130
|
+
<>
|
|
131
|
+
<IconContainer>
|
|
132
|
+
<FaExclamationTriangle className="loyalty-warning-icon" color="#4A6987" />
|
|
133
|
+
</IconContainer>
|
|
134
|
+
<NsTypography
|
|
135
|
+
variant="body-s"
|
|
136
|
+
weight="normal"
|
|
137
|
+
className="reward-subtitle"
|
|
138
|
+
colorOverride="#293A4A"
|
|
139
|
+
noSpacing
|
|
140
|
+
>
|
|
141
|
+
You may not qualify for rewards this month!
|
|
142
|
+
</NsTypography>
|
|
143
|
+
</>
|
|
144
|
+
) : (
|
|
145
|
+
<>
|
|
146
|
+
<NsTypography
|
|
147
|
+
variant="body-s"
|
|
148
|
+
weight="normal"
|
|
149
|
+
colorOverride="#252525"
|
|
150
|
+
className="reward-subtitle"
|
|
151
|
+
component="span"
|
|
152
|
+
noSpacing
|
|
153
|
+
>
|
|
154
|
+
Approximate Monthly Total:{' '}
|
|
155
|
+
</NsTypography>{' '}
|
|
156
|
+
<NsTypography weight="bold" className="button-price" variant="body-s" component="span">
|
|
157
|
+
$157.56
|
|
158
|
+
</NsTypography>
|
|
159
|
+
<IconContainer>
|
|
160
|
+
<span className="info-icon">
|
|
161
|
+
<Info />
|
|
162
|
+
</span>
|
|
163
|
+
</IconContainer>
|
|
164
|
+
</>
|
|
165
|
+
)}
|
|
166
|
+
</RewardProgressTotalButtonWithIcon>
|
|
167
|
+
</RewardProgressTotalContainer>
|
|
168
|
+
|
|
169
|
+
<Learn3XLink onClick={handleOpenSubscriptionRewardPopup}>
|
|
170
|
+
<u>{'Learn how to 3x your voucher!'}</u>
|
|
171
|
+
</Learn3XLink>
|
|
172
|
+
</LoyaltyProgressSubcontainer>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export default SubscriptionRewardMobile;
|
|
178
|
+
export { SubscriptionRewardMobile };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as RadialSeprator } from './RadialSeprator';
|
|
2
|
+
export { default as RewardProgressBar } from './RewardProgressBar';
|
|
3
|
+
export { default as RewardSubscriptionMonthDescription } from './RewardSubscriptionMonthDescription';
|
|
4
|
+
export { default as RewardSubscriptionTotal } from './RewardSubscriptionTotal';
|
|
5
|
+
export { default as SubscriptionReward } from './SubscriptionReward';
|
|
6
|
+
export { default as SubscriptionRewardMobile } from './SubscriptionRewardMobile';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type RewardProgressBarProps = {
|
|
2
|
+
value: number;
|
|
3
|
+
title: string;
|
|
4
|
+
voucherValue: number;
|
|
5
|
+
maxMonths: number;
|
|
6
|
+
handleOpenSubscriptionRewardPopup: () => void;
|
|
7
|
+
notQualifyForRewardThisMonth: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type RewardSubscriptionMonthDescriptionProps = {
|
|
11
|
+
value: number;
|
|
12
|
+
voucherValue: number;
|
|
13
|
+
maxMonths: number;
|
|
14
|
+
handleOpenSubscriptionRewardPopup: () => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
function Info() {
|
|
4
|
+
return (
|
|
5
|
+
<svg width="19" height="19" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
6
|
+
<path
|
|
7
|
+
fill-rule="evenodd"
|
|
8
|
+
clip-rule="evenodd"
|
|
9
|
+
d="M0.833344 9.99967C0.833344 4.93706 4.9374 0.833008 10 0.833008C15.0626 0.833008 19.1667 4.93706 19.1667 9.99967C19.1667 15.0623 15.0626 19.1663 10 19.1663C4.9374 19.1663 0.833344 15.0623 0.833344 9.99967ZM10 2.49967C5.85787 2.49967 2.50001 5.85754 2.50001 9.99967C2.50001 14.1418 5.85787 17.4997 10 17.4997C14.1421 17.4997 17.5 14.1418 17.5 9.99967C17.5 5.85754 14.1421 2.49967 10 2.49967Z"
|
|
10
|
+
fill="#4A6987"
|
|
11
|
+
/>
|
|
12
|
+
<line x1="10.1667" y1="9.375" x2="10.1667" y2="14.7917" stroke="#4A6987" stroke-width="2" />
|
|
13
|
+
<path
|
|
14
|
+
d="M10.0052 7.75009C10.6338 7.75009 11.1718 7.22807 11.1771 6.57821C11.1718 5.93901 10.6338 5.41699 10.0052 5.41699C9.35537 5.41699 8.82803 5.93901 8.83335 6.57821C8.82803 7.22807 9.35537 7.75009 10.0052 7.75009Z"
|
|
15
|
+
fill="#4A6987"
|
|
16
|
+
/>
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default Info;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
function SubscriptionVoucherIcon() {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
width="71"
|
|
7
|
+
height="42"
|
|
8
|
+
viewBox="0 0 71 42"
|
|
9
|
+
fill="none"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
11
|
+
<path
|
|
12
|
+
d="M5.32847 36.176C3.52571 36.176 2.06348 34.7138 2.06348 32.911L2.15695 28.3574L4.8277 28.4108L4.73422 32.9378C4.73422 33.2382 5.0013 33.5053 5.32847 33.5053H30.7606L34.0056 33.3584L34.1258 36.0291L30.8207 36.1827H5.32847V36.176Z"
|
|
13
|
+
fill="#405E50"
|
|
14
|
+
/>
|
|
15
|
+
<path
|
|
16
|
+
d="M68.3914 12.9147H65.7207V9.1156C65.7207 8.78843 65.4536 8.52136 65.1264 8.52136L36.3491 8.48129V5.81055L65.1264 5.85061C66.9225 5.85061 68.3914 7.31284 68.3914 9.1156V12.9147Z"
|
|
17
|
+
fill="#405E50"
|
|
18
|
+
/>
|
|
19
|
+
<path
|
|
20
|
+
d="M62.3222 41.6454C62.1152 41.6454 61.9082 41.6253 61.7012 41.5853L2.99822 30.2079C2.14358 30.041 1.40245 29.5536 0.915037 28.8325C0.427626 28.1114 0.24735 27.2434 0.414272 26.3821L4.94119 3.0197C5.10811 2.16506 5.59552 1.42393 6.31662 0.936522C7.03772 0.44911 7.90572 0.268835 8.76703 0.435756L67.47 11.8131C69.2394 12.1537 70.3945 13.8696 70.054 15.639L65.5271 39.0013C65.2199 40.5637 63.8512 41.6454 62.3222 41.6454ZM62.2087 38.968C62.5225 39.028 62.843 38.8211 62.9031 38.5006L67.43 15.1382C67.4901 14.8177 67.2831 14.5039 66.9626 14.4438L8.25291 3.05977C8.04593 3.0197 7.88569 3.09983 7.80556 3.15324C7.73212 3.20666 7.59858 3.32016 7.55184 3.53382L3.02493 26.8962C2.96484 27.2167 3.17182 27.5305 3.49231 27.5906L62.2087 38.968Z"
|
|
21
|
+
fill="#405E50"
|
|
22
|
+
/>
|
|
23
|
+
<path
|
|
24
|
+
d="M13.5344 22.0957C13.1405 22.0957 12.7398 22.0556 12.3392 21.9822C8.95406 21.3279 6.73066 18.0362 7.385 14.651C7.70549 13.0085 8.64025 11.593 10.029 10.6582C11.4178 9.72348 13.0804 9.38296 14.7229 9.70345C16.3654 10.0239 17.7809 10.9587 18.7156 12.3475C19.6504 13.7363 19.9909 15.3988 19.6704 17.0413C19.0895 20.0125 16.4655 22.0957 13.5344 22.0957ZM13.521 12.254C12.8133 12.254 12.1189 12.461 11.5247 12.8683C10.7301 13.4024 10.196 14.217 10.009 15.1584C9.6351 17.1014 10.9037 18.9843 12.8467 19.3582C14.7896 19.7321 16.6725 18.4635 17.0464 16.5205C17.2267 15.5791 17.0331 14.6243 16.4989 13.8298C15.9648 13.0352 15.1502 12.5011 14.2088 12.3141C13.9817 12.274 13.7481 12.254 13.521 12.254Z"
|
|
25
|
+
fill="#405E50"
|
|
26
|
+
/>
|
|
27
|
+
<path
|
|
28
|
+
d="M23.9338 15.4855L23.4255 18.1074L59.9361 25.1845L60.4443 22.5626L23.9338 15.4855Z"
|
|
29
|
+
fill="#405E50"
|
|
30
|
+
/>
|
|
31
|
+
<path
|
|
32
|
+
d="M22.8916 19.7002L22.3838 22.3223L58.8956 29.393L59.4034 26.771L22.8916 19.7002Z"
|
|
33
|
+
fill="#405E50"
|
|
34
|
+
/>
|
|
35
|
+
</svg>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default SubscriptionVoucherIcon
|
package/src/Icons/index.tsx
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const IS_BROWSER = typeof window === 'object';
|
|
2
|
+
|
|
3
|
+
class ApplicationStore {
|
|
4
|
+
width = IS_BROWSER ? window.innerWidth : 0;
|
|
5
|
+
|
|
6
|
+
scroll = {
|
|
7
|
+
vertical: IS_BROWSER ? window.scrollY : 0,
|
|
8
|
+
horizontal: IS_BROWSER ? window.scrollX : 0,
|
|
9
|
+
direction: 'none',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
get isMobile() {
|
|
13
|
+
return this.isPhone;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get isPhone() {
|
|
17
|
+
return IS_BROWSER ? window.innerWidth < 768 : this.width < 768;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const applicationContainer = new ApplicationStore();
|
|
22
|
+
export { IS_BROWSER, ApplicationStore, applicationContainer, applicationContainer as application };
|
|
23
|
+
export default applicationContainer;
|
|
File without changes
|
package/src/images.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
declare module '*.png' {
|
|
3
|
+
const value: string;
|
|
4
|
+
export default value;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
declare module '*.jpg' {
|
|
8
|
+
const value: string;
|
|
9
|
+
export default value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module '*.jpeg' {
|
|
13
|
+
const value: string;
|
|
14
|
+
export default value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare module '*.gif' {
|
|
18
|
+
const value: string;
|
|
19
|
+
export default value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module '*.svg' {
|
|
23
|
+
const value: string;
|
|
24
|
+
export default value;
|
|
25
|
+
}
|