diff --git a/demo/data-recent-donations.json b/demo/data-recent-donations.json deleted file mode 100644 index fe83524..0000000 --- a/demo/data-recent-donations.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "success": true, - "value": { - 'donation1': { - id: 1, - amount: 100 - - } - - } -} diff --git a/demo/index.html b/demo/index.html index 5fd6b5b..8dbd069 100644 --- a/demo/index.html +++ b/demo/index.html @@ -37,14 +37,169 @@
+ + +
+
+ diff --git a/package-lock.json b/package-lock.json index e81bb58..66045b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "@internetarchive/donation-monthly-portal", - "version": "1.0.0", + "version": "0.0.0-receipts5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@internetarchive/donation-monthly-portal", - "version": "1.0.0", + "version": "0.0.0-receipts5", "license": "AGPL-3.0-only", "dependencies": { + "@internetarchive/iaux-notification-toast": "^0.0.0-alpha2", "@internetarchive/icon-donate": "^1.3.4", "lit": "^2.8.0" }, @@ -913,6 +914,15 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@internetarchive/iaux-notification-toast": { + "version": "0.0.0-alpha2", + "resolved": "https://registry.npmjs.org/@internetarchive/iaux-notification-toast/-/iaux-notification-toast-0.0.0-alpha2.tgz", + "integrity": "sha512-0wbHkP6xJmYE6uIreA0I2hMxC0aHOJzUjW+pFToXbBi8KzhNYvMucBL1jtMpV9HcXq7UsoS2wc0MHFc6lzcCJw==", + "license": "AGPL-3.0-only", + "dependencies": { + "lit": "^2.6.0" + } + }, "node_modules/@internetarchive/icon-donate": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@internetarchive/icon-donate/-/icon-donate-1.3.4.tgz", diff --git a/package.json b/package.json index e4e3a80..e41ae4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@internetarchive/donation-monthly-portal", - "version": "1.0.0", + "version": "0.0.0-receipts5", "description": "The Internet Archive Monthly Portal", "license": "AGPL-3.0-only", "main": "dist/index.js", @@ -27,6 +27,7 @@ "ghpages:generate": "gh-pages -t -d ghpages -m \"Build for $(git log --pretty=format:\"%h %an %ai %s\" -n1) [skip ci]\"" }, "dependencies": { + "@internetarchive/iaux-notification-toast": "^0.0.0-alpha2", "@internetarchive/icon-donate": "^1.3.4", "lit": "^2.8.0" }, diff --git a/src/models/subscription-summary.ts b/src/models/subscription-summary.ts new file mode 100644 index 0000000..ab6c442 --- /dev/null +++ b/src/models/subscription-summary.ts @@ -0,0 +1,62 @@ +type BtNextBillingDate = { + date: string; + timezone_type: number; + timezone: string; +}; + +type BtData = { + billingDayOfMonth: number; + nextBillingDate: BtNextBillingDate; + status: string; // active, inactive + paymentMethodType: string; // cc, paypal, venmo, etc + last4: string | null; + cardType: string | null; + expirationMonth: string | null; + expirationYear: string | null; + paypalEmail?: string; + venmoUsername?: string; +}; + +type aSummary = { + id: string; + token: string; + amount: number; + start_date: string; + contribution_status_id: number; + is_test: boolean; + btdata: BtData; + oldAmount?: number; + oldDate?: string; + oldPaymentMethod?: string; + isCancelled?: boolean; +}; +export default class SubscriptionSummary { + summary: aSummary; + + currencyType: string; + + payment: BtData; + + constructor(summary: aSummary) { + this.summary = summary; + this.payment = summary.btdata; + this.currencyType = 'USD'; // not in data + } + + get id(): string { + // use token as unique id + return this.summary.token; + } + + get amount(): number { + return this.summary.amount; + } + + get startDate(): string { + return this.summary.start_date; + } + + get isTest(): boolean { + return this.summary.is_test; + } +} diff --git a/src/monthly-giving-circle.ts b/src/monthly-giving-circle.ts index 21ad3ee..ae4f20a 100644 --- a/src/monthly-giving-circle.ts +++ b/src/monthly-giving-circle.ts @@ -1,22 +1,100 @@ /* eslint-disable no-debugger */ -import { LitElement, html } from 'lit'; +import { LitElement, html, TemplateResult, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import './welcome-message'; +import './receipts'; +import './plans'; import './presentational/mgc-title'; +import './presentational/button-style'; +import './presentational/update-queue'; @customElement('iaux-monthly-giving-circle') export class MonthlyGivingCircle extends LitElement { @property({ type: String }) patronName: string = ''; + @property({ type: Array }) receipts = []; + + @property({ type: Array }) updates = []; + + @property({ type: Array }) plans = []; + + @property({ type: String }) viewToDisplay: 'welcome' | 'receipts' = 'welcome'; + protected createRenderRoot() { return this; } + get showReceiptsCTA(): TemplateResult { + return html` + + + + `; + } + protected render() { + console.log('***', this.viewToDisplay, this.receipts); + if (this.viewToDisplay === 'receipts') { + return html` + + Recent donations + + + + + + + + { + this.dispatchEvent( + new CustomEvent('EmailReceiptRequest', { + detail: { ...event.detail }, + }) + ); + }} + > + `; + } + + if (this.plans.length) { + return html` + + Monthly Giving Circle + ${this.receipts.length ? this.showReceiptsCTA : nothing} + + + `; + } + return html` - + + Monthly Giving Circle + ${this.receipts.length ? this.showReceiptsCTA : nothing} + `; } diff --git a/src/plans.ts b/src/plans.ts new file mode 100644 index 0000000..9a899aa --- /dev/null +++ b/src/plans.ts @@ -0,0 +1,129 @@ +import { LitElement, html, css, nothing } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +import './presentational/button-style'; +import type SubscriptionSummary from './models/subscription-summary'; + +// type donation = { +// status: string; +// amount: number; +// date: string; +// id: string; +// }; + +@customElement('iaux-mgc-plans') +export class MGCPlans extends LitElement { + @property({ type: Array }) plans = []; + + donationAmountFormatted(amount: number) { + return `USD ${amount}`; + } + + dateFormatted(date: string) { + const splitDate = date.split('-'); + const year = splitDate[0]; + const month = parseInt(splitDate[1], 10); + const day = splitDate[2]; + + const monthMap: { [key: number]: string } = { + 1: 'January', + 2: 'February', + 3: 'March', + 4: 'April', + 5: 'May', + 6: 'June', + 7: 'July', + 8: 'August', + 9: 'September', + 10: 'October', + 11: 'November', + 12: 'December', + }; + + const displayMonth = monthMap[month]; + return `${displayMonth} ${day}, ${year}`; + } + + protected render() { + return html` +
+ +
+ `; + } + + static styles = css` + table { + width: 100%; + text-align: left; + max-width: 600px; + } + `; +} diff --git a/src/presentational/button-style.ts b/src/presentational/button-style.ts new file mode 100644 index 0000000..a4456bd --- /dev/null +++ b/src/presentational/button-style.ts @@ -0,0 +1,71 @@ +import { LitElement, html, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +import '@internetarchive/icon-donate/icon-donate.js'; + +@customElement('iaux-button-style') +export class MonthlyGivingCircle extends LitElement { + render() { + return html``; + } + + static styles = css` + ::slotted(*) { + height: 30px; + border: none; + cursor: pointer; + color: #fff; + line-height: normal; + border-radius: 0.4rem; + text-align: center; + vertical-align: middle; + display: inline-block; + padding: 0.6rem 1.2rem; + border: 1px solid transparent; + + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + } + + :host(.disabled) ::slotted(*:disabled) { + cursor: not-allowed; + opacity: 0.5; + } + + :host(.transparent) ::slotted(*) { + background-color: transparent; + } + + :host(.slim) ::slotted(*) { + padding: 0; + } + + :host(.primary) ::slotted(*) { + background-color: #194880; + border-color: #c5d1df; + } + + :host(.secondary) ::slotted(*) { + background: #333; + } + + :host(.cancel) ::slotted(*) { + background-color: #e51c26; + border-color: #f8c6c8; + } + + :host(.link) ::slotted(*) { + color: #4b64ff; + border: none; + background: transparent; + display: flex; + align-items: flex-end; + padding: 0; + height: inherit; + } + `; +} diff --git a/src/presentational/mgc-title.ts b/src/presentational/mgc-title.ts index 8ad170e..3da5a9e 100644 --- a/src/presentational/mgc-title.ts +++ b/src/presentational/mgc-title.ts @@ -9,10 +9,7 @@ export class MonthlyGivingCircle extends LitElement { get heart(): TemplateResult | typeof nothing { return this.titleStyle === 'heart' - ? html` -
- Monthly Giving Circle - ` + ? html`
` : nothing; } @@ -31,6 +28,11 @@ export class MonthlyGivingCircle extends LitElement { } static styles = css` + :host { + padding-bottom: 5px; + display: block; + } + h2 { font-size: 1.5em; display: flex; diff --git a/src/presentational/update-queue.ts b/src/presentational/update-queue.ts new file mode 100644 index 0000000..5bc369b --- /dev/null +++ b/src/presentational/update-queue.ts @@ -0,0 +1,54 @@ +import { LitElement, html, css, nothing } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +import '@internetarchive/icon-donate/icon-donate.js'; + +type anUpdate = { + message: string; + status: 'success' | 'fail'; +}; + +@customElement('iaux-update-queue') +export class MonthlyGivingCircle extends LitElement { + @property({ type: Array }) updates: anUpdate[] = []; + + render() { + if (!this.updates.length) { + return nothing; + } + + return html` + + `; + } + + updateBlock(update: anUpdate) { + const icon = update.status === 'success' ? '✓' : '✖'; + return html`
  • ${icon} ${update.message}
  • `; + } + + static styles = css` + ul { + display: grid; + background: rgb(238, 253, 238); + margin: 1.5rem 0px; + width: fit-content; + list-style-type: none; + padding: 0; + } + li { + padding: 10px; + } + li.success { + color: rgb(33, 149, 24); + cursor: pointer; + border-left: 5px solid rgb(33, 149, 24); + } + li.fail { + color: #bb0505; + border-left: 5px solid #bb0505; + } + `; +} diff --git a/src/receipts.ts b/src/receipts.ts new file mode 100644 index 0000000..4927c89 --- /dev/null +++ b/src/receipts.ts @@ -0,0 +1,107 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +import './presentational/button-style'; + +type donation = { + status: string; + amount: number; + date: string; + id: string; +}; + +@customElement('iaux-mgc-receipts') +export class MGCWelcome extends LitElement { + @property({ type: Array }) donations = []; + + donationAmountFormatted(amount: number) { + return `USD ${amount}`; + } + + dateFormatted(date: string) { + const splitDate = date.split('-'); + const year = splitDate[0]; + const month = parseInt(splitDate[1], 10); + const day = splitDate[2]; + + const monthMap: { [key: number]: string } = { + 1: 'January', + 2: 'February', + 3: 'March', + 4: 'April', + 5: 'May', + 6: 'June', + 7: 'July', + 8: 'August', + 9: 'September', + 10: 'October', + 11: 'November', + 12: 'December', + }; + + const displayMonth = monthMap[month]; + return `${displayMonth} ${day}, ${year}`; + } + + emailReceipt(donation: donation) { + this.dispatchEvent( + new CustomEvent('EmailReceiptRequest', { + detail: { + donation, + }, + }) + ); + } + + protected render() { + return html` +
    + + + + + + + + ${this.donations.length + ? this.donations.map((donation: donation) => { + const emailUnavailable = donation.status === 'pending'; + return html` + + + + + + + `; + }) + : html`

    No recent donations found

    `} +
    DonorAmountStatusAction
    ${this.dateFormatted(donation.date)}${this.donationAmountFormatted(donation.amount)}${donation.status} + + + +
    +
    + `; + } + + static styles = css` + table { + width: 100%; + text-align: left; + max-width: 600px; + } + + button { + padding: 1rem 0; + } + `; +} diff --git a/test/monthly-giving-circle.test.ts b/test/monthly-giving-circle.test.ts index cc605f0..19d43df 100644 --- a/test/monthly-giving-circle.test.ts +++ b/test/monthly-giving-circle.test.ts @@ -14,4 +14,14 @@ describe('IauxMonthlyGivingCircle', () => { // eslint-disable-next-line no-unused-expressions expect(el.querySelector('iaux-mgc-welcome')).to.not.be.null; }); + + it('displays receipt button when receipts are available', async () => { + const el = await fixture( + html`` + ); + + expect(el.querySelector('iaux-mgc-welcome')).to.not.be.null; + }); });