feat(transactions): UI updates

by @negue
This commit is contained in:
SabreCat
2022-11-15 19:28:36 -06:00
parent 1a5cba57b7
commit 259131ee3f
8 changed files with 350 additions and 86 deletions

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path fill="#DE3F3F" d="M0 5.667 3.333 1h9.334L16 5.667l-8 8.666z"/>
<path fill="#FFF" opacity=".25" d="M4.667 5.533 4 2.333h4zM11.333 5.533l.667-3.2H8z"/>
<path fill="#FFF" opacity=".5" d="M4.667 5.533 8 2.333l3.333 3.2zM1.733 5.533 4 2.333l.667 3.2z"/>
<path fill="#34313A" opacity=".11" d="M14.267 5.533 12 2.333l-.667 3.2zM1.733 5.533h2.934L8 12.4z"/>
<path fill="#FFF" opacity=".5" d="M14.267 5.533h-2.934L8 12.4z"/>
<path fill="#FFF" opacity=".25" d="M4.667 5.533h6.666L8 12.4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 675 B

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path d="M3 12.606v1.778c0 .208.093.408.262.53 1.842 1.347 6.923 1.347 8.766 0a.655.655 0 0 0 .26-.53v-1.778c0-1.621-.831-3.177-2.091-4.104a.666.666 0 0 1 0-1.08c1.26-.927 2.092-2.483 2.092-4.105V1.54a.652.652 0 0 0-.261-.53c-1.843-1.346-6.924-1.346-8.766 0A.65.65 0 0 0 3 1.54v1.777c0 1.622.832 3.178 2.092 4.105.368.27.368.81 0 1.08C3.832 9.429 3 10.985 3 12.606" fill="#F19595"/>
<path d="M7.644 1.327c1.51 0 2.684.274 3.318.587v1.403c0 1.169-.594 2.332-1.551 3.036a2.006 2.006 0 0 0-.818 1.609c0 .63.305 1.232.817 1.608.958.705 1.552 1.868 1.552 3.036v1.404c-.634.313-1.809.587-3.318.587-1.508 0-2.683-.274-3.317-.587v-1.404c0-1.168.594-2.331 1.551-3.035.513-.377.817-.978.817-1.609 0-.63-.304-1.232-.816-1.609-.958-.704-1.552-1.867-1.552-3.036V1.914c.634-.313 1.809-.587 3.317-.587" fill-opacity=".9" fill="#FFF"/>
<path d="M7.797 2.324c-1.132 0-2.331.105-2.343.385-.01.226-.005.664.914 1.13.893.453 1.06 1.282 1.546 1.282.564 0 .596-.477 1.284-.95.71-.488.823-1.148.815-1.408-.011-.363-1.084-.439-2.216-.439" fill="#DE3F3F"/>
<path d="M9.198 4.17c.71-.487.823-1.146.815-1.407-.009-.288-.684-.395-1.526-.427.236.12.543.377.467.88-.078.525-.904 1.105-.77 1.568.025.09.069.162.124.221.247-.17.408-.502.89-.835" fill="#B01515"/>
<path d="M7.644 9.17c-.344 0-.433.628-.933 1.018-.613.478-1.196 1.067-1.356 1.914-.131.698-.012.785.148.834.16.049 1.386.257 2.588 0 1.203-.258 1.87-.737 1.755-1.227-.111-.466-.448-.865-1.068-1.325-.593-.44-.79-1.214-1.134-1.214" fill="#DE3F3F"/>
<path d="M5.503 12.936c.16.05 1.386.257 2.588 0 .956-.205 1.574-.55 1.729-.929a.096.096 0 0 0-.005-.023c-.067-.256-1.073-.41-2.325-.207-1.192.192-2.158.586-2.153 1.03.037.08.097.108.166.129" fill="#B01515"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -49,6 +49,7 @@
<transactions <transactions
:hero="hero" :hero="hero"
:reset-counter="resetCounter"
/> />
<contributor-details <contributor-details

View File

