+
+
+ {t('subscription.current_plan.help_text')}
+
+
+
+
+ {alertText}
+
+ >
+ );
+}
+
+export default JKSubscriptionPlan;
diff --git a/jam-ui/src/components/profile/JKSubscriptionPlaytime.js b/jam-ui/src/components/profile/JKSubscriptionPlaytime.js
new file mode 100644
index 000000000..f391b67d8
--- /dev/null
+++ b/jam-ui/src/components/profile/JKSubscriptionPlaytime.js
@@ -0,0 +1,204 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Card, CardHeader, CardBody, Label } from 'reactstrap';
+import { useTranslation } from 'react-i18next';
+import { formatDateShort } from '../../helpers/utils';
+import { get } from 'react-hook-form';
+
+function JKSubscriptionPlaytime({ userPlan, getDisplayName }) {
+ const { t } = useTranslation('account');
+
+ const [explanation, setExplanation] = useState('');
+ const [warning, setWarning] = useState('');
+ const [billingAddendum, setBillingAddendum] = useState(''); // [TODO: addendum to the warning message about billing, if needed]
+ const showPaymentInfoRef = useRef(false);
+
+ const displayTime = until_time => {
+ if (until_time < 0) return 'no time';
+
+ const untilTime = getTimeRemaining(until_time * 1000);
+
+ let timeString = '';
+ if (untilTime.days !== 0) timeString += `${untilTime.days} days, `;
+ if (untilTime.hours !== 0 || timeString.length > 0) timeString += `${untilTime.hours} hours, `;
+ if (untilTime.minutes !== 0 || timeString.length > 0) timeString += `${untilTime.minutes} minutes `;
+ if (timeString === '') timeString = 'now!';
+
+ return timeString;
+ };
+
+ const getTimeRemaining = t => {
+ if (t < 0) t = -t;
+
+ const seconds = Math.floor((t / 1000) % 60);
+ const minutes = Math.floor((t / 1000 / 60) % 60);
+ const hours = Math.floor((t / (1000 * 60 * 60)) % 24);
+ const days = Math.floor(t / (1000 * 60 * 60 * 24));
+
+ return {
+ total: t,
+ days: days,
+ hours: hours,
+ minutes: minutes,
+ seconds: seconds
+ };
+ };
+
+ const getDisplayNamePrice = planCode => {
+ if (planCode == '') {
+ planCode = null;
+ }
+ const plan = window.gon.global.subscription_codes.find(plan => plan.id === planCode);
+ if (plan) {
+ return plan.price;
+ }
+ return `Unknown plan code=${planCode}`;
+ };
+
+ const getDisplayCycle = planCode => {
+ if (planCode == '') {
+ planCode = null;
+ }
+ for (const subscriptionCode of window.gon.global.subscription_codes) {
+ if (planCode === subscriptionCode.id) {
+ if (subscriptionCode.cycle === 'year') {
+ return 'annual';
+ } else {
+ return subscriptionCode.cycle + 'ly';
+ }
+ }
+ }
+ return `Unknown plan code=${planCode}`;
+ };
+
+ const planNameWithCycle = planCode => {
+ return getDisplayName(planCode) + ' (' + getDisplayCycle(planCode) + ')';
+ };
+
+ useEffect(() => {
+ if (userPlan) {
+ let expl,
+ note,
+ warning,
+ billingAddendumTxt,
+ playtimeTxt = '';
+ const adminOverride = userPlan.admin_override_plan_code;
+ const inTrail = userPlan.in_trail;
+ const hasBillingInfo = userPlan.has_billing_info;
+ const effectivePlanName = planNameWithCycle(userPlan.plan_code);
+ const desiredPlanCode = userPlan.desired_plan_code;
+ const desiredPlanName = planNameWithCycle(desiredPlanCode);
+ const hasPendingSubscription = userPlan.subscription.pending_subscription;
+ const canceledSubscription = userPlan.subscription.remaining_billing_cycles === 0;
+
+ if (adminOverride) {
+ expl = `You have a ${effectivePlanName} account until your gifted plan ends ${formatDateShort(
+ userPlan.admin_override_ends_at
+ )}.`;
+ } else if (inTrail) {
+ if (desiredPlanCode) {
+ if (hasBillingInfo) {
+ note = `Billing starts for the ${desiredPlanName} plan after the trial ends.`;
+ } else {
+ warning = `You will drop to the free plan after the trial ends because you have not yet entered payment info.`;
+ showPaymentInfoRef.current = true;
+ }
+ } else {
+ if (hasBillingInfo) {
+ warning = `You will drop to the free plan after the trial ends because you have not selected a plan.`;
+ } else {
+ warning = `You will drop to the free plan after the trial ends because you have not yet entered payment info or selected a plan.`;
+ showPaymentInfoRef.current = true;
+ }
+ }
+ expl = `You have a ${effectivePlanName} account until your trial ends ${formatDateShort(
+ userPlan.trial_ends_at
+ )}. ${note}`;
+ } else {
+ // NOT admin override and NOT in trial
+ if (desiredPlanCode && !userPlan.plan_code && !hasBillingInfo) {
+ expl = `You have successfully upgraded your plan to the ${desiredPlanName} level, thank you!`;
+ warning = `For this plan to take effect, you must provide a payment method (e.g. a credit card), for the monthly subscription charge. Please click the Update Payment Method button to do this now.`;
+ //show_payment_info = true
+ showPaymentInfoRef.current = true;
+ } else {
+ if (desiredPlanCode) {
+ if (!hasBillingInfo) {
+ //show_payment_info = true
+ showPaymentInfoRef.current = true;
+ expl = `You have successfully upgraded your plan to the ${desiredPlanName} level, thank you`;
+ warning = `However, you must provide a payment method (e.g. a credit card), for the monthly subscription charge. Please click the Update Payment Method button to do this now.`;
+ } else {
+ expl = `You are currently on the ${effectivePlanName} level, thank you!`;
+ }
+ } else {
+ //free plan situation - not much to go on about
+ expl = `You are currently on the ${effectivePlanName} plan.`;
+ }
+ }
+ }
+
+ setExplanation(expl);
+ setWarning(warning);
+
+ //billingAddendum
+ if (hasPendingSubscription) {
+ if (userPlan.subscription.plan.plan_code !== userPlan.plan_code) {
+ billingAddendumTxt = ` You have paid only for the ${planNameWithCycle(
+ userPlan.subscription.plan.plan_code
+ )} level for the current billing cycle, so there will be a change to the ${this.planNameWithCycle(
+ this.props.subscription.subscription.pending_subscription.plan.plan_code
+ )} level on the next billing cycle.`;
+ } else {
+ billingAddendumTxt = ` And your plan and billing will switch to the ${planNameWithCycle(
+ userPlan.subscription.pending_subscription.plan.plan_code
+ )} level on the next billing cycle.`;
+ }
+ } else if (canceledSubscription && userPlan.desired_plan_code === null && userPlan.plan_code !== null) {
+ billingAddendumTxt = ` However, your cancelled ${effectivePlanName} plan is still active until the end of the billing cycle. You will be billed a final time at the ${planNameWithCycle(
+ userPlan.subscription.plan.plan_code
+ )} at end of this billing cycle.`;
+ } else {
+ billingAddendumTxt = '';
+ }
+ setBillingAddendum(billingAddendumTxt);
+ }
+ }, [userPlan]);
+
+ return (
+
+
+
+ You have {planNameWithCycle(userPlan.subscription_rules.remaining_month_play_time)} remaining
+ this month. Only the time you spend in a session with 2 or more people uses your session play time.
+
+
+ ) : (
+
+
You have unlimited play time.
+
+ )}
+
+
+
+
+ {explanation && (
+
+
+
+
+ )}
+ {warning && }
+
+
+
+
+ );
+}
+
+export default JKSubscriptionPlaytime;
diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js
index 7e3536bc6..761582bb1 100644
--- a/jam-ui/src/helpers/rest.js
+++ b/jam-ui/src/helpers/rest.js
@@ -1,3 +1,4 @@
+import { error } from 'is_js';
import apiFetch from './apiFetch';
export const getMusicians = page => {
@@ -277,4 +278,26 @@ export const postUserAppInteraction = (userId, options) => {
.then(response => resolve(response))
.catch(error => reject(error));
});
+}
+
+
+export const getSubscription = () => {
+ return new Promise((resolve, reject) => {
+ apiFetch('/recurly/get_subscription')
+ .then(response => resolve(response))
+ .catch(error => reject(error))
+ });
+}
+
+
+export const changeSubscription = (plan_code) => {
+ const options = {plan_code}
+ return new Promise((resolve, reject) => {
+ apiFetch('/recurly/change_subscription', {
+ method: 'POST',
+ body: JSON.stringify(options)
+ })
+ .then(response => resolve(response))
+ .catch(error => reject(error));
+ })
}
\ No newline at end of file
diff --git a/jam-ui/src/helpers/utils.js b/jam-ui/src/helpers/utils.js
index a8a9746b6..d795eb38e 100644
--- a/jam-ui/src/helpers/utils.js
+++ b/jam-ui/src/helpers/utils.js
@@ -234,4 +234,16 @@ export const currencyFormat = (num) => {
return new Intl.NumberFormat('en-US',
{ style: 'currency', currency: 'USD' }
).format(num);
+}
+
+const days = new Array("Sun", "Mon", "Tue",
+ "Wed", "Thu", "Fri", "Sat");
+
+const months = new Array("January", "February", "March",
+ "April", "May", "June", "July", "August", "September",
+ "October", "November", "December");
+
+export const formatDateShort = (dateString) => {
+ const date = dateString instanceof Date ? dateString : new Date(dateString);
+ return months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
}
\ No newline at end of file
diff --git a/jam-ui/src/i18n/locales/en/account.json b/jam-ui/src/i18n/locales/en/account.json
index a3791c522..aabe7cf5e 100644
--- a/jam-ui/src/i18n/locales/en/account.json
+++ b/jam-ui/src/i18n/locales/en/account.json
@@ -54,5 +54,35 @@
}
+ },
+ "subscription": {
+ "page_title": "Subscription",
+ "current_plan": {
+ "title": "Current Plan",
+ "help_text": "Your JamKazam subscription plan is currently set to the plan displayed below. To change your plan, click the subscription plan box below, select a new plan from the list, and then click the Save Plan button.",
+ "subscription_plan": "Subscription Plan",
+ "validations": {
+ "subscription_plan": {
+ "required": "Subscription plan is required"
+ }
+ },
+ "submit": "Save Plan",
+ "notice": {
+ "part1": "To compare the features available for different subscription plans ",
+ "click_here": "click here",
+ "part2": " to view a help article on our available plans."
+
+ }
+ },
+ "play_time": {
+ "title": "Play Time",
+ "description": ""
+ },
+ "alerts": {
+ "title": "Subscription Plan Update",
+ "changed_to_free_plan": "You have chosen to go back down to the FREE PLAN. Your subscription will be canceled, and you will keep your plan until the end of the current billing cycle.",
+ "failed_to_change_plan": "Failed to update subscription plan. Please try again later. Please contact support@jamkazam.com if you continue to have problems.",
+ "changed_plan_successfully": "You have successfully updated your subscription plan."
+ }
}
}
\ No newline at end of file
diff --git a/jam-ui/src/i18n/locales/en/common.json b/jam-ui/src/i18n/locales/en/common.json
index f04c7a44b..a9c5908c0 100644
--- a/jam-ui/src/i18n/locales/en/common.json
+++ b/jam-ui/src/i18n/locales/en/common.json
@@ -6,6 +6,8 @@
"actions": "Actions",
"no_records": "No Records!",
"close": "Close",
+ "cancel": "Cancel",
+ "ok": "OK",
"navigation": {
"home": "Home",
"friends": "Friends",
diff --git a/jam-ui/src/i18n/locales/es/account.json b/jam-ui/src/i18n/locales/es/account.json
index 0320557fa..6c363b933 100644
--- a/jam-ui/src/i18n/locales/es/account.json
+++ b/jam-ui/src/i18n/locales/es/account.json
@@ -54,5 +54,35 @@
}
+ },
+ "subscription": {
+ "page_title": "Suscripción",
+ "current_plan": {
+ "title": "plan actual",
+ "help_text": "Your JamKazam subscription plan is currently set to the plan displayed below. To change your plan, click the subscription plan box below, select a new plan from the list, and then click the Save Plan button.",
+ "subscription_plan": "Subscription Plan",
+ "validations": {
+ "subscription_plan": {
+ "required": "Subscription plan is required"
+ }
+ },
+ "submit": "Save Plan",
+ "notice": {
+ "part1": "To compare the features available for different subscription plans ",
+ "click_here": "click here",
+ "part2": " to view a help article on our available plans."
+
+ }
+ },
+ "play_time": {
+ "title": "Tiempo de juego",
+ "description": ""
+ },
+ "alerts": {
+ "title": "Subscription Plan Update",
+ "changed_to_free_plan": "You have chosen to go back down to the FREE PLAN. Your subscription will be canceled, and you will keep your plan until the end of the current billing cycle.",
+ "failed_to_change_plan": "Failed to update subscription plan. Please try again later. Please contact support@jamkazam.com if you continue to have problems.",
+ "changed_plan_successfully": "You have successfully updated your subscription plan."
+ }
}
}
\ No newline at end of file