Redesign subscription page
This commit is contained in:
parent
b865eaff10
commit
4110df590d
3 changed files with 192 additions and 28 deletions
1
src/assets/feather/arrow-right.svg
Normal file
1
src/assets/feather/arrow-right.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
|
After Width: | Height: | Size: 316 B |
|
@ -19,6 +19,8 @@ export default class Avatar extends Vue {
|
|||
get avatarUrl(): string {
|
||||
if (this.profile.avatar) {
|
||||
return this.profile.avatar
|
||||
} else if (this.profile.acct === "") {
|
||||
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAC8ElEQVR4nO3Y3UtTcRzHcf+nPdwccQ4GmeBymZDXYUgP2BTbdOIfURmFMibOdJPZ00WRBq1wq3RBBlkIS106N92m9gd8vWrwW5B69fuccz4X78svfH/ntXMOO01/jivCcGrSvQAjCHQEAYsgYBEELIKARRCwCAIWQcAiCFgEAYsgYBEELIKARRCwCAIWQcAiCFgEAYsgYBEELIKARRCwCAIWQcAiCFgEAYsgYBEELIKARRCwCAKWKUGKu9sSn47J4GBQ/B0d4vG0iNvtFm9rqwQCnRK6OySJxBPZL+9o39XSIIe1skxMPJLm5mZxOByn5vG0yEx8SvvelgSpVvbk1s0bZ4JoLDISluOjA+1nsBRIZCSsXGSn0ynBYL8spJLyJfdJ1r+vyepKVubmZqS399o/KA/H72s/g2VA0u+WlIvr9XpleTn935mFVFJcLld9xjAM2drc0H4WS4A0Pqoyp2D8bfzBPWXODO8TeJC94rbyS+/vv33m2eLutrjd7vrscDik/TymB3mffqv8ypPJ2XPNX/L767N9fde1n8f0IEeH+1Io5OXH+jdZ+ZyR3Z2tc813dV0mCEq1akkMw6iDjI2Nat/J1iDz87PK4+75s5T2nWwLUijkxefz1THa2i5IrVrSvpctQbY2N6S7+4pydyy+eaV9L1uCZLMfpL39ooIxFYtq38uWIJOTj5X/HYZhyMsXT7XvZTuQw1pZQqEh5a4IBDpl7WtO+262BGn88DgwcEfKpd/a97IlyNLiawVjNDJsmk/tlgTp6bmqPKYqB0XtO9kW5Ff+p3J3xKdj2neyNUhmOa2A5FY/at/J1iBWjCBgEQQsU4NEoxPKOySVSmjfiSAEwYkgYBGEEcRuEQQsgoBFELAIAhZBwCIIWAQBiyBgEQQsgoBFELAIAhZBwCIIWAQBiyBgEQQsgoBFELAIAhZBwCIIWAQBiyBgEQQsgoBFELAIAhZBwCIIWAQBiyBgEQQsgoBFELAIAhZBwDoBOz/dnwXMOO8AAAAASUVORK5CYII="
|
||||
} else {
|
||||
return makeBlockie(this.profile.acct)
|
||||
}
|
||||
|
|
|
@ -1,38 +1,69 @@
|
|||
<template>
|
||||
<h1>Subscription</h1>
|
||||
<div class="subscription">
|
||||
<h1>Subscription</h1>
|
||||
<div class="participants">
|
||||
<component
|
||||
class="profile-card"
|
||||
:is="sender.id ? 'router-link' : 'div'"
|
||||
:to="{ name: 'profile', params: { profileId: sender.id }}"
|
||||
>
|
||||
<avatar :profile="sender"></avatar>
|
||||
<div class="display-name">{{ sender.getDisplayName() }}</div>
|
||||
<div class="wallet-address">{{ senderEthereumAddress || '?' }}</div>
|
||||
</component>
|
||||
<div class="separator">
|
||||
<img :src="require('@/assets/feather/arrow-right.svg')">
|
||||
</div>
|
||||
<router-link class="profile-card" :to="{ name: 'profile', params: { profileId: recipient.id }}">
|
||||
<avatar :profile="recipient"></avatar>
|
||||
<div class="display-name">{{ recipient.getDisplayName() }}</div>
|
||||
<div class="wallet-address">{{ recipientEthereumAddress }}</div>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="connect-wallet" v-if="canConnectWallet()">
|
||||
<button class="btn" @click="connectWallet()">Connect wallet</button>
|
||||
<button class="btn primary" @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.pricePerMonth.round(2) }}</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>
|
||||
<div class="price">
|
||||
{{ subscription.pricePerMonth }} {{ subscription.tokenSymbol }}
|
||||
<span class="price-subtext">per month</span>
|
||||
</div>
|
||||
<div class="status">
|
||||
<template v-if="subscriptionState && !subscriptionState.senderBalance.isZero()">
|
||||
<div>Your balance {{ subscriptionState.senderBalance }} {{ subscription.tokenSymbol }}</div>
|
||||
<div>Subscription expires {{ subscription.getExpirationDate(subscriptionState.senderBalance).toLocaleString() }}</div>
|
||||
</template>
|
||||
<template v-else>You are not subscribed yet</template>
|
||||
</div>
|
||||
</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>
|
||||
<form class="payment" v-if="canSubscribe()">
|
||||
<div class="input-group">
|
||||
<label for="duration">Duration</label>
|
||||
<input type="number" id="duration" v-model="paymentDuration" min="1">
|
||||
<span>months</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Amount</label>
|
||||
<span>{{ getPaymentAmount() }} {{ subscription.tokenSymbol }}</span>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button type="submit" class="btn primary" @click.prevent="onMakeSubscriptionPayment()">
|
||||
<template v-if="!subscriptionState || subscriptionState.senderBalance.isZero()">Pay</template>
|
||||
<template v-else>Extend</template>
|
||||
</button>
|
||||
<button v-if="canCancel()" class="btn secondary" @click.prevent="onCancelSubscription()">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -49,8 +80,10 @@ import {
|
|||
Subscription,
|
||||
SubscriptionState,
|
||||
} from "@/api/subscriptions"
|
||||
import Avatar from "@/components/Avatar.vue"
|
||||
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 */
|
||||
|
@ -58,14 +91,34 @@ const props = defineProps<{
|
|||
profile: Profile,
|
||||
}>()
|
||||
|
||||
const guest: Profile = {
|
||||
id: "",
|
||||
username: "",
|
||||
acct: "",
|
||||
url: "",
|
||||
display_name: "You",
|
||||
note: null,
|
||||
avatar: null,
|
||||
header: null,
|
||||
identity_proofs: [],
|
||||
fields: [],
|
||||
followers_count: 0,
|
||||
following_count: 0,
|
||||
statuses_count: 0,
|
||||
}
|
||||
|
||||
const { currentUser } = $(useCurrentUser())
|
||||
const { instance } = $(useInstanceInfo())
|
||||
const { connectWallet: connectEthereumWallet } = useWallet()
|
||||
const recipient = new ProfileWrapper(props.profile)
|
||||
const recipientEthereumAddress = recipient.getVerifiedEthereumAddress()
|
||||
const sender = $ref<ProfileWrapper>(new ProfileWrapper(currentUser || guest))
|
||||
let senderEthereumAddress = $ref<string | null>(sender.getVerifiedEthereumAddress())
|
||||
let { walletAddress, walletError } = $(useWallet())
|
||||
let subscriptionConfigured = $ref<boolean | null>(null)
|
||||
let subscription = $ref<Subscription | null>(null)
|
||||
let subscriptionState = $ref<SubscriptionState | null>(null)
|
||||
const paymentDuration = $ref<number>(1)
|
||||
|
||||
onMounted(() => {
|
||||
if (walletAddress && !walletError) {
|
||||
|
@ -111,6 +164,7 @@ async function checkSubscription() {
|
|||
walletError = "incorrect wallet address"
|
||||
return
|
||||
}
|
||||
senderEthereumAddress = walletAddress.toLowerCase()
|
||||
const signer = getWeb3Provider().getSigner()
|
||||
subscription = await getSubscriptionInfo(
|
||||
instance.blockchain_contract_address,
|
||||
|
@ -130,6 +184,13 @@ async function checkSubscription() {
|
|||
)
|
||||
}
|
||||
|
||||
function getPaymentAmount(): number {
|
||||
if (!subscription) {
|
||||
return 0
|
||||
}
|
||||
return subscription.pricePerMonth.toUnsafeFloat() * paymentDuration
|
||||
}
|
||||
|
||||
function canSubscribe(): boolean {
|
||||
return subscriptionConfigured === true
|
||||
}
|
||||
|
@ -144,7 +205,7 @@ async function onMakeSubscriptionPayment() {
|
|||
return
|
||||
}
|
||||
const signer = getWeb3Provider().getSigner()
|
||||
const amount = subscription.pricePerMonthInt
|
||||
const amount = subscription.pricePerMonthInt.mul(paymentDuration)
|
||||
const transaction = await makeSubscriptionPayment(
|
||||
instance.blockchain_contract_address,
|
||||
signer,
|
||||
|
@ -196,20 +257,120 @@ async function onCancelSubscription() {
|
|||
@import "../styles/mixins";
|
||||
@import "../styles/theme";
|
||||
|
||||
.subscription {
|
||||
@include block-btn;
|
||||
.btn.primary {
|
||||
background-color: $btn-background-hover-color;
|
||||
color: $btn-text-hover-color;
|
||||
|
||||
&:hover {
|
||||
background-color: $block-background-color;
|
||||
color: $btn-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.btn.secondary {
|
||||
&:hover {
|
||||
background-color: $block-background-color;
|
||||
color: $btn-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.subscription {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $block-outer-padding;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.participants {
|
||||
$avatar-size: 60px;
|
||||
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: $block-inner-padding;
|
||||
|
||||
.profile-card {
|
||||
background-color: $block-background-color;
|
||||
border-radius: $block-border-radius;
|
||||
display: flex;
|
||||
flex-basis: 50%;
|
||||
flex-direction: column;
|
||||
gap: $block-inner-padding / 2;
|
||||
min-width: 0;
|
||||
padding: $block-inner-padding;
|
||||
}
|
||||
|
||||
.separator img {
|
||||
height: $icon-size;
|
||||
min-width: $icon-size;
|
||||
object-fit: contain;
|
||||
width: $icon-size;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: $avatar-size;
|
||||
margin: 0 auto;
|
||||
width: $avatar-size;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.wallet-address {
|
||||
font-family: monospace;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
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;
|
||||
.price {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.price-subtext {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: $secondary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.payment {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $block-inner-padding;
|
||||
|
||||
.input-group {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
gap: $input-padding;
|
||||
justify-content: center;
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: $block-inner-padding;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue