aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-rw-r--r--src/features/announcements/components/AnnouncementScreen.js41
-rw-r--r--src/features/appearance/index.js29
-rw-r--r--src/features/planSelection/components/PlanItem.js19
-rw-r--r--src/features/planSelection/components/PlanSelection.js76
-rw-r--r--src/features/planSelection/containers/PlanSelectionScreen.js4
-rw-r--r--src/features/todos/components/TodosWebview.js27
-rw-r--r--src/features/webControls/containers/WebControlsScreen.js5
7 files changed, 136 insertions, 65 deletions
diff --git a/src/features/announcements/components/AnnouncementScreen.js b/src/features/announcements/components/AnnouncementScreen.js
index 38de2dbc8..2f25e7139 100644
--- a/src/features/announcements/components/AnnouncementScreen.js
+++ b/src/features/announcements/components/AnnouncementScreen.js
@@ -192,6 +192,11 @@ class AnnouncementScreen extends Component {
192 stores: PropTypes.shape({ 192 stores: PropTypes.shape({
193 ui: PropTypes.instanceOf(UIStore).isRequired, 193 ui: PropTypes.instanceOf(UIStore).isRequired,
194 }).isRequired, 194 }).isRequired,
195 actions: PropTypes.shape({
196 app: PropTypes.shape({
197 openExternalUrl: PropTypes.func.isRequired,
198 }).isRequired,
199 }).isRequired,
195 }; 200 };
196 201
197 static contextTypes = { 202 static contextTypes = {
@@ -199,7 +204,7 @@ class AnnouncementScreen extends Component {
199 }; 204 };
200 205
201 render() { 206 render() {
202 const { classes, stores } = this.props; 207 const { classes, stores, actions } = this.props;
203 const { intl } = this.context; 208 const { intl } = this.context;
204 const { changelog, announcement } = announcementsStore; 209 const { changelog, announcement } = announcementsStore;
205 const themeImage = stores.ui.isDarkThemeActive ? 'dark' : 'light'; 210 const themeImage = stores.ui.isDarkThemeActive ? 'dark' : 'light';
@@ -223,14 +228,23 @@ class AnnouncementScreen extends Component {
223 __html: marked(announcement.main.text, markedOptions), 228 __html: marked(announcement.main.text, markedOptions),
224 }} 229 }}
225 /> 230 />
226 <div className={classes.mainCtaButton}> 231 {(announcement.main.cta.label || announcement.main.cta.href) && (
227 <Button 232 <div className={classes.mainCtaButton}>
228 label={announcement.main.cta.label} 233 <Button
229 onClick={() => { 234 label={announcement.main.cta.label}
230 window.location.href = `#${announcement.main.cta.href}`; 235 onClick={() => {
231 }} 236 const {
232 /> 237 href,
233 </div> 238 } = announcement.main.cta;
239 if (announcement.main.cta.href.startsWith('http')) {
240 actions.app.openExternalUrl({ url: href });
241 } else {
242 window.location.href = `#${href}`;
243 }
244 }}
245 />
246 </div>
247 )}
234 </div> 248 </div>
235 </div> 249 </div>
236 </div> 250 </div>
@@ -250,7 +264,14 @@ class AnnouncementScreen extends Component {
250 <Button 264 <Button
251 label={announcement.spotlight.cta.label} 265 label={announcement.spotlight.cta.label}
252 onClick={() => { 266 onClick={() => {
253 window.location.href = `#${announcement.spotlight.cta.href}`; 267 const {
268 href,
269 } = announcement.spotlight.cta;
270 if (announcement.spotlight.cta.href.startsWith('http')) {
271 actions.app.openExternalUrl({ url: href });
272 } else {
273 window.location.href = `#${href}`;
274 }
254 }} 275 }}
255 /> 276 />
256 </div> 277 </div>
diff --git a/src/features/appearance/index.js b/src/features/appearance/index.js
index 6dcdfc986..a14d1461e 100644
--- a/src/features/appearance/index.js
+++ b/src/features/appearance/index.js
@@ -63,6 +63,22 @@ function generateServiceRibbonWidthStyle(widthStr, iconSizeStr) {
63 `; 63 `;
64} 64}
65 65
66function generateShowDragAreaStyle(accentColor) {
67 return `
68 .sidebar {
69 padding-top: 0px !important;
70 }
71 .window-draggable {
72 position: initial;
73 background-color: ${accentColor};
74 }
75 #root {
76 /** Remove 22px from app height, otherwise the page will be to high */
77 height: calc(100% - 22px);
78 }
79 `;
80}
81
66function generateStyle(settings) { 82function generateStyle(settings) {
67 let style = ''; 83 let style = '';
68 84
@@ -70,6 +86,7 @@ function generateStyle(settings) {
70 accentColor, 86 accentColor,
71 serviceRibbonWidth, 87 serviceRibbonWidth,
72 iconSize, 88 iconSize,
89 showDragArea,
73 } = settings; 90 } = settings;
74 91
75 if (accentColor !== DEFAULT_APP_SETTINGS.accentColor) { 92 if (accentColor !== DEFAULT_APP_SETTINGS.accentColor) {
@@ -79,6 +96,9 @@ function generateStyle(settings) {
79 || iconSize !== DEFAULT_APP_SETTINGS.iconSize) { 96 || iconSize !== DEFAULT_APP_SETTINGS.iconSize) {
80 style += generateServiceRibbonWidthStyle(serviceRibbonWidth, iconSize); 97 style += generateServiceRibbonWidthStyle(serviceRibbonWidth, iconSize);
81 } 98 }
99 if (showDragArea) {
100 style += generateShowDragAreaStyle(accentColor);
101 }
82 102
83 return style; 103 return style;
84} 104}
@@ -121,4 +141,13 @@ export default function initAppearance(stores) {
121 updateStyle(settings.all.app); 141 updateStyle(settings.all.app);
122 }, 142 },
123 ); 143 );
144 // Update draggable area
145 reaction(
146 () => (
147 settings.all.app.showDragArea
148 ),
149 () => {
150 updateStyle(settings.all.app);
151 },
152 );
124} 153}
diff --git a/src/features/planSelection/components/PlanItem.js b/src/features/planSelection/components/PlanItem.js
index ec061377b..3855fedf1 100644
--- a/src/features/planSelection/components/PlanItem.js
+++ b/src/features/planSelection/components/PlanItem.js
@@ -49,6 +49,7 @@ const styles = theme => ({
49 priceWrapper: { 49 priceWrapper: {
50 height: 50, 50 height: 50,
51 marginBottom: 0, 51 marginBottom: 0,
52 marginTop: ({ text }) => (!text ? 15 : 0),
52 }, 53 },
53 price: { 54 price: {
54 fontSize: 50, 55 fontSize: 50,
@@ -64,7 +65,7 @@ const styles = theme => ({
64 cta: { 65 cta: {
65 background: theme.styleTypes.primary.accent, 66 background: theme.styleTypes.primary.accent,
66 color: theme.styleTypes.primary.contrast, 67 color: theme.styleTypes.primary.contrast,
67 margin: [40, 'auto', 0, 'auto'], 68 margin: [30, 'auto', 0, 'auto'],
68 }, 69 },
69 divider: { 70 divider: {
70 width: 40, 71 width: 40,
@@ -77,10 +78,14 @@ const styles = theme => ({
77 background: color(theme.styleTypes.primary.accent).darken(0.25).hex(), 78 background: color(theme.styleTypes.primary.accent).darken(0.25).hex(),
78 color: theme.styleTypes.primary.contrast, 79 color: theme.styleTypes.primary.contrast,
79 position: 'relative', 80 position: 'relative',
81 height: 'auto',
80 }, 82 },
81 content: { 83 content: {
82 padding: [10, 20, 20], 84 padding: [10, 20, 20],
83 background: '#EFEFEF', 85 background: '#EFEFEF',
86 display: 'flex',
87 flexDirection: 'column',
88 justifyContent: 'space-between',
84 }, 89 },
85 simpleCTA: { 90 simpleCTA: {
86 background: 'none', 91 background: 'none',
@@ -167,10 +172,14 @@ export default @observer @injectSheet(styles) class PlanItem extends Component {
167 </div> 172 </div>
168 )} 173 )}
169 <H2 className={classes.planName}>{name}</H2> 174 <H2 className={classes.planName}>{name}</H2>
170 <p className={classes.text}> 175 {text && (
171 {text} 176 <>
172 </p> 177 <p className={classes.text}>
173 <hr className={classes.divider} /> 178 {text}
179 </p>
180 <hr className={classes.divider} />
181 </>
182 )}
174 <p className={classes.priceWrapper}> 183 <p className={classes.priceWrapper}>
175 <span className={classes.currency}>{currency}</span> 184 <span className={classes.currency}>{currency}</span>
176 <span className={classes.price}> 185 <span className={classes.price}>
diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js
index 4bf5238dd..6f0dd30ad 100644
--- a/src/features/planSelection/components/PlanSelection.js
+++ b/src/features/planSelection/components/PlanSelection.js
@@ -6,7 +6,7 @@ import { defineMessages, intlShape } from 'react-intl';
6import { H1, H2, Icon } from '@meetfranz/ui'; 6import { H1, H2, Icon } from '@meetfranz/ui';
7import color from 'color'; 7import color from 'color';
8 8
9import { mdiRocket, mdiArrowRight } from '@mdi/js'; 9import { mdiArrowRight } from '@mdi/js';
10import PlanItem from './PlanItem'; 10import PlanItem from './PlanItem';
11import { i18nPlanName } from '../../../helpers/plan-helpers'; 11import { i18nPlanName } from '../../../helpers/plan-helpers';
12import { PLANS } from '../../../config'; 12import { PLANS } from '../../../config';
@@ -79,10 +79,10 @@ const styles = theme => ({
79 overflowY: 'scroll', 79 overflowY: 'scroll',
80 }, 80 },
81 container: { 81 container: {
82 width: '80%', 82 // width: '80%',
83 height: 'auto', 83 height: 'auto',
84 background: theme.styleTypes.primary.accent, 84 // background: theme.styleTypes.primary.accent,
85 padding: 40, 85 // padding: 40,
86 borderRadius: theme.borderRadius, 86 borderRadius: theme.borderRadius,
87 maxWidth: 1000, 87 maxWidth: 1000,
88 88
@@ -104,23 +104,6 @@ const styles = theme => ({
104 boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()], 104 boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()],
105 }, 105 },
106 }, 106 },
107 bigIcon: {
108 background: theme.styleTypes.danger.accent,
109 width: 120,
110 height: 120,
111 display: 'flex',
112 alignItems: 'center',
113 borderRadius: '100%',
114 justifyContent: 'center',
115 margin: [-100, 'auto', 20],
116
117 '& svg': {
118 width: '80px !important',
119 height: '80px !important',
120 filter: 'drop-shadow( 0px 2px 3px rgba(0, 0, 0, 0.3))',
121 fill: theme.styleTypes.danger.contrast,
122 },
123 },
124 headline: { 107 headline: {
125 fontSize: 40, 108 fontSize: 40,
126 }, 109 },
@@ -158,7 +141,7 @@ const styles = theme => ({
158 overflow: 'scroll-x', 141 overflow: 'scroll-x',
159 }, 142 },
160 featuredPlan: { 143 featuredPlan: {
161 transform: 'scale(1.05)', 144 transform: ({ isPersonalPlanAvailable }) => (isPersonalPlanAvailable ? 'scale(1.05)' : null),
162 }, 145 },
163 disclaimer: { 146 disclaimer: {
164 textAlign: 'right', 147 textAlign: 'right',
@@ -177,8 +160,13 @@ class PlanSelection extends Component {
177 upgradeAccount: PropTypes.func.isRequired, 160 upgradeAccount: PropTypes.func.isRequired,
178 stayOnFree: PropTypes.func.isRequired, 161 stayOnFree: PropTypes.func.isRequired,
179 hadSubscription: PropTypes.bool.isRequired, 162 hadSubscription: PropTypes.bool.isRequired,
163 isPersonalPlanAvailable: PropTypes.bool,
180 }; 164 };
181 165
166 static defaultProps = {
167 isPersonalPlanAvailable: true,
168 }
169
182 static contextTypes = { 170 static contextTypes = {
183 intl: intlShape, 171 intl: intlShape,
184 }; 172 };
@@ -196,6 +184,7 @@ class PlanSelection extends Component {
196 upgradeAccount, 184 upgradeAccount,
197 stayOnFree, 185 stayOnFree,
198 hadSubscription, 186 hadSubscription,
187 isPersonalPlanAvailable,
199 } = this.props; 188 } = this.props;
200 189
201 const { intl } = this.context; 190 const { intl } = this.context;
@@ -206,15 +195,14 @@ class PlanSelection extends Component {
206 className={classes.root} 195 className={classes.root}
207 > 196 >
208 <div className={classes.container}> 197 <div className={classes.container}>
209 <div className={classes.bigIcon}>
210 <Icon icon={mdiRocket} />
211 </div>
212 <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1> 198 <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1>
213 <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2> 199 {isPersonalPlanAvailable && (
200 <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2>
201 )}
214 <div className={classes.plans}> 202 <div className={classes.plans}>
215 <PlanItem 203 <PlanItem
216 name={i18nPlanName(PLANS.FREE, intl)} 204 name={i18nPlanName(PLANS.FREE, intl)}
217 text={intl.formatMessage(messages.textFree)} 205 text={isPersonalPlanAvailable ? intl.formatMessage(messages.textFree) : null}
218 price={0} 206 price={0}
219 currency={currency} 207 currency={currency}
220 ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)} 208 ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)}
@@ -228,33 +216,35 @@ class PlanSelection extends Component {
228 </PlanItem> 216 </PlanItem>
229 <PlanItem 217 <PlanItem
230 name={i18nPlanName(plans.pro.yearly.id, intl)} 218 name={i18nPlanName(plans.pro.yearly.id, intl)}
231 text={intl.formatMessage(messages.textProfessional)} 219 text={isPersonalPlanAvailable ? intl.formatMessage(messages.textProfessional) : null}
232 price={plans.pro.yearly.price} 220 price={plans.pro.yearly.price}
233 currency={currency} 221 currency={currency}
234 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)} 222 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)}
235 upgrade={() => upgradeAccount(plans.pro.yearly.id)} 223 upgrade={() => upgradeAccount(plans.pro.yearly.id)}
236 className={classes.featuredPlan} 224 className={classes.featuredPlan}
237 perUser 225 perUser
238 bestValue 226 bestValue={isPersonalPlanAvailable}
239 >
240 <FeatureList
241 plan={PLANS.PRO}
242 className={classes.featureList}
243 />
244 </PlanItem>
245 <PlanItem
246 name={i18nPlanName(plans.personal.yearly.id, intl)}
247 text={intl.formatMessage(messages.textPersonal)}
248 price={plans.personal.yearly.price}
249 currency={currency}
250 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)}
251 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
252 > 227 >
253 <FeatureList 228 <FeatureList
254 plan={PLANS.PERSONAL} 229 plan={isPersonalPlanAvailable ? PLANS.PRO : null}
255 className={classes.featureList} 230 className={classes.featureList}
256 /> 231 />
257 </PlanItem> 232 </PlanItem>
233 {isPersonalPlanAvailable && (
234 <PlanItem
235 name={i18nPlanName(plans.personal.yearly.id, intl)}
236 text={intl.formatMessage(messages.textPersonal)}
237 price={plans.personal.yearly.price}
238 currency={currency}
239 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)}
240 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
241 >
242 <FeatureList
243 plan={PLANS.PERSONAL}
244 className={classes.featureList}
245 />
246 </PlanItem>
247 )}
258 </div> 248 </div>
259 <div className={classes.footer}> 249 <div className={classes.footer}>
260 <a 250 <a
diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js
index d202c924e..e4d85cda5 100644
--- a/src/features/planSelection/containers/PlanSelectionScreen.js
+++ b/src/features/planSelection/containers/PlanSelectionScreen.js
@@ -53,7 +53,8 @@ class PlanSelectionScreen extends Component {
53 const { intl } = this.context; 53 const { intl } = this.context;
54 54
55 const { user, features } = this.props.stores; 55 const { user, features } = this.props.stores;
56 const { plans, currency } = features.features.pricingConfig; 56 const { isPersonalPlanAvailable, pricingConfig } = features.features;
57 const { plans, currency } = pricingConfig;
57 const { activateTrial } = this.props.actions.user; 58 const { activateTrial } = this.props.actions.user;
58 const { downgradeAccount, hideOverlay } = this.props.actions.planSelection; 59 const { downgradeAccount, hideOverlay } = this.props.actions.planSelection;
59 60
@@ -95,6 +96,7 @@ class PlanSelectionScreen extends Component {
95 }} 96 }}
96 subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded} 97 subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded}
97 hadSubscription={user.data.hadSubscription} 98 hadSubscription={user.data.hadSubscription}
99 isPersonalPlanAvailable={isPersonalPlanAvailable}
98 /> 100 />
99 </ErrorBoundary> 101 </ErrorBoundary>
100 ); 102 );
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js
index e9b1963f7..2626186e9 100644
--- a/src/features/todos/components/TodosWebview.js
+++ b/src/features/todos/components/TodosWebview.js
@@ -9,10 +9,20 @@ import { defineMessages, intlShape } from 'react-intl';
9import { mdiCheckAll } from '@mdi/js'; 9import { mdiCheckAll } from '@mdi/js';
10import SettingsStore from '../../../stores/SettingsStore'; 10import SettingsStore from '../../../stores/SettingsStore';
11 11
12import * as environment from '../../../environment';
13import Appear from '../../../components/ui/effects/Appear'; 12import Appear from '../../../components/ui/effects/Appear';
14import UpgradeButton from '../../../components/ui/UpgradeButton'; 13import UpgradeButton from '../../../components/ui/UpgradeButton';
15 14
15// NOTE: https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
16function validURL(str) {
17 const pattern = new RegExp('^(https?:\\/\\/)?' // protocol
18 + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' // domain name
19 + '((\\d{1,3}\\.){3}\\d{1,3}))' // OR ip (v4) address
20 + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' // port and path
21 + '(\\?[;&a-z\\d%_.~+=-]*)?' // query string
22 + '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
23 return !!pattern.test(str);
24}
25
16const messages = defineMessages({ 26const messages = defineMessages({
17 premiumInfo: { 27 premiumInfo: {
18 id: 'feature.todos.premium.info', 28 id: 'feature.todos.premium.info',
@@ -194,6 +204,16 @@ class TodosWebview extends Component {
194 204
195 const { intl } = this.context; 205 const { intl } = this.context;
196 206
207 const isUsingPredefinedTodoServer = stores.settings.all.app.predefinedTodoServer !== 'isUsingCustomTodoService';
208 const todoUrl = isUsingPredefinedTodoServer
209 ? stores.settings.all.app.predefinedTodoServer
210 : stores.settings.all.app.customTodoServer;
211 let isTodoUrlValid = true;
212 if (isUsingPredefinedTodoServer === false) {
213 isTodoUrlValid = validURL(todoUrl);
214 }
215
216
197 return ( 217 return (
198 <div 218 <div
199 className={classes.root} 219 className={classes.root}
@@ -213,6 +233,8 @@ class TodosWebview extends Component {
213 /> 233 />
214 )} 234 )}
215 {isTodosIncludedInCurrentPlan ? ( 235 {isTodosIncludedInCurrentPlan ? (
236 isTodoUrlValid
237 && (
216 <Webview 238 <Webview
217 className={classes.webview} 239 className={classes.webview}
218 onDidAttach={() => { 240 onDidAttach={() => {
@@ -223,8 +245,9 @@ class TodosWebview extends Component {
223 partition="persist:todos" 245 partition="persist:todos"
224 preload="./features/todos/preload.js" 246 preload="./features/todos/preload.js"
225 ref={(webview) => { this.webview = webview ? webview.view : null; }} 247 ref={(webview) => { this.webview = webview ? webview.view : null; }}
226 src={stores.settings.all.app.todoServer || environment.TODOS_FRONTEND} 248 src={todoUrl}
227 /> 249 />
250 )
228 ) : ( 251 ) : (
229 <Appear> 252 <Appear>
230 <div className={classes.premiumContainer}> 253 <div className={classes.premiumContainer}>
diff --git a/src/features/webControls/containers/WebControlsScreen.js b/src/features/webControls/containers/WebControlsScreen.js
index 31168a397..258b15b14 100644
--- a/src/features/webControls/containers/WebControlsScreen.js
+++ b/src/features/webControls/containers/WebControlsScreen.js
@@ -52,11 +52,8 @@ class WebControlsScreen extends Component {
52 } 52 }
53 53
54 goHome() { 54 goHome() {
55 const { reloadActive } = this.props.actions.service;
56
57 if (!this.webview) return; 55 if (!this.webview) return;
58 56 this.webview.goToIndex(0);
59 reloadActive();
60 } 57 }
61 58
62 reload() { 59 reload() {