Split subscription page into two components

This commit is contained in:
silverpill 2022-06-05 20:02:25 +00:00
parent 4d6ff7b386
commit c5cf53a1be
5 changed files with 466 additions and 354 deletions

View file

@ -0,0 +1,202 @@
<template>
<div class="subscription">
<h1>Subscription</h1>
<div class="connect-wallet" v-if="canConnectWallet()">
<button class="btn" @click="connectWallet()">Connect wallet</button>
</div>
<div class="wallet-error" v-if="walletError">
{{ walletError }}
</div>
<div class="info" v-if="subscriptionConfigured !== null">
<template v-if="subscription">
<div>Recipient address: {{ subscription.recipientAddress }}</div>
<div>Token address: {{ subscription.tokenAddress }}</div>
<div>Token symbol: {{ subscription.tokenSymbol }}</div>
<div>Price of one month: {{ subscription.price }}</div>
<template v-if="subscriptionState">
<div>Your address: {{ subscriptionState.senderAddress }}</div>
<div>Your balance: {{ subscriptionState.senderBalance }}</div>
<div>Expires at: {{ subscription.getExpirationDate(subscriptionState.senderBalance).toLocaleString() }}</div>
</template>
</template>
<template v-else>
Subscription is not available.
</template>
</div>
<div class="payment" v-if="canSubscribe()">
<button class="btn" @click="onMakeSubscriptionPayment()">
Pay for subscription
</button>
</div>
<div class="cancel" v-if="canCancel()">
<button class="btn" @click="onCancelSubscription()">
Cancel subscription
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { $, $ref } from "vue/macros"
import { getVerifiedEthereumAddress, Profile } from "@/api/users"
import {
cancelSubscription,
getSubscriptionInfo,
getSubscriptionState,
makeSubscriptionPayment,
Subscription,
SubscriptionState,
} from "@/api/subscriptions"
import { useWallet } from "@/composables/wallet"
import { useInstanceInfo } from "@/store/instance"
import { ethereumAddressMatch, getWeb3Provider } from "@/utils/ethereum"
/* eslint-disable-next-line no-undef */
const props = defineProps<{
profile: Profile,
}>()
const { instance } = $(useInstanceInfo())
const { connectWallet: connectEthereumWallet } = useWallet()
const recipientEthereumAddress = getVerifiedEthereumAddress(props.profile)
let { walletAddress, walletError } = $(useWallet())
let subscriptionConfigured = $ref<boolean | null>(null)
let subscription = $ref<Subscription | null>(null)
let subscriptionState = $ref<SubscriptionState | null>(null)
function canConnectWallet(): boolean {
return (
Boolean(instance?.blockchain_id) &&
Boolean(instance?.blockchain_contract_address) &&
// Only profiles with verified address can have subscription
recipientEthereumAddress !== null &&
walletAddress === null
)
}
function reset() {
subscriptionConfigured = null
subscription = null
subscriptionState = null
}
async function connectWallet() {
await connectEthereumWallet(reset)
if (!recipientEthereumAddress || !walletAddress) {
return
}
if (ethereumAddressMatch(walletAddress, recipientEthereumAddress)) {
walletError = "incorrect wallet address"
return
}
checkSubscription()
}
async function checkSubscription() {
if (
!instance?.blockchain_contract_address ||
!recipientEthereumAddress ||
!walletAddress
) {
return
}
const signer = getWeb3Provider().getSigner()
subscription = await getSubscriptionInfo(
instance.blockchain_contract_address,
signer,
recipientEthereumAddress,
)
if (subscription !== null) {
subscriptionConfigured = true
} else {
subscriptionConfigured = false
}
subscriptionState = await getSubscriptionState(
instance.blockchain_contract_address,
signer,
walletAddress,
recipientEthereumAddress,
)
}
function canSubscribe(): boolean {
return subscriptionConfigured === true
}
async function onMakeSubscriptionPayment() {
if (
!instance?.blockchain_contract_address ||
!recipientEthereumAddress ||
!walletAddress
) {
return
}
const signer = getWeb3Provider().getSigner()
const transaction = await makeSubscriptionPayment(
instance.blockchain_contract_address,
signer,
recipientEthereumAddress,
)
await transaction.wait()
subscriptionState = await getSubscriptionState(
instance.blockchain_contract_address,
signer,
walletAddress,
recipientEthereumAddress,
)
}
function canCancel(): boolean {
return (
subscriptionState !== null &&
!subscriptionState.senderBalance.isZero()
)
}
async function onCancelSubscription() {
if (
!instance?.blockchain_contract_address ||
!recipientEthereumAddress ||
!walletAddress
) {
return
}
const signer = getWeb3Provider().getSigner()
const transaction = await cancelSubscription(
instance.blockchain_contract_address,
signer,
recipientEthereumAddress,
)
await transaction.wait()
subscriptionState = await getSubscriptionState(
instance.blockchain_contract_address,
signer,
walletAddress,
recipientEthereumAddress,
)
}
</script>
<style scoped lang="scss">
@import "../styles/layout";
@import "../styles/mixins";
@import "../styles/theme";
.subscription {
@include block-btn;
background-color: $block-background-color;
border-radius: $block-border-radius;
display: flex;
flex-direction: column;
gap: $block-inner-padding / 2;
margin-bottom: $block-outer-padding;
padding: $block-inner-padding;
h1 {
font-size: 20px;
margin: 0;
}
}
</style>

