TryGhost/Ghost

View on GitHub
ghost/admin/app/components/modal-member-tier.js

Summary

Maintainability
A
0 mins
Test Coverage
import ModalComponent from 'ghost-admin/components/modal-base';
import moment from 'moment-timezone';
import {action} from '@ember/object';
import {didCancel, task} from 'ember-concurrency';
import {inject as service} from '@ember/service';
import {tracked} from '@glimmer/tracking';

export default class ModalMemberTier extends ModalComponent {
    @service store;
    @service ghostPaths;
    @service ajax;

    @tracked price;
    @tracked tier;
    @tracked tiers = [];
    @tracked selectedTier = null;
    @tracked loadingTiers = false;
    @tracked expiryAt = 'forever';
    @tracked customExpiryDate = moment().startOf('day');

    @tracked expiryOptions = [
        {
            label: 'Forever',
            duration: 'forever'
        },
        {
            label: '1 Week',
            duration: 'week'
        },
        {
            label: '1 Month',
            duration: 'month'
        },
        {
            label: '6 Months',
            duration: 'half-year'
        },
        {
            label: '1 Year',
            duration: 'year'
        },
        {
            label: 'Custom',
            duration: 'custom'
        }
    ];

    @task({drop: true})
    *fetchTiers() {
        this.tiers = yield this.store.query('tier', {filter: 'type:paid+active:true', include: 'monthly_price,yearly_price,benefits'});

        this.loadingTiers = false;
        if (this.tiers.length > 0) {
            this.selectedTier = this.tiers.firstObject.id;
        }
    }

    get activeSubscriptions() {
        const subscriptions = this.member.get('subscriptions') || [];
        return subscriptions.filter((sub) => {
            return ['active', 'trialing', 'unpaid', 'past_due'].includes(sub.status);
        });
    }

    get member() {
        return this.model;
    }

    get cannotAddPrice() {
        return !this.price || this.price.amount !== 0;
    }

    get minCustomDate() {
        return moment().startOf('day');
    }

    @action
    setup() {
        this.loadingTiers = true;
        try {
            this.fetchTiers.perform();
        } catch (e) {
            // Do not throw cancellation errors
            if (didCancel(e)) {
                return;
            }

            throw e;
        }
    }

    @action
    setTier(tierId) {
        this.selectedTier = tierId;
    }

    @action
    setPrice(price) {
        this.price = price;
    }

    @action
    confirmAction() {
        return this.addTier.perform();
    }

    @action
    close(event) {
        event?.preventDefault?.();
        this.closeModal();
    }

    @action
    updateExpiry(expiryDuration) {
        this.expiryAt = expiryDuration;
    }

    @action
    updateCustomExpiryDate(date) {
        this.customExpiryDate = moment(date).startOf('day');
    }

    @task({drop: true})
    *addTier() {
        const url = `${this.ghostPaths.url.api(`members/${this.member.get('id')}`)}?include=tiers`;

        // Cancel existing active subscriptions for member
        for (let i = 0; i < this.activeSubscriptions.length; i++) {
            const subscription = this.activeSubscriptions[i];
            const cancelUrl = this.ghostPaths.url.api(`members/${this.member.get('id')}/subscriptions/${subscription.id}`);
            yield this.ajax.put(cancelUrl, {
                data: {
                    status: 'canceled'
                }
            });
        }

        let expiryAt = null;

        if (this.expiryAt === 'week') {
            expiryAt = moment.utc().add(7, 'days').startOf('day').toISOString();
        } else if (this.expiryAt === 'month') {
            expiryAt = moment.utc().add(1, 'month').startOf('day').toISOString();
        } else if (this.expiryAt === 'half-year') {
            expiryAt = moment.utc().add(6, 'months').startOf('day').toISOString();
        } else if (this.expiryAt === 'year') {
            expiryAt = moment.utc().add(1, 'year').startOf('day').toISOString();
        } else if (this.expiryAt === 'custom') {
            expiryAt = this.customExpiryDate
                .endOf('day')
                .set('milliseconds', 0) // Prevent db rounding up to the next day
                .add(moment().utcOffset(), 'minutes') // Adjust for timezone offset
                .toISOString();
        }
        const tiersData = {
            id: this.selectedTier
        };
        if (expiryAt) {
            tiersData.expiry_at = expiryAt;
        }
        const response = yield this.ajax.put(url, {
            data: {
                members: [{
                    id: this.member.get('id'),
                    email: this.member.get('email'),
                    tiers: [tiersData]
                }]
            }
        });

        this.store.pushPayload('member', response);
        this.closeModal();
        return response;
    }

    actions = {
        confirm() {
            this.confirmAction(...arguments);
        },
        // needed because ModalBase uses .send() for keyboard events
        closeModal() {
            this.close();
        }
    };
}