From ba2e44c13b72f50a75d466a2caa7361ed73e8f21 Mon Sep 17 00:00:00 2001
From: elehmandevelopment
Date: Thu, 14 Mar 2024 20:29:35 -0600
Subject: [PATCH 01/60] feat: instantiate subscriptions
---
src/applications/application.ts | 2 +
src/components/popup/WalletHeader.tsx | 5 +-
src/popup.tsx | 2 +
src/routes/popup/subscriptions.tsx | 57 +++++++++++++
src/subscriptions/allowance.ts | 0
src/subscriptions/payments.ts | 0
src/subscriptions/subscription.ts | 118 ++++++++++++++++++++++++++
7 files changed, 183 insertions(+), 1 deletion(-)
create mode 100644 src/routes/popup/subscriptions.tsx
create mode 100644 src/subscriptions/allowance.ts
create mode 100644 src/subscriptions/payments.ts
create mode 100644 src/subscriptions/subscription.ts
diff --git a/src/applications/application.ts b/src/applications/application.ts
index 4b3a3dd65..096e8b2d8 100644
--- a/src/applications/application.ts
+++ b/src/applications/application.ts
@@ -4,6 +4,7 @@ import { useStorage } from "@plasmohq/storage/hook";
import { ExtensionStorage } from "~utils/storage";
import type { Storage } from "@plasmohq/storage";
import { defaultGateway, Gateway } from "~gateways/gateway";
+import type { SubscriptionData } from "~subscriptions/subscription";
export const PREFIX = "app_";
export const defaultBundler = "https://turbo.ardrive.io";
@@ -192,4 +193,5 @@ export interface InitAppParams extends AppInfo {
allowance?: Allowance;
blocked?: boolean;
bundler?: string;
+ subscriptionData?: SubscriptionData;
}
diff --git a/src/components/popup/WalletHeader.tsx b/src/components/popup/WalletHeader.tsx
index 99aff3c93..7da4d10c0 100644
--- a/src/components/popup/WalletHeader.tsx
+++ b/src/components/popup/WalletHeader.tsx
@@ -41,7 +41,7 @@ import browser from "webextension-polyfill";
import styled from "styled-components";
import copy from "copy-to-clipboard";
import { type Gateway } from "~gateways/gateway";
-import { Bell03 } from "@untitled-ui/icons-react";
+import { Bell03, CreditCard01 } from "@untitled-ui/icons-react";
import { svgie } from "~utils/svgies";
import { useHistory } from "~utils/hash_router";
@@ -232,6 +232,9 @@ export default function WalletHeader() {
+
+ push("/subscriptions")} />
+
+
{(params: { id: string }) => (
diff --git a/src/routes/popup/subscriptions.tsx b/src/routes/popup/subscriptions.tsx
new file mode 100644
index 000000000..72e084cd1
--- /dev/null
+++ b/src/routes/popup/subscriptions.tsx
@@ -0,0 +1,57 @@
+import Subscription, {
+ type SubscriptionData
+} from "~subscriptions/subscription";
+import HeadV2 from "~components/popup/HeadV2";
+import { useEffect, useState } from "react";
+import { getActiveAddress } from "~wallets";
+import { ExtensionStorage } from "~utils/storage";
+
+export default function Subscriptions() {
+ const [subData, setSubData] = useState(null);
+
+ useEffect(() => {
+ async function getSubData() {
+ const address = await getActiveAddress();
+
+ // await ExtensionStorage.set(`subscriptions_${address}`,
+ // {
+ // "applicationName": "ArDrive Turbo",
+ // "arweaveAccountAddress": "JNC6vBhjHY1EPwV3pEeNmrsgFMxH5d38_LHsZ7jful8",
+ // "nextPaymentDue": "03-08-2025",
+ // "recurringPaymentFrequency": "Annually",
+ // "subscriptionEndDate": "03-08-2025",
+ // "subscriptionFeeAmount": 25,
+ // "subscriptionName": "Turbo Subscription",
+ // "subscriptionStartData": "03-08-2024",
+ // "subscriptionStatus": "Active"
+ // }
+ // );
+
+ try {
+ const sub = new Subscription(address);
+ const data = await sub.getSubscriptionData();
+
+ console.log("data: ", data);
+ setSubData(data);
+ } catch (error) {
+ console.error("Error fetching subscription data:", error);
+ }
+ }
+ getSubData();
+ }, []);
+
+ return (
+
+
+ {subData ? (
+ <>
+
App: {subData.applicationName}
+
Subscription: {subData.subscriptionName}
+
Fee: {subData.subscriptionFeeAmount} AR
+ >
+ ) : (
+
No notifications found
+ )}
+
+ );
+}
diff --git a/src/subscriptions/allowance.ts b/src/subscriptions/allowance.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/subscriptions/payments.ts b/src/subscriptions/payments.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/subscriptions/subscription.ts b/src/subscriptions/subscription.ts
new file mode 100644
index 000000000..10538f889
--- /dev/null
+++ b/src/subscriptions/subscription.ts
@@ -0,0 +1,118 @@
+import Application, { type InitAppParams } from "~applications/application";
+import { ExtensionStorage } from "~utils/storage";
+import type { Storage } from "@plasmohq/storage";
+
+export default class Subscription {
+ activeAddress: string;
+ application: Application;
+ applicationUrl: string;
+ #storage: Storage;
+
+ constructor(activeAddress: string, applicationUrl?: string) {
+ this.activeAddress = activeAddress;
+ this.application = new Application(applicationUrl);
+ this.applicationUrl = applicationUrl;
+ this.#storage = ExtensionStorage;
+ }
+
+ // get subscription data from storage
+ async getSubscriptionData(): Promise {
+ const subscriptionData = await this.#storage.get(
+ `subscriptions_${this.activeAddress}`
+ );
+ return subscriptionData;
+ }
+
+ // handle subscription signup
+ async signUpSubscription(subscriptionData: SubscriptionData): Promise {
+ // validate subscription data
+ const requiredFields: (keyof SubscriptionData)[] = [
+ "arweaveAccountAddress",
+ "applicationName",
+ "subscriptionName",
+ "subscriptionFeeAmount",
+ "subscriptionStatus",
+ "recurringPaymentFrequency",
+ "nextPaymentDue",
+ "subscriptionStartData",
+ "subscriptionEndDate"
+ ];
+ for (const field of requiredFields) {
+ if (!subscriptionData[field]) {
+ throw new Error(`Missing required field: ${field}`);
+ }
+ }
+
+ // retrieve existing subscriptions
+ let existingSubscriptions: SubscriptionData[] =
+ await this.getSubscriptionData();
+
+ // append the new subsciption
+ existingSubscriptions.push(subscriptionData);
+
+ // store subscription data
+ await this.#storage.set(
+ `subscriptions_${this.activeAddress}`,
+ existingSubscriptions
+ );
+ }
+
+ // // fetch / add subscription data from app
+ // async fetchAppSubscriptionData(): Promise {
+
+ // // check if app is connected
+ // const hasConnection: boolean = await this.application.isConnected();
+ // if (!hasConnection) {
+ // throw new Error ("Not connected to an Arweave Application");
+ // }
+
+ // // call application hook() to get app data
+ // const [appData] = this.application.hook();
+
+ // // TODO check
+ // if (appData && appData.subscriptionData) {
+ // return appData.subscriptionData;
+ // } else {
+ // console.error("Error fetching subscription data")
+ // return null;
+ // }
+ // }
+}
+
+/**
+ * Params to add a subscription
+ * &
+ * Subscription info submitted by the dApp
+ */
+export interface SubscriptionData {
+ arweaveAccountAddress: string;
+ applicationIcon?: string;
+ applicationName: string;
+ subscriptionName: string;
+ subscriptionFeeAmount: number;
+ subscriptionStatus: SubscriptionStatus;
+ recurringPaymentFrequency: RecurringPaymentFrequency;
+ nextPaymentDue: Date;
+ subscriptionStartData: Date;
+ subscriptionEndDate: Date;
+}
+
+/**
+ * Enum for subscription status
+ */
+export enum SubscriptionStatus {
+ ACTIVE = "Active",
+ EXPIRED = "Expired",
+ CANCELED = "Canceled",
+ AWAITING_PAYMENT = "Awaiting-Payment"
+}
+
+/**
+ * Enum for recurring payment frequency
+ */
+export enum RecurringPaymentFrequency {
+ ANNUALLY = "Annually",
+ QUARTERLY = "Quarterly",
+ MONTHLY = "Monthly",
+ WEEKLY = "Weekly"
+}
From 8deb856cec8d65cda0584bdb9fc72c7d7df585ce Mon Sep 17 00:00:00 2001
From: elehmandevelopment
Date: Fri, 15 Mar 2024 11:33:05 -0600
Subject: [PATCH 02/60] feat: subscription allowance setting
---
assets/_locales/en/messages.json | 8 ++++++++
src/settings/index.ts | 9 +++++++++
2 files changed, 17 insertions(+)
diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json
index 23b246dbc..ef724687b 100644
--- a/assets/_locales/en/messages.json
+++ b/assets/_locales/en/messages.json
@@ -1705,5 +1705,13 @@
"token_imported": {
"message": "Token Succesfully Added",
"description": "token added"
+ },
+ "subscription_allowance": {
+ "message": "Subscription Allowance",
+ "description": "subscription settings title"
+ },
+ "subscription_description": {
+ "message": "Manage auto withdrawal allowance",
+ "description": "subscription setting description"
}
}
diff --git a/src/settings/index.ts b/src/settings/index.ts
index 3de88adea..a2447f29c 100644
--- a/src/settings/index.ts
+++ b/src/settings/index.ts
@@ -12,11 +12,20 @@ import {
SunIcon
} from "@iconicicons/react";
import Setting from "./setting";
+import { CreditCard02 } from "@untitled-ui/icons-react";
export const PREFIX = "setting_";
/** All settings */
const settings: Setting[] = [
+ new Setting({
+ name: "subscription_allowance",
+ displayName: "subscription_allowance",
+ icon: CreditCard02,
+ description: "subscription_description",
+ type: "number",
+ defaultValue: 0
+ }),
new Setting({
name: "fee_multiplier",
displayName: "setting_fee_multiplier",
From e8952301f5080c01efee2122c4a5194d6cae1fca Mon Sep 17 00:00:00 2001
From: elehmandevelopment
Date: Fri, 15 Mar 2024 11:58:36 -0600
Subject: [PATCH 03/60] ci: subscription allowance hook
---
src/subscriptions/allowance.ts | 0
src/subscriptions/subscription.ts | 8 ++++++++
2 files changed, 8 insertions(+)
delete mode 100644 src/subscriptions/allowance.ts
diff --git a/src/subscriptions/allowance.ts b/src/subscriptions/allowance.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/subscriptions/subscription.ts b/src/subscriptions/subscription.ts
index 10538f889..08b1a5bdd 100644
--- a/src/subscriptions/subscription.ts
+++ b/src/subscriptions/subscription.ts
@@ -23,6 +23,14 @@ export default class Subscription {
return subscriptionData;
}
+ // get subscription auto withdrawal allowance
+ async getAutoAllowance(): Promise {
+ const subscriptionAllowance = await this.#storage.get(
+ "setting_subscription_allowance"
+ );
+ return subscriptionAllowance;
+ }
+
// handle subscription signup
async signUpSubscription(subscriptionData: SubscriptionData): Promise {
// validate subscription data
From 40848322cf1036a790b496e7a63ddea448c46961 Mon Sep 17 00:00:00 2001
From: elehmandevelopment
Date: Fri, 15 Mar 2024 13:18:14 -0600
Subject: [PATCH 04/60] ci: injected subscription event
---
src/api/modules/subscription/index.ts | 11 +++++++++
src/injected.ts | 14 +++++++++++
src/subscriptions/subscription.ts | 35 +++++++++++++--------------
src/utils/events.ts | 2 ++
4 files changed, 44 insertions(+), 18 deletions(-)
create mode 100644 src/api/modules/subscription/index.ts
diff --git a/src/api/modules/subscription/index.ts b/src/api/modules/subscription/index.ts
new file mode 100644
index 000000000..c32ef7b9f
--- /dev/null
+++ b/src/api/modules/subscription/index.ts
@@ -0,0 +1,11 @@
+import type { PermissionType } from "~applications/permissions";
+import type { ModuleProperties } from "~api/module";
+
+const permissions: PermissionType[] = ["ACCESS_ADDRESS"];
+
+const subsciption: ModuleProperties = {
+ functionName: "subscription",
+ permissions
+};
+
+export default subsciption;
diff --git a/src/injected.ts b/src/injected.ts
index 423b71e19..c801c0bfa 100644
--- a/src/injected.ts
+++ b/src/injected.ts
@@ -109,4 +109,18 @@ window.addEventListener(
}
);
+/** Handle app subscriptions */
+window.addEventListener(
+ "message",
+ (
+ e: MessageEvent<{
+ type: "subscription";
+ event: Event;
+ }>
+ ) => {
+ if (e.data.type !== "subscription") return;
+ events.emit(e.data.event.subsciption, e.data.event.value);
+ }
+);
+
export {};
diff --git a/src/subscriptions/subscription.ts b/src/subscriptions/subscription.ts
index 08b1a5bdd..4ea96791f 100644
--- a/src/subscriptions/subscription.ts
+++ b/src/subscriptions/subscription.ts
@@ -65,26 +65,25 @@ export default class Subscription {
);
}
- // // fetch / add subscription data from app
- // async fetchAppSubscriptionData(): Promise {
-
- // // check if app is connected
- // const hasConnection: boolean = await this.application.isConnected();
- // if (!hasConnection) {
- // throw new Error ("Not connected to an Arweave Application");
- // }
+ // fetch / add subscription data from app
+ async fetchAppSubscriptionData(): Promise {
+ // check if app is connected
+ const hasConnection: boolean = await this.application.isConnected();
+ if (!hasConnection) {
+ throw new Error("Not connected to an Arweave Application");
+ }
- // // call application hook() to get app data
- // const [appData] = this.application.hook();
+ // call application hook() to get app data
+ const [appData] = this.application.hook();
- // // TODO check
- // if (appData && appData.subscriptionData) {
- // return appData.subscriptionData;
- // } else {
- // console.error("Error fetching subscription data")
- // return null;
- // }
- // }
+ // TODO check
+ if (appData && appData.subscriptionData) {
+ return appData.subscriptionData;
+ } else {
+ console.error("Error fetching subscription data");
+ return null;
+ }
+ }
}
/**
diff --git a/src/utils/events.ts b/src/utils/events.ts
index 731551c91..d9775beab 100644
--- a/src/utils/events.ts
+++ b/src/utils/events.ts
@@ -2,6 +2,7 @@ import type { PermissionType } from "~applications/permissions";
import { ExtensionStorage } from "./storage";
import { Gateway } from "~gateways/gateway";
import type { EventType } from "mitt";
+import type { SubscriptionData } from "~subscriptions/subscription";
interface SecurityEvent {
type: string;
@@ -32,4 +33,5 @@ export interface InjectedEvents extends Record {
addresses: string[];
permissions: PermissionType[];
gateway: Gateway;
+ subscription: SubscriptionData[];
}
From e045f1a5d92f10bc9940b90438c1b59153ec80e4 Mon Sep 17 00:00:00 2001
From: elehmandevelopment
Date: Fri, 15 Mar 2024 14:53:38 -0600
Subject: [PATCH 05/60] ci: subscription foreground module
---
.../subscription/subscription.background.ts | 0
.../subscription/subscription.foreground.ts | 49 +++++++++++++++++++
src/injected.ts | 14 ------
src/subscriptions/subscription.ts | 34 ++++++-------
4 files changed, 66 insertions(+), 31 deletions(-)
create mode 100644 src/api/modules/subscription/subscription.background.ts
create mode 100644 src/api/modules/subscription/subscription.foreground.ts
diff --git a/src/api/modules/subscription/subscription.background.ts b/src/api/modules/subscription/subscription.background.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/api/modules/subscription/subscription.foreground.ts b/src/api/modules/subscription/subscription.foreground.ts
new file mode 100644
index 000000000..38671a186
--- /dev/null
+++ b/src/api/modules/subscription/subscription.foreground.ts
@@ -0,0 +1,49 @@
+import type { SubscriptionData } from "~subscriptions/subscription";
+import type { ModuleFunction } from "~api/module";
+
+const foreground: ModuleFunction = (
+ arweaveAccountAddress,
+ applicationName,
+ subscriptionName,
+ subscriptionFeeAmount,
+ subscriptionStatus,
+ recurringPaymentFrequency,
+ nextPaymentDue,
+ subscriptionStartDate,
+ subscriptionEndDate,
+ applicationIcon?
+) => {
+ // Validate required fields
+ const requiredFields: (keyof SubscriptionData)[] = [
+ "arweaveAccountAddress",
+ "applicationName",
+ "subscriptionName",
+ "subscriptionFeeAmount",
+ "subscriptionStatus",
+ "recurringPaymentFrequency",
+ "nextPaymentDue",
+ "subscriptionStartDate",
+ "subscriptionEndDate"
+ ];
+
+ for (const field of requiredFields) {
+ if (typeof eval(field) === "undefined") {
+ throw new Error(`Missing required field: ${field}`);
+ }
+ }
+
+ return [
+ arweaveAccountAddress,
+ applicationName,
+ subscriptionName,
+ subscriptionFeeAmount,
+ subscriptionStatus,
+ recurringPaymentFrequency,
+ nextPaymentDue,
+ subscriptionStartDate,
+ subscriptionEndDate,
+ applicationIcon
+ ];
+};
+
+export default foreground;
diff --git a/src/injected.ts b/src/injected.ts
index c801c0bfa..423b71e19 100644
--- a/src/injected.ts
+++ b/src/injected.ts
@@ -109,18 +109,4 @@ window.addEventListener(
}
);
-/** Handle app subscriptions */
-window.addEventListener(
- "message",
- (
- e: MessageEvent<{
- type: "subscription";
- event: Event;
- }>
- ) => {
- if (e.data.type !== "subscription") return;
- events.emit(e.data.event.subsciption, e.data.event.value);
- }
-);
-
export {};
diff --git a/src/subscriptions/subscription.ts b/src/subscriptions/subscription.ts
index 4ea96791f..9c32edad3 100644
--- a/src/subscriptions/subscription.ts
+++ b/src/subscriptions/subscription.ts
@@ -66,24 +66,24 @@ export default class Subscription {
}
// fetch / add subscription data from app
- async fetchAppSubscriptionData(): Promise {
- // check if app is connected
- const hasConnection: boolean = await this.application.isConnected();
- if (!hasConnection) {
- throw new Error("Not connected to an Arweave Application");
- }
+ // async fetchAppSubscriptionData(): Promise {
+ // // check if app is connected
+ // const hasConnection: boolean = await this.application.isConnected();
+ // if (!hasConnection) {
+ // throw new Error("Not connected to an Arweave Application");
+ // }
- // call application hook() to get app data
- const [appData] = this.application.hook();
+ // // call application hook() to get app data
+ // const [appData] = this.application.hook();
- // TODO check
- if (appData && appData.subscriptionData) {
- return appData.subscriptionData;
- } else {
- console.error("Error fetching subscription data");
- return null;
- }
- }
+ // // TODO check
+ // if (appData && appData.subscriptionData) {
+ // return appData.subscriptionData;
+ // } else {
+ // console.error("Error fetching subscription data");
+ // return null;
+ // }
+ // }
}
/**
@@ -100,7 +100,7 @@ export interface SubscriptionData {
subscriptionStatus: SubscriptionStatus;
recurringPaymentFrequency: RecurringPaymentFrequency;
nextPaymentDue: Date;
- subscriptionStartData: Date;
+ subscriptionStartDate: Date;
subscriptionEndDate: Date;
}
From b485e80b32d38ca84cb7d978128394ad259aea27 Mon Sep 17 00:00:00 2001
From: elehmandevelopment
Date: Fri, 15 Mar 2024 18:28:33 -0600
Subject: [PATCH 06/60] feat: subscription flow
---
src/api/modules/connect/auth.ts | 3 +-
.../subscription/subscription.background.ts | 51 +++++++++++++++
src/applications/application.ts | 2 -
src/subscriptions/index.ts | 18 ++++++
src/subscriptions/subscription.ts | 28 ++------
src/utils/assertions.ts | 64 +++++++++++++++++++
src/utils/events.ts | 2 -
7 files changed, 141 insertions(+), 27 deletions(-)
create mode 100644 src/subscriptions/index.ts
diff --git a/src/api/modules/connect/auth.ts b/src/api/modules/connect/auth.ts
index d4ce5893c..2df92ca60 100644
--- a/src/api/modules/connect/auth.ts
+++ b/src/api/modules/connect/auth.ts
@@ -10,7 +10,8 @@ export type AuthType =
| "unlock"
| "token"
| "sign"
- | "signature";
+ | "signature"
+ | "subscription";
export interface AuthData {
// type of auth to request from the user
diff --git a/src/api/modules/subscription/subscription.background.ts b/src/api/modules/subscription/subscription.background.ts
index e69de29bb..acddcc972 100644
--- a/src/api/modules/subscription/subscription.background.ts
+++ b/src/api/modules/subscription/subscription.background.ts
@@ -0,0 +1,51 @@
+import {
+ isAddress,
+ isPermission,
+ isAppInfo,
+ isSubscriptionType
+} from "~utils/assertions";
+import type { ModuleFunction } from "~api/background";
+import authenticate from "../connect/auth";
+import { getSubscriptionData } from "~subscriptions";
+import {
+ RecurringPaymentFrequency,
+ type SubscriptionData
+} from "~subscriptions/subscription";
+
+const background: ModuleFunction = async (
+ appData,
+ subscriptionData: SubscriptionData,
+ type?: unknown
+) => {
+ // validate input
+ isAddress(subscriptionData.arweaveAccountAddress);
+
+ if (type) isSubscriptionType(type);
+
+ // check if subsciption exists
+ const subscriptions = await getSubscriptionData();
+
+ if (
+ subscriptions.find(
+ (subscription) =>
+ subscription.arweaveAccountAddress ===
+ subscriptionData.arweaveAccountAddress
+ )
+ ) {
+ throw new Error("Token already added");
+ }
+
+ await authenticate({
+ type: "subscription",
+ url: appData.appURL,
+ arweaveAccountAddress: subscriptionData.arweaveAccountAddress,
+ applicationName: subscriptionData.applicationName,
+ subscriptionName: subscriptionData.subscriptionName,
+ subscriptionFeeAmount: subscriptionData.subscriptionFeeAmount,
+ subsciptionStatus: subscriptionData.subscriptionStatus,
+ recurringPaymentFrequency: subscriptionData.recurringPaymentFrequency,
+ nextPaymentDue: subscriptionData.nextPaymentDue,
+ subscriptionStartDate: subscriptionData.subscriptionStartDate,
+ subscriptionEndDate: subscriptionData.subscriptionEndDate
+ });
+};
diff --git a/src/applications/application.ts b/src/applications/application.ts
index 096e8b2d8..4b3a3dd65 100644
--- a/src/applications/application.ts
+++ b/src/applications/application.ts
@@ -4,7 +4,6 @@ import { useStorage } from "@plasmohq/storage/hook";
import { ExtensionStorage } from "~utils/storage";
import type { Storage } from "@plasmohq/storage";
import { defaultGateway, Gateway } from "~gateways/gateway";
-import type { SubscriptionData } from "~subscriptions/subscription";
export const PREFIX = "app_";
export const defaultBundler = "https://turbo.ardrive.io";
@@ -193,5 +192,4 @@ export interface InitAppParams extends AppInfo {
allowance?: Allowance;
blocked?: boolean;
bundler?: string;
- subscriptionData?: SubscriptionData;
}
diff --git a/src/subscriptions/index.ts b/src/subscriptions/index.ts
new file mode 100644
index 000000000..469760c50
--- /dev/null
+++ b/src/subscriptions/index.ts
@@ -0,0 +1,18 @@
+import type { SubscriptionData } from "./subscription";
+import { ExtensionStorage } from "~utils/storage";
+
+// get subscription data from storage
+export async function getSubscriptionData(): Promise {
+ const subscriptionData = await ExtensionStorage.get(
+ `subscriptions_${this.activeAddress}`
+ );
+ return subscriptionData;
+}
+
+// get subscription auto withdrawal allowance
+export async function getAutoAllowance(): Promise {
+ const subscriptionAllowance = await ExtensionStorage.get(
+ "setting_subscription_allowance"
+ );
+ return subscriptionAllowance;
+}
diff --git a/src/subscriptions/subscription.ts b/src/subscriptions/subscription.ts
index 9c32edad3..156ddc008 100644
--- a/src/subscriptions/subscription.ts
+++ b/src/subscriptions/subscription.ts
@@ -1,6 +1,7 @@
import Application, { type InitAppParams } from "~applications/application";
import { ExtensionStorage } from "~utils/storage";
import type { Storage } from "@plasmohq/storage";
+import { getSubscriptionData } from "~subscriptions";
export default class Subscription {
activeAddress: string;
@@ -15,22 +16,6 @@ export default class Subscription {
this.#storage = ExtensionStorage;
}
- // get subscription data from storage
- async getSubscriptionData(): Promise {
- const subscriptionData = await this.#storage.get(
- `subscriptions_${this.activeAddress}`
- );
- return subscriptionData;
- }
-
- // get subscription auto withdrawal allowance
- async getAutoAllowance(): Promise {
- const subscriptionAllowance = await this.#storage.get(
- "setting_subscription_allowance"
- );
- return subscriptionAllowance;
- }
-
// handle subscription signup
async signUpSubscription(subscriptionData: SubscriptionData): Promise {
// validate subscription data
@@ -42,7 +27,7 @@ export default class Subscription {
"subscriptionStatus",
"recurringPaymentFrequency",
"nextPaymentDue",
- "subscriptionStartData",
+ "subscriptionStartDate",
"subscriptionEndDate"
];
for (const field of requiredFields) {
@@ -52,8 +37,7 @@ export default class Subscription {
}
// retrieve existing subscriptions
- let existingSubscriptions: SubscriptionData[] =
- await this.getSubscriptionData();
+ let existingSubscriptions: SubscriptionData[] = await getSubscriptionData();
// append the new subsciption
existingSubscriptions.push(subscriptionData);
@@ -99,9 +83,9 @@ export interface SubscriptionData {
subscriptionFeeAmount: number;
subscriptionStatus: SubscriptionStatus;
recurringPaymentFrequency: RecurringPaymentFrequency;
- nextPaymentDue: Date;
- subscriptionStartDate: Date;
- subscriptionEndDate: Date;
+ nextPaymentDue: Date | string;
+ subscriptionStartDate: Date | string;
+ subscriptionEndDate: Date | string;
}
/**
diff --git a/src/utils/assertions.ts b/src/utils/assertions.ts
index 2aed24c82..7e4846ceb 100644
--- a/src/utils/assertions.ts
+++ b/src/utils/assertions.ts
@@ -33,6 +33,7 @@ import {
isExactly
} from "typed-assert";
import { Gateway } from "~gateways/gateway";
+import type { SubscriptionData } from "~subscriptions/subscription";
export function isGateway(input: unknown): asserts input is Gateway {
isRecordWithKeys(
@@ -49,6 +50,69 @@ export function isGateway(input: unknown): asserts input is Gateway {
);
}
+export function isSubscriptionType(
+ input: unknown
+): asserts input is SubscriptionData[] {
+ isArray(input, "Input should be an array");
+
+ for (const item of input) {
+ isInstanceOf(item, Object, "Each item in the array should be an object.");
+
+ const {
+ arweaveAccountAddress,
+ applicationName,
+ subscriptionName,
+ subscriptionFeeAmount,
+ subscriptionStatus,
+ recurringPaymentFrequency,
+ nextPaymentDue,
+ subscriptionStartDate,
+ subscriptionEndDate,
+ applicationIcon
+ } = item as SubscriptionData;
+
+ isString(
+ arweaveAccountAddress,
+ "arweaveAccountAddress should be a string."
+ );
+ isString(applicationName, "applicationName should be a string.");
+ isString(subscriptionName, "subscriptionName should be a string.");
+ isNumber(
+ subscriptionFeeAmount,
+ "subscriptionFeeAmount should be a number."
+ );
+ isOneOf(
+ subscriptionStatus,
+ Object.values(subscriptionStatus),
+ "Invalid subscriptionStatus."
+ );
+ isOneOf(
+ recurringPaymentFrequency,
+ Object.values(recurringPaymentFrequency),
+ "Invalid recurringPaymentFrequency."
+ );
+ isOneOf(
+ nextPaymentDue,
+ Object.values(nextPaymentDue),
+ "Invalid nextPaymentDue."
+ );
+ isOneOf(
+ subscriptionStartDate,
+ Object.values(subscriptionStartDate),
+ "Invalid subscriptionStartDate."
+ );
+ isOneOf(
+ subscriptionEndDate,
+ Object.values(subscriptionEndDate),
+ "Invalid subscriptionEndDate"
+ );
+
+ if (applicationIcon !== undefined) {
+ isString(applicationIcon, "applicationIcon should be a string.");
+ }
+ }
+}
+
export function isTokenType(input: unknown): asserts input is TokenType {
isString(input, "Token type should be a string.");
isOneOf(
diff --git a/src/utils/events.ts b/src/utils/events.ts
index d9775beab..731551c91 100644
--- a/src/utils/events.ts
+++ b/src/utils/events.ts
@@ -2,7 +2,6 @@ import type { PermissionType } from "~applications/permissions";
import { ExtensionStorage } from "./storage";
import { Gateway } from "~gateways/gateway";
import type { EventType } from "mitt";
-import type { SubscriptionData } from "~subscriptions/subscription";
interface SecurityEvent {
type: string;
@@ -33,5 +32,4 @@ export interface InjectedEvents extends Record {
addresses: string[];
permissions: PermissionType[];
gateway: Gateway;
- subscription: SubscriptionData[];
}
From 004cd86e37ad34e37190fad0232394b7d6ec7726 Mon Sep 17 00:00:00 2001
From: elehmandevelopment
Date: Fri, 15 Mar 2024 18:35:28 -0600
Subject: [PATCH 07/60] ci: subscription class
---
src/subscriptions/subscription.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/subscriptions/subscription.ts b/src/subscriptions/subscription.ts
index 156ddc008..102a87299 100644
--- a/src/subscriptions/subscription.ts
+++ b/src/subscriptions/subscription.ts
@@ -49,6 +49,9 @@ export default class Subscription {
);
}
+ // TODO loadTokenLogo ?
+
+ // TODO check for subscriptionData from application
// fetch / add subscription data from app
// async fetchAppSubscriptionData(): Promise {
// // check if app is connected
From 119ea6668db2e66b776070438dc58c8262008e34 Mon Sep 17 00:00:00 2001
From: elehmandevelopment
Date: Sun, 17 Mar 2024 15:30:45 -0600
Subject: [PATCH 08/60] ci: add subscription module to foreground
---
src/api/foreground.ts | 5 ++++-
src/api/modules/subscription/index.ts | 4 ++--
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/api/foreground.ts b/src/api/foreground.ts
index dfad1260e..eac7e96a8 100644
--- a/src/api/foreground.ts
+++ b/src/api/foreground.ts
@@ -47,6 +47,8 @@ import signMessageModule from "./modules/sign_message";
import signMessage, {
finalizer as signMessageFinalizer
} from "./modules/sign_message/sign_message.foreground";
+import subscriptionModule from "./modules/subscription";
+import subscription from "./modules/subscription/subscription.foreground";
import privateHashModule from "./modules/private_hash";
import privateHash, {
finalizer as privateHashFinalizer
@@ -90,7 +92,8 @@ const modules: ForegroundModule[] = [
...signDataItemModule,
function: signDataItem,
finalizer: signDataItemFinalizer
- }
+ },
+ { ...subscriptionModule, function: subscription }
];
export default modules;
diff --git a/src/api/modules/subscription/index.ts b/src/api/modules/subscription/index.ts
index c32ef7b9f..9abd067d1 100644
--- a/src/api/modules/subscription/index.ts
+++ b/src/api/modules/subscription/index.ts
@@ -3,9 +3,9 @@ import type { ModuleProperties } from "~api/module";
const permissions: PermissionType[] = ["ACCESS_ADDRESS"];
-const subsciption: ModuleProperties = {
+const subsciptionModule: ModuleProperties = {
functionName: "subscription",
permissions
};
-export default subsciption;
+export default subsciptionModule;
From a5dab2b21f8b7b595133f60036fc295fdd70ba56 Mon Sep 17 00:00:00 2001
From: nicholas ma
Date: Tue, 19 Mar 2024 10:19:53 -0700
Subject: [PATCH 09/60] feat: connect subscription data from application wip
---
assets/_locales/en/messages.json | 8 ++
src/api/background.ts | 5 +-
.../subscription/subscription.background.ts | 7 +-
.../subscription/subscription.foreground.ts | 22 ++--
src/routes/auth/subscription.tsx | 108 ++++++++++++++++++
src/subscriptions/index.ts | 6 +-
src/tabs/auth.tsx | 2 +
7 files changed, 143 insertions(+), 15 deletions(-)
create mode 100644 src/routes/auth/subscription.tsx
diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json
index ef724687b..572ef010b 100644
--- a/assets/_locales/en/messages.json
+++ b/assets/_locales/en/messages.json
@@ -1710,6 +1710,14 @@
"message": "Subscription Allowance",
"description": "subscription settings title"
},
+ "subscription_title": {
+ "message": "Subscription",
+ "description": "subscription title"
+ },
+ "subscribe_description": {
+ "message": "APPNAME wants to set up a Period subscription",
+ "description": "subscribe description"
+ },
"subscription_description": {
"message": "Manage auto withdrawal allowance",
"description": "subscription setting description"
diff --git a/src/api/background.ts b/src/api/background.ts
index 3d83f063b..5f2b1ebcb 100644
--- a/src/api/background.ts
+++ b/src/api/background.ts
@@ -39,6 +39,8 @@ import verifyMessageModule from "./modules/verify_message";
import verifyMessage from "./modules/verify_message/verify_message.background";
import signDataItemModule from "./modules/sign_data_item";
import signDataItem from "./modules/sign_data_item/sign_data_item.background";
+import subscriptionModule from "./modules/subscription";
+import subscription from "./modules/subscription/subscription.background";
/** Background modules */
const modules: BackgroundModule[] = [
@@ -60,7 +62,8 @@ const modules: BackgroundModule[] = [
{ ...signMessageModule, function: signMessage },
{ ...privateHashModule, function: privateHash },
{ ...verifyMessageModule, function: verifyMessage },
- { ...signDataItemModule, function: signDataItem }
+ { ...signDataItemModule, function: signDataItem },
+ { ...subscriptionModule, function: subscription }
];
export default modules;
diff --git a/src/api/modules/subscription/subscription.background.ts b/src/api/modules/subscription/subscription.background.ts
index acddcc972..d31fb35e7 100644
--- a/src/api/modules/subscription/subscription.background.ts
+++ b/src/api/modules/subscription/subscription.background.ts
@@ -4,6 +4,7 @@ import {
isAppInfo,
isSubscriptionType
} from "~utils/assertions";
+import { getActiveAddress } from "~wallets";
import type { ModuleFunction } from "~api/background";
import authenticate from "../connect/auth";
import { getSubscriptionData } from "~subscriptions";
@@ -21,9 +22,9 @@ const background: ModuleFunction = async (
isAddress(subscriptionData.arweaveAccountAddress);
if (type) isSubscriptionType(type);
-
+ const address = await getActiveAddress();
// check if subsciption exists
- const subscriptions = await getSubscriptionData();
+ const subscriptions = await getSubscriptionData(address);
if (
subscriptions.find(
@@ -49,3 +50,5 @@ const background: ModuleFunction = async (
subscriptionEndDate: subscriptionData.subscriptionEndDate
});
};
+
+export default background;
diff --git a/src/api/modules/subscription/subscription.foreground.ts b/src/api/modules/subscription/subscription.foreground.ts
index 38671a186..061423378 100644
--- a/src/api/modules/subscription/subscription.foreground.ts
+++ b/src/api/modules/subscription/subscription.foreground.ts
@@ -33,16 +33,18 @@ const foreground: ModuleFunction = (
}
return [
- arweaveAccountAddress,
- applicationName,
- subscriptionName,
- subscriptionFeeAmount,
- subscriptionStatus,
- recurringPaymentFrequency,
- nextPaymentDue,
- subscriptionStartDate,
- subscriptionEndDate,
- applicationIcon
+ {
+ arweaveAccountAddress,
+ applicationName,
+ subscriptionName,
+ subscriptionFeeAmount,
+ subscriptionStatus,
+ recurringPaymentFrequency,
+ nextPaymentDue,
+ subscriptionStartDate,
+ subscriptionEndDate,
+ applicationIcon
+ }
];
};
diff --git a/src/routes/auth/subscription.tsx b/src/routes/auth/subscription.tsx
new file mode 100644
index 000000000..cb7962beb
--- /dev/null
+++ b/src/routes/auth/subscription.tsx
@@ -0,0 +1,108 @@
+import { replyToAuthRequest, useAuthParams, useAuthUtils } from "~utils/auth";
+import {
+ Button,
+ Card,
+ ListItem,
+ Section,
+ Spacer,
+ Text
+} from "@arconnect/components";
+import { useEffect, useMemo, useState } from "react";
+import Wrapper from "~components/auth/Wrapper";
+import browser from "webextension-polyfill";
+import Head from "~components/popup/Head";
+import styled from "styled-components";
+import HeadV2 from "~components/popup/HeadV2";
+
+export default function Subscription() {
+ // connect params
+ const params = useAuthParams();
+
+ // get auth utils
+ const { closeWindow, cancel } = useAuthUtils("signature", params?.authID);
+
+ // listen for enter to reset
+ // useEffect(() => {
+ // const listener = async (e: KeyboardEvent) => {
+ // if (e.key !== "Enter") return;
+ // await sign();
+ // };
+
+ // window.addEventListener("keydown", listener);
+
+ // return () => window.removeEventListener("keydown", listener);
+ // }, [params?.authID]);
+
+ // sign message
+ // async function sign() {
+ // // send response
+ // await replyToAuthRequest("signature", params?.authID);
+
+ // // close the window
+ // closeWindow();
+ // }
+
+ // message decode type
+
+ return (
+
+
+
+
+
+
+ {browser.i18n.getMessage("subscribe_description")}
+
+
+
+
+ Application address: YSykB4NhJHA7jk4
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const MessageHeader = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ p,
+ select {
+ font-size: 0.95rem;
+ }
+`;
+
+const EncodingSelect = styled.select`
+ font-weight: 500;
+ color: rgb(${(props) => props.theme.secondaryText});
+ outline: none;
+ border: none;
+ padding: 0;
+ margin: 0;
+ background-color: transparent;
+`;
+
+const MessageText = styled(Text).attrs({
+ noMargin: true
+})`
+ font-size: 0.9rem;
+`;
diff --git a/src/subscriptions/index.ts b/src/subscriptions/index.ts
index 469760c50..4187333b6 100644
--- a/src/subscriptions/index.ts
+++ b/src/subscriptions/index.ts
@@ -2,9 +2,11 @@ import type { SubscriptionData } from "./subscription";
import { ExtensionStorage } from "~utils/storage";
// get subscription data from storage
-export async function getSubscriptionData(): Promise {
+export async function getSubscriptionData(
+ activeAddress: string
+): Promise {
const subscriptionData = await ExtensionStorage.get(
- `subscriptions_${this.activeAddress}`
+ `subscriptions_${activeAddress}`
);
return subscriptionData;
}
diff --git a/src/tabs/auth.tsx b/src/tabs/auth.tsx
index 2ea1618db..58fc7dc15 100644
--- a/src/tabs/auth.tsx
+++ b/src/tabs/auth.tsx
@@ -14,6 +14,7 @@ import Connect from "~routes/auth/connect";
import Unlock from "~routes/auth/unlock";
import Token from "~routes/auth/token";
import Sign from "~routes/auth/sign";
+import Subscription from "~routes/auth/subscription";
export default function Auth() {
const theme = useTheme();
@@ -34,6 +35,7 @@ export default function Auth() {
+
From d6f22089805c9d6aedb9eaab241fabe681d4581f Mon Sep 17 00:00:00 2001
From: nicholas ma
Date: Mon, 18 Mar 2024 16:12:50 -0700
Subject: [PATCH 10/60] style: basic ui done for sub list
---
src/components/popup/home/AppIcon.tsx | 2 +-
src/routes/popup/subscriptions.tsx | 252 ++++++++++++++++++++++++--
2 files changed, 234 insertions(+), 20 deletions(-)
diff --git a/src/components/popup/home/AppIcon.tsx b/src/components/popup/home/AppIcon.tsx
index ad87612f1..1fd754632 100644
--- a/src/components/popup/home/AppIcon.tsx
+++ b/src/components/popup/home/AppIcon.tsx
@@ -9,7 +9,7 @@ export const NoAppIcon = styled(GlobeIcon)`
color: #fff;
`;
-const AppIcon = styled(Squircle)<{ color?: string }>`
+export const AppIcon = styled(Squircle)<{ color?: string }>`
color: ${(props) =>
props.color ? props.color : "rgb(" + props.theme.theme + ")"};
width: 3rem;
diff --git a/src/routes/popup/subscriptions.tsx b/src/routes/popup/subscriptions.tsx
index 72e084cd1..7a71fc728 100644
--- a/src/routes/popup/subscriptions.tsx
+++ b/src/routes/popup/subscriptions.tsx
@@ -5,6 +5,10 @@ import HeadV2 from "~components/popup/HeadV2";
import { useEffect, useState } from "react";
import { getActiveAddress } from "~wallets";
import { ExtensionStorage } from "~utils/storage";
+import styled from "styled-components";
+import Squircle from "~components/Squircle";
+import { getSubscriptionData } from "~subscriptions";
+import dayjs from "dayjs";
export default function Subscriptions() {
const [subData, setSubData] = useState(null);
@@ -13,23 +17,60 @@ export default function Subscriptions() {
async function getSubData() {
const address = await getActiveAddress();
- // await ExtensionStorage.set(`subscriptions_${address}`,
- // {
- // "applicationName": "ArDrive Turbo",
- // "arweaveAccountAddress": "JNC6vBhjHY1EPwV3pEeNmrsgFMxH5d38_LHsZ7jful8",
- // "nextPaymentDue": "03-08-2025",
- // "recurringPaymentFrequency": "Annually",
- // "subscriptionEndDate": "03-08-2025",
- // "subscriptionFeeAmount": 25,
- // "subscriptionName": "Turbo Subscription",
- // "subscriptionStartData": "03-08-2024",
- // "subscriptionStatus": "Active"
- // }
- // );
+ await ExtensionStorage.set(`subscriptions_${address}`, [
+ {
+ applicationName: "ArDrive",
+ applicationIcon: "tN4vheZxrAIjqCfbs3MDdWTXg8a_57JUNyoqA4uwr1k",
+ arweaveAccountAddress: "JNC6vBhjHY1EPwV3pEeNmrsgFMxH5d38_LHsZ7jful8",
+ nextPaymentDue: "03-08-2025",
+ recurringPaymentFrequency: "Annually",
+ subscriptionEndDate: "03-08-2025",
+ subscriptionFeeAmount: 25,
+ subscriptionName: "Turbo Subscription",
+ subscriptionStartData: "03-08-2024",
+ subscriptionStatus: "Awaiting payment"
+ },
+ {
+ applicationName: "PermaSwap Pro",
+ applicationIcon: "tN4vheZxrAIjqCfbs3MDdWTXg8a_57JUNyoqA4uwr1k",
+ arweaveAccountAddress: "JNC6vBhjHY1EPwV3pEeNmrsgFMxH5d38_LHsZ7jful8",
+ nextPaymentDue: "",
+ recurringPaymentFrequency: "Quarterly",
+ subscriptionEndDate: "03-08-2025",
+ subscriptionFeeAmount: 25,
+ subscriptionName: "PermaSwap Pro Subscription",
+ subscriptionStartData: "03-08-2024",
+ subscriptionStatus: "Cancelled"
+ },
+ {
+ applicationName: "BARK Pro",
+ applicationIcon: "tN4vheZxrAIjqCfbs3MDdWTXg8a_57JUNyoqA4uwr1k",
+ arweaveAccountAddress: "JNC6vBhjHY1EPwV3pEeNmrsgFMxH5d38_LHsZ7jful8",
+ nextPaymentDue: "03-08-2025",
+ recurringPaymentFrequency: "Weekly",
+ subscriptionEndDate: "03-08-2025",
+ subscriptionFeeAmount: 25,
+ subscriptionName: "Turbo Subscription",
+ subscriptionStartData: "03-08-2024",
+ subscriptionStatus: "Active"
+ },
+ {
+ applicationName: "Coinbase One",
+ applicationIcon: "tN4vheZxrAIjqCfbs3MDdWTXg8a_57JUNyoqA4uwr1k",
+ arweaveAccountAddress: "JNC6vBhjHY1EPwV3pEeNmrsgFMxH5d38_LHsZ7jful8",
+ nextPaymentDue: "",
+ recurringPaymentFrequency: "Monthly",
+ subscriptionEndDate: "03-08-2025",
+ subscriptionFeeAmount: 25,
+ subscriptionName: "Turbo Subscription",
+ subscriptionStartData: "03-08-2024",
+ subscriptionStatus: "Expired"
+ }
+ ]);
try {
const sub = new Subscription(address);
- const data = await sub.getSubscriptionData();
+ const data = await getSubscriptionData(address);
console.log("data: ", data);
setSubData(data);
@@ -44,14 +85,187 @@ export default function Subscriptions() {
{subData ? (
- <>
-
App: {subData.applicationName}
-
Subscription: {subData.subscriptionName}
-
Fee: {subData.subscriptionFeeAmount} AR
- >
+
+ {subData.map((sub) => {
+ return (
+
+ );
+ })}
+
) : (
No notifications found
)}
);
}
+
+const SubscriptionListItem = ({
+ title,
+ expiration,
+ status,
+ frequency,
+ amount,
+ icon
+}) => {
+ let period: string = "";
+ let color: string = "";
+ switch (status) {
+ case "Active":
+ color = "#14D110";
+ break;
+ case "Cancelled":
+ color = "#FF1A1A";
+ break;
+ case "Awaiting payment":
+ color = "#CFB111";
+ break;
+ default:
+ color = "#A3A3A3";
+ }
+
+ switch (frequency) {
+ case "Weekly":
+ period = "week";
+ break;
+ case "Monthly":
+ period = "month";
+ break;
+ case "Annually":
+ period = "year";
+ break;
+ case "Quarterly":
+ period = "quarter";
+ break;
+ default:
+ period = "";
+ }
+ return (
+
+
+
+
+
+ {title}
+
+ Next payment date:{" "}
+ {expiration ? (
+ {dayjs(expiration).format("MMM DD, YYYY")}
+ ) : (
+ "--"
+ )}
+
+
+
+
+ {status}
+
+
+ {amount} AR/{period}
+
+
+
+
+
+ );
+};
+
+const StatusCircle = ({ color }: { color: string }) => (
+
+);
+
+const Title = styled.div`
+ h3 {
+ color: #a3a3a3;
+
+ span {
+ color: white;
+ }
+ }
+`;
+
+const SubscriptionList = styled.div`
+ display: flex;
+ flex-direction: column;
+ border: 1px solid #333333;
+ border-radius: 10px;
+ margin: 0 15px;
+`;
+
+const ListItem = styled.div`
+ padding: 10px 0;
+ margin: 0 10px;
+
+ &:not(:last-child) {
+ border-bottom: 1px solid rgb(${(props) => props.theme.cardBorder});
+ }
+`;
+
+const Content = styled.div`
+ cursor: pointer;
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+ h2 {
+ margin: 0;
+ padding: 0;
+ font-weight: 500;
+ font-size: 1rem;
+ }
+ h3 {
+ margin: 0;
+ padding: 0;
+ font-weight: 500;
+ font-size: 10px;
+ }
+`;
+
+const ListDetails = styled.div`
+ display: flex;
+ height: 100%;
+ justify-content: space-between;
+ width: 100%;
+`;
+
+const SubscriptionInformation = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 36px;
+ text-align: right;
+`;
+
+const Status = styled.p<{ color: string }>`
+ margin: 0;
+ color: ${(props) => props.color};
+ font-size: 10px;
+`;
+
+const Image = styled.img`
+ width: 16px;
+ padding: 0 8px;
+ border: 1px solid rgb(${(props) => props.theme.cardBorder});
+ border-radius: 2px;
+`;
+
+const AppIcon = styled(Squircle)<{ color?: string }>`
+ color: ${(props) =>
+ props.color ? props.color : "rgb(" + props.theme.theme + ")"};
+ width: 2rem;
+ height: 2rem;
+ cursor: pointer;
+`;
From 881af29daac3d31c0eb159cf6977a239e03f6790 Mon Sep 17 00:00:00 2001
From: nicholas ma
Date: Tue, 19 Mar 2024 17:00:12 -0700
Subject: [PATCH 11/60] feat: added subscription details page
---
src/popup.tsx | 8 +-
.../subscriptions/subscriptionDetails.tsx | 267 ++++++++++++++++++
.../{ => subscriptions}/subscriptions.tsx | 26 +-
3 files changed, 292 insertions(+), 9 deletions(-)
create mode 100644 src/routes/popup/subscriptions/subscriptionDetails.tsx
rename src/routes/popup/{ => subscriptions}/subscriptions.tsx (92%)
diff --git a/src/popup.tsx b/src/popup.tsx
index 9f1c5b897..3b242e934 100644
--- a/src/popup.tsx
+++ b/src/popup.tsx
@@ -20,7 +20,7 @@ import SendAuth from "~routes/popup/send/auth";
import Explore from "~routes/popup/explore";
import Unlock from "~routes/popup/unlock";
import Notifications from "~routes/popup/notifications";
-import Subscriptions from "~routes/popup/subscriptions";
+import Subscriptions from "~routes/popup/subscriptions/subscriptions";
import Tokens from "~routes/popup/tokens";
import Asset from "~routes/popup/token/[id]";
import Collectibles from "~routes/popup/collectibles";
@@ -30,6 +30,7 @@ import Recipient from "~routes/popup/send/recipient";
import Confirm from "~routes/popup/send/confirm";
import { NavigationBar } from "~components/popup/Navigation";
import MessageNotification from "~routes/popup/notification/[id]";
+import SubscriptionDetails from "~routes/popup/subscriptions/subscriptionDetails";
export default function Popup() {
const theme = useTheme();
@@ -73,6 +74,11 @@ export default function Popup() {
+
+ {(params: { id: string }) => (
+
+ )}
+
{(params: { id: string }) => (
diff --git a/src/routes/popup/subscriptions/subscriptionDetails.tsx b/src/routes/popup/subscriptions/subscriptionDetails.tsx
new file mode 100644
index 000000000..68b275456
--- /dev/null
+++ b/src/routes/popup/subscriptions/subscriptionDetails.tsx
@@ -0,0 +1,267 @@
+import Subscription, {
+ type SubscriptionData
+} from "~subscriptions/subscription";
+import HeadV2 from "~components/popup/HeadV2";
+import { useEffect, useState } from "react";
+import { getActiveAddress } from "~wallets";
+import { ExtensionStorage } from "~utils/storage";
+import styled from "styled-components";
+import Squircle from "~components/Squircle";
+import { getSubscriptionData } from "~subscriptions";
+import dayjs from "dayjs";
+import { ButtonV2, Input, InputV2, ListItem } from "@arconnect/components";
+import { AppIcon, Content, Title } from "./subscriptions";
+
+interface Props {
+ id?: string;
+}
+
+export default function SubscriptionDetails({ id }: Props) {
+ const [subData, setSubData] = useState(null);
+
+ useEffect(() => {
+ async function getSubData() {
+ const address = await getActiveAddress();
+
+ try {
+ const sub = new Subscription(address);
+ const data = await getSubscriptionData(address);
+ // finding like this for now
+ const subscription = data.find(
+ (subscription) => subscription.arweaveAccountAddress === id
+ );
+ setSubData(subscription);
+ } catch (error) {
+ console.error("Error fetching subscription data:", error);
+ }
+ }
+ getSubData();
+ }, []);
+
+ return (
+ <>
+
+ {subData && (
+
+
+
+
+
+
+ {subData.applicationName}
+
+ Status:{" "}
+
+ {subData.subscriptionStatus}
+
+
+
+
+
+
+ Application address: YSykB4NhJHA7jk4
+
+
+ Recurring payment amount
+
+ 25 AR
+
+ Subscription: Yearly
+
+
+
+ $625.00 USD
+
+ Next payment: Mar 8, 2025
+
+
+
+
+
+
+
+ Start
+
+
+ End
+
+
+
+ Mar 8, 2024
+ Mar 8, 2025
+
+
+ {/* Toggle */}
+
+ Auto-renewal
+
+
+
+
+
+ Automatic Payment Threshold
+
+
+ {/* */}
+
+
+
+
+ Manage Subscription
+
+
+ Cancel Subscription
+
+
+
+ )}
+ >
+ );
+}
+
+const SubscriptionText = styled.div<{ fontSize?: string; color?: string }>`
+ font-size: ${(props) => props.fontSize || "16px"};
+ font-weight: 500;
+ color: ${(props) => props.color || "#a3a3a3"};
+
+ span {
+ color: #ffffff;
+ }
+`;
+
+const Threshold = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+`;
+
+const Body = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+`;
+
+const PaymentDetails = styled.div`
+ h3 {
+ margin: 0;
+ font-size: 32px;
+ font-weight: 600;
+ }
+ h6 {
+ margin: 0;
+ font-weight: 500;
+ font-size: 10px;
+ }
+`;
+
+const Main = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+`;
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - 100px);
+ justify-content: space-between;
+ padding: 15px;
+`;
+
+export const SubscriptionListItem = styled.div`
+ display: flex;
+`;
+
+const ToggleSwitch = () => {
+ const [checked, setChecked] = useState(false);
+
+ const handleChange = () => {
+ setChecked(!checked);
+ };
+
+ return (
+
+
+
+
+ );
+};
+const SwitchWrapper = styled.label`
+ position: relative;
+ display: inline-block;
+ width: 44px; // Total width of the switch
+ height: 22px; // Total height of the switch
+`;
+
+const Slider = styled.span`
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ transition: 0.4s;
+ border-radius: 22px;
+
+ &:before {
+ position: absolute;
+ content: "";
+ height: 18px;
+ width: 18px;
+ left: 2px;
+ bottom: 2px;
+ background-color: white;
+ transition: 0.4s;
+ border-radius: 50%;
+ }
+`;
+
+const Checkbox = styled.input`
+ opacity: 0;
+ width: 0;
+ height: 0;
+
+ &:checked + ${Slider} {
+ background-color: #8e7bea;
+ }
+
+ &:checked + ${Slider}:before {
+ // The translateX value should match the width of the switch minus the circle diameter and margins
+ transform: translateX(22px); // Adjusted to fit the new size
+ }
+`;
+
+const InfoCircle = () => (
+
+);
diff --git a/src/routes/popup/subscriptions.tsx b/src/routes/popup/subscriptions/subscriptions.tsx
similarity index 92%
rename from src/routes/popup/subscriptions.tsx
rename to src/routes/popup/subscriptions/subscriptions.tsx
index 7a71fc728..8dbc0c64a 100644
--- a/src/routes/popup/subscriptions.tsx
+++ b/src/routes/popup/subscriptions/subscriptions.tsx
@@ -9,6 +9,7 @@ import styled from "styled-components";
import Squircle from "~components/Squircle";
import { getSubscriptionData } from "~subscriptions";
import dayjs from "dayjs";
+import { useHistory } from "~utils/hash_router";
export default function Subscriptions() {
const [subData, setSubData] = useState(null);
@@ -95,6 +96,7 @@ export default function Subscriptions() {
status={sub.subscriptionStatus}
frequency={sub.recurringPaymentFrequency}
amount={sub.subscriptionFeeAmount}
+ id={sub.arweaveAccountAddress}
/>
);
})}
@@ -112,6 +114,8 @@ const SubscriptionListItem = ({
status,
frequency,
amount,
+ id,
+
icon
}) => {
let period: string = "";
@@ -146,8 +150,10 @@ const SubscriptionListItem = ({
default:
period = "";
}
+
+ const [push] = useHistory();
return (
-
+ push(`/subscriptions/${id}`)}>
@@ -188,7 +194,7 @@ const StatusCircle = ({ color }: { color: string }) => (
);
-const Title = styled.div`
+export const Title = styled.div`
h3 {
color: #a3a3a3;
@@ -215,7 +221,7 @@ const ListItem = styled.div`
}
`;
-const Content = styled.div`
+export const Content = styled.div`
cursor: pointer;
display: flex;
gap: 0.75rem;
@@ -262,10 +268,14 @@ const Image = styled.img`
border-radius: 2px;
`;
-const AppIcon = styled(Squircle)<{ color?: string }>`
- color: ${(props) =>
- props.color ? props.color : "rgb(" + props.theme.theme + ")"};
- width: 2rem;
- height: 2rem;
+type SquircleProps = {
+ color?: string;
+ customSize?: string;
+};
+
+export const AppIcon = styled(Squircle)`
+ color: ${(props) => props.color || `rgb(${props.theme.theme})`};
+ width: ${(props) => props.customSize || "2rem"};
+ height: ${(props) => props.customSize || "2rem"};
cursor: pointer;
`;
From 5a1b9bb1551e1e6cdbd3afbb6d3a365877dc0d7b Mon Sep 17 00:00:00 2001
From: nicholas ma
Date: Tue, 19 Mar 2024 17:53:22 -0700
Subject: [PATCH 12/60] feat: added connect popup ui
---
.../subscription/subscription.background.ts | 18 +-
src/routes/auth/subscription.tsx | 159 +++++++++++++-----
.../subscriptions/subscriptionDetails.tsx | 17 +-
3 files changed, 133 insertions(+), 61 deletions(-)
diff --git a/src/api/modules/subscription/subscription.background.ts b/src/api/modules/subscription/subscription.background.ts
index d31fb35e7..b640bfb87 100644
--- a/src/api/modules/subscription/subscription.background.ts
+++ b/src/api/modules/subscription/subscription.background.ts
@@ -26,15 +26,15 @@ const background: ModuleFunction = async (
// check if subsciption exists
const subscriptions = await getSubscriptionData(address);
- if (
- subscriptions.find(
- (subscription) =>
- subscription.arweaveAccountAddress ===
- subscriptionData.arweaveAccountAddress
- )
- ) {
- throw new Error("Token already added");
- }
+ // if (
+ // subscriptions.find(
+ // (subscription) =>
+ // subscription.arweaveAccountAddress ===
+ // subscriptionData.arweaveAccountAddress
+ // )
+ // ) {
+ // throw new Error("Token already added");
+ // }
await authenticate({
type: "subscription",
diff --git a/src/routes/auth/subscription.tsx b/src/routes/auth/subscription.tsx
index cb7962beb..4b3e9ac6d 100644
--- a/src/routes/auth/subscription.tsx
+++ b/src/routes/auth/subscription.tsx
@@ -1,25 +1,31 @@
import { replyToAuthRequest, useAuthParams, useAuthUtils } from "~utils/auth";
-import {
- Button,
- Card,
- ListItem,
- Section,
- Spacer,
- Text
-} from "@arconnect/components";
-import { useEffect, useMemo, useState } from "react";
-import Wrapper from "~components/auth/Wrapper";
-import browser from "webextension-polyfill";
-import Head from "~components/popup/Head";
+import { ButtonV2, InputV2, Text } from "@arconnect/components";
+
import styled from "styled-components";
import HeadV2 from "~components/popup/HeadV2";
+import {
+ Body,
+ InfoCircle,
+ Main,
+ PaymentDetails,
+ SubscriptionListItem,
+ SubscriptionText,
+ Threshold,
+ ToggleSwitch
+} from "~routes/popup/subscriptions/subscriptionDetails";
+import {
+ AppIcon,
+ Content,
+ Title
+} from "~routes/popup/subscriptions/subscriptions";
+import dayjs from "dayjs";
export default function Subscription() {
// connect params
const params = useAuthParams();
// get auth utils
- const { closeWindow, cancel } = useAuthUtils("signature", params?.authID);
+ const { closeWindow, cancel } = useAuthUtils("subscription", params?.authID);
// listen for enter to reset
// useEffect(() => {
@@ -45,41 +51,104 @@ export default function Subscription() {
// message decode type
return (
-
-
-
-
-
-
- {browser.i18n.getMessage("subscribe_description")}
-
-
-
-
- Application address: YSykB4NhJHA7jk4
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
+ {console.log("params", params)}
+ {params && (
+
+
+
+
+
+
+ {params.applicationName}
+
+ Status:{" "}
+ Pending
+
+
+
+
+
+ Application address: {params.arweaveAccountAddress}
+
+
+ Recurring payment amount
+
+ {params.subscriptionFeeAmount} AR
+
+ Subscription: {params.recurringPaymentFrequency}
+
+
+
+ $625.00 USD
+
+ Next payment:{" "}
+ {dayjs(params.nextPaymentDue).format("MMM DD, YYYY")}
+
+
+
+
+
+
+
+ Start
+
+
+ End
+
+
+
+ Mar 8, 2024
+ Mar 8, 2025
+
+
+ {/* Toggle */}
+
+ Auto-renewal
+
+
+
+
+
+ Automatic Payment Threshold
+
+
+
+
+
+
+
+ Confirm Subscription
+
+
+ Cancel
+
+
+
+ )}
+ >
);
}
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - 100px);
+ justify-content: space-between;
+ padding: 15px;
+`;
+
const MessageHeader = styled.div`
display: flex;
align-items: center;
diff --git a/src/routes/popup/subscriptions/subscriptionDetails.tsx b/src/routes/popup/subscriptions/subscriptionDetails.tsx
index 68b275456..9c4395992 100644
--- a/src/routes/popup/subscriptions/subscriptionDetails.tsx
+++ b/src/routes/popup/subscriptions/subscriptionDetails.tsx
@@ -128,7 +128,10 @@ export default function SubscriptionDetails({ id }: Props) {
);
}
-const SubscriptionText = styled.div<{ fontSize?: string; color?: string }>`
+export const SubscriptionText = styled.div<{
+ fontSize?: string;
+ color?: string;
+}>`
font-size: ${(props) => props.fontSize || "16px"};
font-weight: 500;
color: ${(props) => props.color || "#a3a3a3"};
@@ -138,19 +141,19 @@ const SubscriptionText = styled.div<{ fontSize?: string; color?: string }>`
}
`;
-const Threshold = styled.div`
+export const Threshold = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
`;
-const Body = styled.div`
+export const Body = styled.div`
display: flex;
justify-content: space-between;
align-items: flex-end;
`;
-const PaymentDetails = styled.div`
+export const PaymentDetails = styled.div`
h3 {
margin: 0;
font-size: 32px;
@@ -163,7 +166,7 @@ const PaymentDetails = styled.div`
}
`;
-const Main = styled.div`
+export const Main = styled.div`
display: flex;
flex-direction: column;
gap: 18px;
@@ -181,7 +184,7 @@ export const SubscriptionListItem = styled.div`
display: flex;
`;
-const ToggleSwitch = () => {
+export const ToggleSwitch = () => {
const [checked, setChecked] = useState(false);
const handleChange = () => {
@@ -241,7 +244,7 @@ const Checkbox = styled.input`
}
`;
-const InfoCircle = () => (
+export const InfoCircle = () => (
- $625.00 USD
+
+ ${price ? price.toFixed(2) : "--.--"} {currency}
+
(null);
+ const [, goBack] = useHistory();
+ const { setToast } = useToasts();
+ const [price, setPrice] = useState();
+ const [currency] = useSetting("currency");
+ const [color, setColor] = useState("");
+
+ const cancel = async () => {
+ const address = await getActiveAddress();
+ if (subData.subscriptionStatus !== SubscriptionStatus.CANCELED) {
+ try {
+ await updateSubscription(
+ address,
+ subData.arweaveAccountAddress,
+ SubscriptionStatus.CANCELED
+ );
+ setToast({
+ type: "success",
+ content: browser.i18n.getMessage("subscription_cancelled"),
+ duration: 5000
+ });
+ } catch {
+ setToast({
+ type: "error",
+ content: browser.i18n.getMessage("subscription_cancelled_error"),
+ duration: 5000
+ });
+ }
+ } else {
+ try {
+ await deleteSubscription(address, subData.arweaveAccountAddress);
+ setToast({
+ type: "success",
+ content: browser.i18n.getMessage("subscription_deleted"),
+ duration: 5000
+ });
+ } catch (err) {
+ setToast({
+ type: "error",
+ content: browser.i18n.getMessage("subcription_delete_error"),
+ duration: 5000
+ });
+ }
+ }
+ goBack();
+ // redirect to subscription page
+ };
useEffect(() => {
async function getSubData() {
@@ -43,6 +98,11 @@ export default function SubscriptionDetails({ id }: Props) {
(subscription) => subscription.arweaveAccountAddress === id
);
setSubData(subscription);
+ setColor(getColorByStatus(subscription.subscriptionStatus));
+ const arPrice = await getPrice("arweave", currency);
+ if (arPrice) {
+ setPrice(arPrice * subscription.subscriptionFeeAmount);
+ }
} catch (error) {
console.error("Error fetching subscription data:", error);
}
@@ -52,12 +112,12 @@ export default function SubscriptionDetails({ id }: Props) {
return (
<>
-
+
{subData && (
-
+
{subData.applicationName}
Status:{" "}
-
- {subData.subscriptionStatus}
-
+ {subData.subscriptionStatus}
-
+
Application address:{" "}
{formatAddress(subData.arweaveAccountAddress, 8)}
Recurring payment amount
- 25 AR
+ {subData.subscriptionFeeAmount} AR
- Subscription: Yearly
+ Subscription: {subData.recurringPaymentFrequency}
- $625.00 USD
+
+ ${price ? price.toFixed(2) : "--.--"} {currency}
+
- Mar 8, 2024
- Mar 8, 2025
+
+ {dayjs(subData.subscriptionStartDate).format("MMM DD, YYYY")}
+
+
+ {dayjs(subData.subscriptionEndDate).format("MMM DD, YYYY")}
+
{/* Toggle */}
- Auto-renewal
+
+ Auto-renewal
+
@@ -133,7 +204,12 @@ export default function SubscriptionDetails({ id }: Props) {
- Automatic Payment Threshold
+ Automatic Payment Threshold{" "}
+
+
+