View file

@ -0,0 +1,221 @@
<template>
<div class="subscription">
<h1>Configure subscription</h1>
<div class="connect-wallet" v-if="canConnectWallet()">
<button class="btn" @click="connectWallet()">Connect wallet</button>
</div>
<div class="wallet-error" v-if="walletError">
{{ walletError }}
</div>
<div class="info" v-if="subscriptionConfigured !== null">
<template v-if="subscription">
<div>Recipient address: {{ subscription.recipientAddress }}</div>
<div>Token address: {{ subscription.tokenAddress }}</div>
<div>Token symbol: {{ subscription.tokenSymbol }}</div>
<div>Price of one month: {{ subscription.price }}</div>
</template>
<template v-else>
Subscription is not configured.
</template>
</div>
<div class="setup" v-if="canConfigureSubscription()">
<button class="btn" @click="onConfigureSubscription()">
Set up subscription
</button>
</div>
<div class="withdraw" v-if="subscription !== null">
<input v-model="subscriberAddress" placeholder="Subscriber address">
<button class="btn" @click="onCheckSubsciptionState()">Check</button>
<button class="btn" v-if="subscriptionState !== null" @click="onWithdrawReceived()">
Withdraw {{ subscriptionState.recipientBalance }} {{ subscription.tokenSymbol }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { $, $ref } from "vue/macros"
import { getVerifiedEthereumAddress, Profile } from "@/api/users"
import {
configureSubscription,
getSubscriptionAuthorization,
getSubscriptionInfo,
getSubscriptionState,
withdrawReceived,
Subscription,
SubscriptionState,
} from "@/api/subscriptions"
import { useWallet } from "@/composables/wallet"
import { useInstanceInfo } from "@/store/instance"
import { useCurrentUser } from "@/store/user"
import { ethereumAddressMatch, getWeb3Provider } from "@/utils/ethereum"
/* eslint-disable-next-line no-undef */
const props = defineProps<{
profile: Profile,
}>()
const { currentUser, ensureAuthToken } = $(useCurrentUser())
const { instance } = $(useInstanceInfo())
const { connectWallet: connectEthereumWallet } = useWallet()
const profileEthereumAddress = getVerifiedEthereumAddress(props.profile)
let { walletAddress, walletError } = $(useWallet())
let subscriptionConfigured = $ref<boolean | null>(null)
let subscription = $ref<Subscription | null>(null)
let subscriptionState = $ref<SubscriptionState | null>(null)
let subscriberAddress = $ref<string | null>(null)
function canConnectWallet(): boolean {
return (
Boolean(instance?.blockchain_id) &&
Boolean(instance?.blockchain_contract_address) &&
// Only profiles with verified address can have subscription
profileEthereumAddress !== null &&
walletAddress === null
)
}
function reset() {
subscriptionConfigured = null
subscription = null
subscriptionState = null
subscriberAddress = null
}
async function connectWallet() {
await connectEthereumWallet(reset)
if (!profileEthereumAddress || !walletAddress) {
return
}
if (!ethereumAddressMatch(walletAddress, profileEthereumAddress)) {
// Recipient must use verified account
walletError = "incorrect wallet address"
return
}
checkSubscription()
}
async function checkSubscription() {
if (
!profileEthereumAddress ||
!instance ||
!instance.blockchain_contract_address
) {
return
}
const signer = getWeb3Provider().getSigner()
subscription = await getSubscriptionInfo(
instance.blockchain_contract_address,
signer,
profileEthereumAddress,
)
if (subscription !== null) {
subscriptionConfigured = true
} else {
subscriptionConfigured = false
}
}
function canConfigureSubscription(): boolean {
return (
Boolean(currentUser?.wallet_address) &&
subscriptionConfigured === false
)
}
async function onConfigureSubscription() {
if (
!currentUser ||
!currentUser.wallet_address ||
!instance ||
!instance.blockchain_contract_address
) {
return
}
// Subscription configuration tx can be sent from any address
const signer = getWeb3Provider().getSigner()
const authToken = ensureAuthToken()
const signature = await getSubscriptionAuthorization(authToken)
const transaction = await configureSubscription(
instance.blockchain_contract_address,
signer,
currentUser.wallet_address,
signature,
)
await transaction.wait()
subscriptionConfigured = true
subscription = await getSubscriptionInfo(
instance.blockchain_contract_address,
signer,
currentUser.wallet_address,
)
}
async function onCheckSubsciptionState() {
if (
!profileEthereumAddress ||
!instance?.blockchain_contract_address ||
!subscriberAddress
) {
return
}
const signer = getWeb3Provider().getSigner()
subscriptionState = await getSubscriptionState(
instance.blockchain_contract_address,
signer,
subscriberAddress,
profileEthereumAddress,
)
}
async function onWithdrawReceived() {
if (
!instance?.blockchain_contract_address ||
!subscriberAddress
) {
return
}
const signer = getWeb3Provider().getSigner()
await withdrawReceived(
instance.blockchain_contract_address,
signer,
subscriberAddress,
)
subscriptionState = null
}
</script>
<style scoped lang="scss">
@import "../styles/layout";
@import "../styles/mixins";
@import "../styles/theme";
.subscription {
@include block-btn;
background-color: $block-background-color;
border-radius: $block-border-radius;
display: flex;
flex-direction: column;
gap: $block-inner-padding / 2;
margin-bottom: $block-outer-padding;
padding: $block-inner-padding;
h1 {
font-size: 20px;
margin: 0;
}
}
.withdraw {
display: flex;
flex-wrap: wrap;
gap: $block-inner-padding / 2;
input {
border: 1px solid $btn-background-hover-color;
border-radius: $btn-border-radius;
}
}
</style>

View file

@ -13,7 +13,7 @@ import PostOverlay from "@/views/PostOverlay.vue"
import PublicTimeline from "@/views/PublicTimeline.vue" import PublicTimeline from "@/views/PublicTimeline.vue"
import TagTimeline from "@/views/TagTimeline.vue" import TagTimeline from "@/views/TagTimeline.vue"
import SearchResultList from "@/views/SearchResultList.vue" import SearchResultList from "@/views/SearchResultList.vue"
import SubscriptionView from "@/views/Subscription.vue" import SubscriptionPage from "@/views/SubscriptionPage.vue"
import { useCurrentUser } from "@/store/user" import { useCurrentUser } from "@/store/user"
@ -103,7 +103,7 @@ const routes: Array<RouteRecordRaw> = [
{ {
path: "/profile/:profileId/subscription", path: "/profile/:profileId/subscription",
name: "profile-subscription", name: "profile-subscription",
component: SubscriptionView, component: SubscriptionPage,
meta: { }, meta: { },
}, },
{ {

View file

@ -1,352 +0,0 @@
<template>
<div id="main" v-if="profile">
<div class="content subscription">
<h1>
Subscription: @{{ getActorAddress(profile) }}
</h1>
<div class="connect-wallet" v-if="canConnectWallet()">
<button class="btn" @click="connectWallet()">Connect wallet</button>
</div>
<div class="wallet-error" v-if="walletError">
{{ walletError }}
</div>
<div class="info" v-if="subscriptionConfigured !== null">
<template v-if="subscription">
<div>Recipient address: {{ subscription.recipientAddress }}</div>
<div>Token address: {{ subscription.tokenAddress }}</div>
<div>Token symbol: {{ subscription.tokenSymbol }}</div>
<div>Price of one month: {{ subscription.price }}</div>
<template v-if="!isRecipient() && subscriptionState">
<div>Your address: {{ subscriptionState.senderAddress }}</div>
<div>Your balance: {{ subscriptionState.senderBalance }}</div>
<div>Expires at: {{ subscription.getExpirationDate(subscriptionState.senderBalance).toLocaleString() }}</div>
</template>
</template>
<template v-else-if="isCurrentUser()">
Subscription is not configured.
</template>
<template v-else>
Subscription is not available.
</template>
</div>
<div class="setup" v-if="canConfigureSubscription()">
<button class="btn" @click="onConfigureSubscription()">
Set up subscription
</button>
</div>
<div class="payment" v-if="canSubscribe()">
<button class="btn" @click="onMakeSubscriptionPayment()">
Pay for subscription
</button>
</div>
<div class="cancel" v-if="canCancel()">
<button class="btn" @click="onCancelSubscription()">
Cancel subscription
</button>
</div>
<div class="withdraw" v-if="isRecipient()">
<input v-model="subscriberAddress" placeholder="Subscriber address">
<button class="btn" @click="onCheckSubsciptionState()">Check</button>
<button class="btn" v-if="canWithdrawReceived()" @click="onWithdrawReceived()">
Withdraw {{ subscriptionState.recipientBalance }}
</button>
</div>
</div>
<sidebar></sidebar>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue"
import { $, $ref } from "vue/macros"
import { useRoute } from "vue-router"
import {
getProfile,
getVerifiedEthereumAddress,
Profile,
} from "@/api/users"
import {
cancelSubscription,
configureSubscription,
getSubscriptionAuthorization,
getSubscriptionInfo,
getSubscriptionState,
makeSubscriptionPayment,
withdrawReceived,
Subscription,
SubscriptionState,
} from "@/api/subscriptions"
import Sidebar from "@/components/Sidebar.vue"
import { useWallet } from "@/composables/wallet"
import { useInstanceInfo } from "@/store/instance"
import { useCurrentUser } from "@/store/user"
import { ethereumAddressMatch, getWeb3Provider } from "@/utils/ethereum"
const route = useRoute()
const { currentUser, ensureAuthToken } = $(useCurrentUser())
const { instance, getActorAddress } = $(useInstanceInfo())
const { connectWallet: connectEthereumWallet } = useWallet()
let { walletAddress, walletError } = $(useWallet())
let profile = $ref<Profile | null>(null)
let profileEthereumAddress = $ref<string | null>(null)
let subscriptionConfigured = $ref<boolean | null>(null)
let subscription = $ref<Subscription | null>(null)
let subscriptionState = $ref<SubscriptionState | null>(null)
let subscriberAddress = $ref<string | null>(null)
onMounted(async () => {
const { authToken } = $(useCurrentUser())
profile = await getProfile(
authToken,
route.params.profileId as string,
)
profileEthereumAddress = getVerifiedEthereumAddress(profile)
})
function isCurrentUser(): boolean {
if (!currentUser || !profile) {
return false
}
return currentUser.id === profile.id
}
function canConnectWallet(): boolean {
return (
Boolean(instance?.blockchain_id) &&
Boolean(instance?.blockchain_contract_address) &&
// Only profiles with verified address can have subscription
profileEthereumAddress !== null &&
walletAddress === null
)
}
function reset() {
subscriptionConfigured = null
subscription = null
subscriptionState = null
subscriberAddress = null
}
async function connectWallet() {
await connectEthereumWallet(reset)
if (!profileEthereumAddress || !walletAddress) {
return
}
if (isCurrentUser() && !ethereumAddressMatch(walletAddress, profileEthereumAddress)) {
// Recipient must use verified account
walletError = "incorrect wallet address"
return
}
if (!isCurrentUser() && ethereumAddressMatch(walletAddress, profileEthereumAddress)) {
walletError = "incorrect wallet address"
return
}
checkSubscription()
}
async function checkSubscription() {
if (
!profile ||
!profileEthereumAddress ||
!instance ||
!instance.blockchain_contract_address
) {
return
}
const signer = getWeb3Provider().getSigner()
subscription = await getSubscriptionInfo(
instance.blockchain_contract_address,
signer,
profileEthereumAddress,
)
if (subscription !== null) {
subscriptionConfigured = true
} else {
subscriptionConfigured = false
}
const signerAddress = await signer.getAddress()
if (!ethereumAddressMatch(signerAddress, profileEthereumAddress)) {
// Connected wallet is a subscriber
subscriptionState = await getSubscriptionState(
instance.blockchain_contract_address,
signer,
signerAddress,
profileEthereumAddress,
)
}
}
function canConfigureSubscription(): boolean {
return (
isCurrentUser() &&
Boolean(currentUser?.wallet_address) &&
subscriptionConfigured === false
)
}
async function onConfigureSubscription() {
if (
!currentUser ||
!currentUser.wallet_address ||
!isCurrentUser() ||
!instance ||
!instance.blockchain_contract_address
) {
return
}
// Subscription configuration tx can be send from any address
const signer = getWeb3Provider().getSigner()
const authToken = ensureAuthToken()
const signature = await getSubscriptionAuthorization(authToken)
const transaction = await configureSubscription(
instance.blockchain_contract_address,
signer,
currentUser.wallet_address,
signature,
)
await transaction.wait()
subscriptionConfigured = true
subscription = await getSubscriptionInfo(
instance.blockchain_contract_address,
signer,
currentUser.wallet_address,
)
}
function canSubscribe(): boolean {
return !isCurrentUser() && subscriptionConfigured === true
}
async function onMakeSubscriptionPayment() {
if (
!profile ||
!profileEthereumAddress ||
!instance ||
!instance.blockchain_contract_address
) {
return
}
const signer = getWeb3Provider().getSigner()
const transaction = await makeSubscriptionPayment(
instance.blockchain_contract_address,
signer,
profileEthereumAddress,
)
await transaction.wait()
subscriptionState = await getSubscriptionState(
instance.blockchain_contract_address,
signer,
await signer.getAddress(),
profileEthereumAddress,
)
}
function canCancel(): boolean {
return (
!isCurrentUser() &&
subscriptionState !== null &&
!subscriptionState.senderBalance.isZero()
)
}
async function onCancelSubscription() {
if (
!profile ||
!profileEthereumAddress ||
!instance?.blockchain_contract_address
) {
return
}
const signer = getWeb3Provider().getSigner()
const transaction = await cancelSubscription(
instance.blockchain_contract_address,
signer,
profileEthereumAddress,
)
await transaction.wait()
subscriptionState = await getSubscriptionState(
instance.blockchain_contract_address,
signer,
await signer.getAddress(),
profileEthereumAddress,
)
}
function isRecipient(): boolean {
return isCurrentUser() && subscription !== null
}
async function onCheckSubsciptionState() {
if (
!profile ||
!profileEthereumAddress ||
!instance?.blockchain_contract_address ||
!subscriberAddress
) {
return
}
const signer = getWeb3Provider().getSigner()
subscriptionState = await getSubscriptionState(
instance.blockchain_contract_address,
signer,
subscriberAddress,
profileEthereumAddress,
)
}
function canWithdrawReceived(): boolean {
return isRecipient() && subscriptionState !== null
}
async function onWithdrawReceived() {
if (
!profile ||
!instance?.blockchain_contract_address ||
!subscriberAddress
) {
return
}
const signer = getWeb3Provider().getSigner()
await withdrawReceived(
instance.blockchain_contract_address,
signer,
subscriberAddress,
)
subscriptionState = null
}
</script>
<style scoped lang="scss">
@import "../styles/layout";
@import "../styles/mixins";
@import "../styles/theme";
.subscription {
@include block-btn;
background-color: $block-background-color;
border-radius: $block-border-radius;
display: flex;
flex-direction: column;
gap: $block-inner-padding / 2;
margin-bottom: $block-outer-padding;
padding: $block-inner-padding;
h1 {
font-size: 20px;
margin: 0;
}
}
.withdraw {
display: flex;
flex-wrap: wrap;
gap: $block-inner-padding / 2;
input {
border: 1px solid $btn-background-hover-color;
border-radius: $btn-border-radius;
}
}
</style>

View file

@ -0,0 +1,41 @@
<template>
<div id="main" v-if="profile">
<div class="content">
<component
:is="isCurrentUser() ? SubscriptionSetup : Subscription"
:profile="profile"
></component>
</div>
<sidebar></sidebar>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue"
import { $, $ref } from "vue/macros"
import { useRoute } from "vue-router"
import { getProfile, Profile } from "@/api/users"
import Sidebar from "@/components/Sidebar.vue"
import Subscription from "@/components/Subscription.vue"
import SubscriptionSetup from "@/components/SubscriptionSetup.vue"
import { useCurrentUser } from "@/store/user"
const route = useRoute()
const { currentUser, authToken } = $(useCurrentUser())
let profile = $ref<Profile | null>(null)
onMounted(async () => {
profile = await getProfile(
authToken,
route.params.profileId as string,
)
})
function isCurrentUser(): boolean {
if (!currentUser || !profile) {
return false
}
return currentUser.id === profile.id
}
</script>