@@ -30,6 +30,10 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
resetCounter: {
type: Number,
required: true,
},
}, },
data () { data () {
return { return {
@@ -38,6 +42,14 @@ export default {
hourglassTransactions: [], hourglassTransactions: [],
}; };
}, },
watch: {
resetCounter () {
if (this.expand) {
this.expand = !this.expand;
this.toggleTransactionsOpen();
}
},
},
methods: { methods: {
async toggleTransactionsOpen () { async toggleTransactionsOpen () {
this.expand = !this.expand; this.expand = !this.expand;

View File

@@ -1,105 +1,308 @@
<template> <template>
<div class="row"> <div>
<div class="col-6"> <div class="clearfix">
<h1>{{ $t('gemTransactions') }}</h1> <div class="mb-4 float-left">
<span v-if="gemTransactions.length === 0">{{ $t('noGemTransactions') }}</span> <button
<table class="table"> class="page-header btn-flat tab-button textCondensed"
<tr :class="{'active': selectedTab === 'gems'}"
v-for="entry in gemTransactions" @click="selectTab('gems')"
:key="entry.createdAt"
> >
<td> {{ $t('gems') }}
<span </button>
v-b-tooltip.hover="entry.createdAt" <button
>{{ entry.createdAt | timeAgo }}</span> class="page-header btn-flat tab-button textCondensed"
</td> :class="{'active': selectedTab === 'hourglass'}"
<td> @click="selectTab('hourglass')"
<span
class="svg-icon inline icon-24"
aria-hidden="true"
v-html="icons.gem"
></span>
<span
class="amount gems"
:class="entry.amount < 0 ? 'deducted' : 'added'"
>{{ entry.amount * 4 }}</span>
</td>
<td>
<span>{{ transactionTypeText(entry.transactionType) }}</span>
</td>
<td>
<span v-html="entryReferenceText(entry)"></span>
</td>
</tr>
</table>
</div>
<div class="col-6">
<h1>{{ $t('hourglassTransactions') }}</h1>
<span v-if="hourglassTransactions.length === 0">{{ $t('noHourglassTransactions') }}</span>
<table class="table">
<tr
v-for="entry in hourglassTransactions"
:key="entry.createdAt"
> >
<td> {{ $t('mysticHourglass', { amount: ''}) }}
<span </button>
v-b-tooltip.hover="entry.createdAt" </div>
>{{ entry.createdAt | timeAgo }}</span>
</td>
<td>
<span
class="svg-icon inline icon-24"
aria-hidden="true"
v-html="icons.hourglass"
></span>
<span
class="amount hourglasses"
:class="entry.amount < 0 ? 'deducted' : 'added'"
>{{ entry.amount }}</span>
</td>
<td>
<span>{{ transactionTypeText(entry.transactionType) }}</span>
</td>
<td>
<span v-html="entryReferenceText(entry)"></span>
</td>
</tr>
</table>
</div> </div>
<div class="row">
<div class="col-12" v-if="selectedTab === 'gems'">
<span v-if="gemTransactions.length === 0">
{{ $t('noGemTransactions') }}
</span>
<table class="table">
<tr>
<th v-once class="timestamp-column">
{{ $t('timestamp')}}
</th>
<th v-once class="amount-column">
{{ $t('amount')}}
</th>
<th v-once class="action-column">
{{ $t('action')}}
</th>
<th v-once class="note-column">
{{ $t('note')}}
</th>
</tr>
<tr
v-for="entry in gemTransactions"
:key="entry.createdAt"
>
<td>
<span
v-b-tooltip.hover="entry.createdAt"
>{{ entry.createdAt | timeAgo }}</span>
</td>
<td>
<div class="amount-with-icon" :id="entry.id">
<span
class="svg-icon inline icon-16 my-1"
aria-hidden="true"
v-html="entry.amount < 0 ? icons.gemRed : icons.gem"
></span>
<span
class="amount gems"
:class="entry.amount | addedDeducted"
>{{ entry.amount * 4 }}</span>
</div>
<b-popover
v-if="typeof entry.currentAmount !== 'undefined'"
ref="popover"
:target="entry.id"
triggers="hover focus click"
placement="bottom"
>
<div class="remaining-amount-popover-content">
{{ $t('remainingBalance') }}:
<span
class="svg-icon inline icon-16 ml-1"
aria-hidden="true"
v-html="icons.gem"
></span>
<span
class="amount gems"
>{{ entry.currentAmount * 4 }}</span>
</div>
</b-popover>
</td>
<td class="entry-action">
<span v-html="transactionTypeText(entry.transactionType)"></span>
</td>
<td>
<span v-if="transactionTypes.gifted.includes(entry.transactionType)">
<router-link
class="user-link"
:to="{'name': 'userProfile', 'params': {'userId': entry.reference}}"
>
@{{ entry.referenceText }}
</router-link>
</span>
<span v-else-if="transactionTypes.challenges.includes(entry.transactionType)">
<router-link
class="challenge-link"
:to="{ name: 'challenge', params: { challengeId: entry.reference } }">
<span
v-markdown="entry.referenceText"
></span>
</router-link>
</span>
<span v-else v-html="entryReferenceText(entry)"></span>
<span v-if="entry.reference">
({{entry.reference}})
</span>
</td>
</tr>
</table>
</div>
<div class="col-12" v-if="selectedTab === 'hourglass'">
<span v-if="hourglassTransactions.length === 0">
{{ $t('noHourglassTransactions') }}
</span>
<table class="table">
<tr>
<th v-once class="timestamp-column">
{{ $t('timestamp')}}
</th>
<th v-once class="amount-column">
{{ $t('amount')}}
</th>
<th v-once class="action-column">
{{ $t('action')}}
</th>
<th v-once class="note-column">
{{ $t('note')}}
</th>
</tr>
<tr
v-for="entry in hourglassTransactions"
:key="entry.createdAt"
>
<td>
<span
v-b-tooltip.hover="entry.createdAt"
>{{ entry.createdAt | timeAgo }}</span>
</td>
<td>
<div class="amount-with-icon" :id="entry.id">
<span
class="svg-icon inline icon-16 my-1"
aria-hidden="true"
v-html="entry.amount < 0 ? icons.hourglassRed : icons.hourglass"
></span>
<span
class="amount hourglasses"
:class="entry.amount | addedDeducted"
>{{ entry.amount }}</span>
</div>
<b-popover
v-if="typeof entry.currentAmount !== 'undefined'"
ref="popover"
:target="entry.id"
triggers="hover focus click"
placement="bottom"
>
<div class="remaining-amount-popover-content">
{{ $t('remainingBalance') }}:
<span
class="svg-icon inline icon-16 ml-1"
aria-hidden="true"
v-html="icons.hourglass"
></span>
<span
class="amount gems"
>{{ entry.currentAmount }}</span>
</div>
</b-popover>
</td>
<td class="entry-action">
<span v-html="transactionTypeText(entry.transactionType)"></span>
</td>
<td>
<span v-html="entryReferenceText(entry)"></span>
</td>
</tr>
</table>
</div>
</div>
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
@import '~@/assets/scss/colors.scss'; @import '~@/assets/scss/colors.scss';
.page-header.btn-flat {
background: transparent;
}
.tab-button {
height: 2rem;
font-size: 24px;
font-weight: bold;
font-stretch: condensed;
line-height: 1.33;
letter-spacing: normal;
color: $gray-10;
margin-right: 1.125rem;
padding-left: 0;
padding-right: 0;
padding-bottom: 2.5rem;
&.active, &:hover {
color: $purple-300;
box-shadow: 0px -0.25rem 0px $purple-300 inset;
outline: none;
}
}
.amount-column {
white-space: nowrap;
}
.svg-icon { .svg-icon {
vertical-align: middle; vertical-align: middle;
} }
.amount { .amount {
font-weight: bold; font-weight: bold;
font-size: 1.1rem;
margin-left: 4px; margin-left: 4px;
} }
.added::before { .added::before {
content: "+"; content: "+";
} }
.gems { .gems {
color: $gems-color; color: $green-10;
&.deducted { &.deducted {
color: $red-10; color: $maroon-50;
} }
} }
.hourglasses { .hourglasses {
font-weight: bold; font-weight: bold;
color: $hourglass-color; color: $green-10;
&.deducted { &.deducted {
color: $red-10; color: $maroon-50;
}
}
.amount-with-icon {
display: inline-flex;
}
.remaining-amount-popover-content {
display: flex;
font-size: 12px;
line-height: 1.33;
color: $white;
}
table {
line-height: 1.71;
color: $gray-50;
}
th {
border-top: 0 !important;
padding: 0.25rem 0.5rem !important;
font-weight: bold;
line-height: 1.71;
color: $gray-50;
}
td {
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
line-height: 1.71;
color: $gray-50;
}
th, td {
padding-top: 0.35rem !important;
padding-bottom: 0.35rem !important;
}
.timestamp-column, .action-column {
width: 20%;
}
.amount-column {
width: 10%;
}
.note-column {
width: 50%;
}
.challenge-link, .user-link {
color: $blue-10 !important;
}
.entry-action {
b {
text-transform: uppercase;
} }
} }
</style> </style>
@@ -107,9 +310,15 @@
<script> <script>
import moment from 'moment'; import moment from 'moment';
import svgGem from '@/assets/svg/gem.svg'; import svgGem from '@/assets/svg/gem.svg';
import svgGemRed from '@/assets/svg/gem-red.svg';
import svgHourglass from '@/assets/svg/hourglass.svg'; import svgHourglass from '@/assets/svg/hourglass.svg';
import svgHourglassRed from '@/assets/svg/hourglass-red.svg';
import markdownDirective from '@/directives/markdown';
export default { export default {
directives: {
markdown: markdownDirective,
},
filters: { filters: {
timeAgo (value) { timeAgo (value) {
return moment(value).fromNow(); return moment(value).fromNow();
@@ -118,6 +327,13 @@ export default {
// @TODO: Vue doesn't support this so we cant user preference // @TODO: Vue doesn't support this so we cant user preference
return moment(value).toDate().toString(); return moment(value).toDate().toString();
}, },
addedDeducted (amount) {
if (amount === 0) {
return '';
}
return amount < 0 ? 'deducted' : 'added';
},
}, },
props: { props: {
gemTransactions: { gemTransactions: {
@@ -133,11 +349,21 @@ export default {
return { return {
icons: Object.freeze({ icons: Object.freeze({
gem: svgGem, gem: svgGem,
gemRed: svgGemRed,
hourglass: svgHourglass, hourglass: svgHourglass,
hourglassRed: svgHourglassRed,
}),
selectedTab: 'gems',
transactionTypes: Object.freeze({
gifted: ['gift_send', 'gift_receive'],
challenges: ['create_challenge', 'create_bank_challenge'],
}), }),
}; };
}, },
methods: { methods: {
selectTab (type) {
this.selectedTab = type;
},
entryReferenceText (entry) { entryReferenceText (entry) {
if (entry.reference === undefined && entry.referenceText === undefined) { if (entry.reference === undefined && entry.referenceText === undefined) {
return ''; return '';

View File

@@ -192,27 +192,32 @@
"everywhere": "Everywhere", "everywhere": "Everywhere",
"onlyPrivateSpaces": "Only in private spaces", "onlyPrivateSpaces": "Only in private spaces",
"bannedSlurUsedInProfile": "Your Display Name or About text contained a slur, and your chat privileges have been revoked.", "bannedSlurUsedInProfile": "Your Display Name or About text contained a slur, and your chat privileges have been revoked.",
"timestamp": "Timestamp",
"amount": "Amount",
"action": "Action",
"note": "Note",
"remainingBalance": "Remaining Balance",
"transactions": "Transactions", "transactions": "Transactions",
"gemTransactions": "Gem Transactions",
"hourglassTransactions": "Hourglass Transactions", "hourglassTransactions": "Hourglass Transactions",
"noGemTransactions": "You don't have any gem transactions yet.", "noGemTransactions": "You don't have any gem transactions yet.",
"noHourglassTransactions": "You don't have any hourglass transactions yet.", "noHourglassTransactions": "You don't have any hourglass transactions yet.",
"transaction_debug": "Debug Action", "transaction_debug": "Debug Action",
"transaction_buy_money": "Bought with money", "transaction_buy_money": "<b>Bought</b> with money",
"transaction_buy_gold": "Bought with gold", "transaction_buy_gold": "<b>Bought</b> with gold",
"transaction_contribution": "Through contribution", "transaction_contribution": "<b>Tier</b> change",
"transaction_spend": "Spent on", "transaction_spend": "<b>Spent</b> on",
"transaction_gift_send": "Gifted to", "transaction_gift_send": "<b>Gifted</b> to",
"transaction_gift_receive": "Received from", "transaction_gift_receive": "<b>Received</b> from",
"transaction_create_challenge": "Created challenge", "transaction_create_challenge": "<b>Created</b> challenge",
"transaction_create_bank_challenge": "<b>Created</b> bank challenge",
"transaction_create_bank_challenge": "Created bank challenge", "transaction_create_bank_challenge": "Created bank challenge",
"transaction_create_guild": "Created guild", "transaction_create_guild": "<b>Created</b> guild",
"transaction_change_class": "Changed class", "transaction_change_class": "<b>Class</b> change",
"transaction_rebirth": "Used Orb of Rebirth", "transaction_rebirth": "Used Orb of Rebirth",
"transaction_release_pets": "Released pets", "transaction_release_pets": "Released pets",
"transaction_release_mounts": "Released mounts", "transaction_release_mounts": "Released mounts",
"transaction_reroll": "Used Fortify Potion", "transaction_reroll": "Used Fortify Potion",
"transaction_subscription_perks": "From subscription perk", "transaction_subscription_perks": "<b>Subscription</b> perk",
"transaction_admin_update_balance": "Admin given", "transaction_admin_update_balance": "<b>Admin</b> given",
"transaction_admin_update_hourglasses": "Admin updated" "transaction_admin_update_hourglasses": "<b>Admin</b> updated"
} }

View File

@@ -267,7 +267,7 @@ api.updateHero = {
const hero = await User.findById(heroId).exec(); const hero = await User.findById(heroId).exec();
if (!hero) throw new NotFound(res.t('userWithIDNotFound', { userId: heroId })); if (!hero) throw new NotFound(res.t('userWithIDNotFound', { userId: heroId }));
if (updateData.balance) { if (updateData.balance && updateData.balance !== hero.balance) {
await hero.updateBalance(updateData.balance - hero.balance, 'admin_update_balance', '', 'Given by Habitica staff'); await hero.updateBalance(updateData.balance - hero.balance, 'admin_update_balance', '', 'Given by Habitica staff');
hero.balance = updateData.balance; hero.balance = updateData.balance;

View File

@@ -5,7 +5,7 @@ import baseModel from '../libs/baseModel';
const { Schema } = mongoose; const { Schema } = mongoose;
export const currencies = ['gems', 'hourglasses']; export const currencies = ['gems', 'hourglasses'];
export const transactionTypes = ['buy_money', 'buy_gold', 'spend', 'gift_send', 'gift_receive', 'debug', 'create_challenge', 'create_bank_challenge', 'create_guild', 'change_class', 'rebirth', 'release_pets', 'release_mounts', 'reroll', 'contribution', 'subscription_perks', 'admin_update_balance', 'admin_update_hourglasses']; export const transactionTypes = ['buy_money', 'buy_gold', 'spend', 'gift_send', 'gifted_with_money', 'gift_receive', 'debug', 'create_challenge', 'create_bank_challenge', 'create_guild', 'change_class', 'rebirth', 'release_pets', 'release_mounts', 'reroll', 'contribution', 'subscription_perks', 'admin_update_balance', 'admin_update_hourglasses'];
export const schema = new Schema({ export const schema = new Schema({
currency: { $type: String, enum: currencies, required: true }, currency: { $type: String, enum: currencies, required: true },