Browse Source

Change some data types, introduce infrastructure to send payments to contributors

master
Jared 1 year ago
parent
commit
6f14711fe4
9 changed files with 6992 additions and 6664 deletions
  1. +78
    -9
      package-lock.json
  2. +1
    -0
      package.json
  3. +8
    -0
      src/api/composed.ts
  4. +10
    -1
      src/api/types.ts
  5. +10
    -3
      src/lib/helpers.ts
  6. +11
    -16
      src/views/AddFundView.vue
  7. +3
    -8
      src/views/AdminDashboardView.vue
  8. +161
    -85
      src/views/FundView.vue
  9. +6710
    -6542
      yarn.lock

+ 78
- 9
package-lock.json View File

@@ -12,10 +12,14 @@
"@vueuse/core": "^9.5.0",
"bulma": "^0.9.4",
"core-js": "^3.8.3",
"decimal.js": "^10.4.3",
"jenesius-vue-modal": "^1.8.2",
"jwt-decode": "^3.1.2",
"luxon": "^3.1.0",
"vue": "^3.2.13",
"vue-draggable-next": "^2.1.1",
"vue-router": "^4.0.3",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.0"
},
"devDependencies": {
@@ -5609,11 +5613,9 @@
}
},
"node_modules/decimal.js": {
"version": "10.4.2",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz",
"integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==",
"dev": true,
"license": "MIT"
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
},
"node_modules/decode-uri-component": {
"version": "0.2.0",
@@ -8922,6 +8924,11 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -12671,6 +12678,12 @@
"websocket-driver": "^0.7.4"
}
},
"node_modules/sortablejs": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==",
"peer": true
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -13847,6 +13860,15 @@
}
}
},
"node_modules/vue-draggable-next": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/vue-draggable-next/-/vue-draggable-next-2.1.1.tgz",
"integrity": "sha512-f5lmA7t6LMaL4viR7dU30zzvqJzaKQs0ymL0Jy9UDT9uiZ2tXF3MzPzEvpTH2UODXZJkT+SnjeV1fXHMsgXLYA==",
"peerDependencies": {
"sortablejs": "^1.14.0",
"vue": "^3.2.2"
}
},
"node_modules/vue-eslint-parser": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
@@ -14002,6 +14024,22 @@
"dev": true,
"license": "MIT"
},
"node_modules/vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"dependencies": {
"sortablejs": "1.14.0"
},
"peerDependencies": {
"vue": "^3.0.1"
}
},
"node_modules/vuedraggable/node_modules/sortablejs": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
},
"node_modules/vuex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
@@ -18648,10 +18686,9 @@
"dev": true
},
"decimal.js": {
"version": "10.4.2",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz",
"integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==",
"dev": true
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
},
"decode-uri-component": {
"version": "0.2.0",
@@ -20934,6 +20971,11 @@
"universalify": "^2.0.0"
}
},
"jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -23530,6 +23572,12 @@
"websocket-driver": "^0.7.4"
}
},
"sortablejs": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==",
"peer": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -24339,6 +24387,12 @@
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"requires": {}
},
"vue-draggable-next": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/vue-draggable-next/-/vue-draggable-next-2.1.1.tgz",
"integrity": "sha512-f5lmA7t6LMaL4viR7dU30zzvqJzaKQs0ymL0Jy9UDT9uiZ2tXF3MzPzEvpTH2UODXZJkT+SnjeV1fXHMsgXLYA==",
"requires": {}
},
"vue-eslint-parser": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
@@ -24443,6 +24497,21 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"requires": {
"sortablejs": "1.14.0"
},
"dependencies": {
"sortablejs": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
}
}
},
"vuex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",


+ 1
- 0
package.json View File

@@ -13,6 +13,7 @@
"@vueuse/core": "^9.5.0",
"bulma": "^0.9.4",
"core-js": "^3.8.3",
"decimal.js": "^10.4.3",
"jenesius-vue-modal": "^1.8.2",
"jwt-decode": "^3.1.2",
"luxon": "^3.1.0",


+ 8
- 0
src/api/composed.ts View File

@@ -23,6 +23,7 @@ import {
NearlyCompleteFundsRequest,
NearlyCompleteFundsResponse,
QueueMember,
SubmitRewardFundRequest,
SuccessResponse,
} from '@/api/types';

@@ -49,6 +50,7 @@ export const createQueue = (name: string) => controller.post<CreateQueueResponse
export const createRewardFund = (
asset: string,
fundWallet: string,
fundSecret: string,
sellingWallet: string,
issuerWallet: string,
memo: string,
@@ -59,6 +61,7 @@ export const createRewardFund = (
) => controller.post<SuccessResponse, CreateRewardFundRequest>('CreateRewardFund', {
asset,
fundWallet,
fundSecret,
sellingWallet,
issuerWallet,
memo,
@@ -112,3 +115,8 @@ export const reorderQueue = (queueID: number, fundOrders: QueueMember[]) => cont
queueID,
fundOrders,
});

export const submitRewardFund = (fundID: number, submit: boolean) => controller.post<SuccessResponse, SubmitRewardFundRequest>('SubmitRewardFund', {
fundID,
submit,
});

+ 10
- 1
src/api/types.ts View File

@@ -1,3 +1,6 @@
// eslint-disable-next-line no-shadow
import Decimal from 'decimal.js';

// eslint-disable-next-line no-shadow
export enum Privileges {
None = -1,
@@ -8,7 +11,7 @@ export enum Privileges {

export interface Contribution {
createdAt: string;
amount: number;
amount: Decimal;
rewardFundID: number;
transactionID: string;
wallet: string;
@@ -71,6 +74,7 @@ export interface Bonus {
export interface CreateRewardFundRequest {
asset: string;
fundWallet: string;
fundSecret: string;
sellingWallet: string;
issuerWallet: string;
memo: string;
@@ -188,3 +192,8 @@ export interface EditQueueRequest {
queueID: number;
fundOrders: QueueMember[];
}

export interface SubmitRewardFundRequest {
fundID: number;
submit: boolean;
}

+ 10
- 3
src/lib/helpers.ts View File

@@ -1,4 +1,11 @@
export const truncateWallet: (wallet: string, preDigits: number, postDigits: number | undefined) => string = (wallet: string, preDigits: number, postDigits = preDigits) => `${wallet.slice(0, preDigits)}...${wallet.slice(-(postDigits + 1), -1)}`;
export const truncateWallet: (
wallet: string,
preDigits: number,
postDigits: number | undefined,
) => string = (wallet: string, preDigits: number, postDigits = preDigits) => {
const [med, mtd] = [Math.max(preDigits, 3), Math.max(postDigits, 3)];
return `${wallet.slice(0, med)}...${wallet.slice(-(mtd + 1), -1)}`;
};
export const isNumber = (s: string) => /^[0-9]+$/.test(s);

export const sanitize = (s: string) => {
@@ -7,8 +14,8 @@ export const sanitize = (s: string) => {
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'\'': '&#x27;',
'/': '&#x2F;',
} as {[key: string]: string};
} as { [key: string]: string };
return s.replace(/[&<>"'/]/ig, (match) => chars[match]);
};

+ 11
- 16
src/views/AddFundView.vue View File

@@ -6,12 +6,7 @@
<div class="title is-5 has-text-white-ter">Post</div>
<div class="control my-2">
<input class="input is-normal has-background-white has-text-black" type="text"
placeholder="Title" aria-label="Title" v-model="title">
</div>
<div class="control my-2">
<textarea class="textarea is-normal has-background-white has-text-black"
placeholder="Description" aria-label="Description" v-model="description">
</textarea>
placeholder="Telegram Link" aria-label="Telegram Link" v-model="telegramLink">
</div>
</section>
<section class="section px-0 py-4">
@@ -20,6 +15,10 @@
<input class="input is-normal has-background-white has-text-black" type="text"
placeholder="Fund Wallet" aria-label="Fund Wallet" v-model="fundWallet">
</div>
<div class="control my-2">
<input class="input is-normal has-background-white has-text-black" type="text"
placeholder="Fund Secret" aria-label="Fund Secret" v-model="fundSecret">
</div>
<div class="control my-2">
<input class="input is-normal has-background-white has-text-black" type="text"
placeholder="Selling Wallet" aria-label="Selling Wallet" v-model="sellWallet">
@@ -68,7 +67,6 @@

<script setup lang="ts">

import SignetRequestController from '@/api/requests';
import {
Bonus,
CreateQueueResponse,
@@ -87,13 +85,10 @@ const router = useRouter();

document.title = 'Beignet - Add Fund';

const controller = new SignetRequestController();

const title = ref('');
const description = ref('');
const telegramLink = ref('');
const fundWallet = ref('');
const sellWallet = ref('');
// const fundSecret = ref('');
const fundSecret = ref('');
const issuerWallet = ref('');
const asset = ref('');
const memo = ref('');
@@ -129,12 +124,12 @@ const doCreateQueue = async () => {
const constructFund = () => ({
asset: asset.value,
fundWallet: sanitize(fundWallet.value),
fundSecret: sanitize(fundSecret.value),
sellingWallet: sanitize(sellWallet.value),
issuerWallet: sanitize(issuerWallet.value),
memo: sanitize(memo.value),
minContribution: minContribution.value,
title: sanitize(title.value),
description: sanitize(description.value),
title: sanitize(telegramLink.value),
bonuses: bonuses.value,
queueID: null,
});
@@ -154,12 +149,12 @@ const submit = async () => {
const resp = await createRewardFund(
asset.value,
sanitize(fundWallet.value),
sanitize(fundSecret.value),
sanitize(sellWallet.value),
sanitize(issuerWallet.value),
sanitize(memo.value),
minContribution.value,
sanitize(title.value),
sanitize(description.value),
sanitize(telegramLink.value),
bonuses.value,
forQueue.value,
);


+ 3
- 8
src/views/AdminDashboardView.vue View File

@@ -5,7 +5,7 @@
<div class="title is-4 has-text-white-ter has-text-centered">Nearly Completed Funds</div>
<div v-for="(fund, ind) in nearlyCompletedFunds" :key="ind">
<RouterLink :to="`/fund/${fund.id}`">
<FundLink :fund="fund" :aside="`${round(fund.raised/fund.amountAvailable*100)}%`"/>
<FundLink :fund="fund" :aside="`${fund.amountAvailable} remaining`"/>
</RouterLink>
</div>
</template>
@@ -21,17 +21,12 @@ import { NearlyCompletedFund } from '@/api/types';

const nearlyCompletedFunds = ref([] as NearlyCompletedFund[]);

const pctThreshold = 85;
const threshold = 5000;

const req = await getNearlyCompletedFunds(pctThreshold);
const req = await getNearlyCompletedFunds(threshold);
if (req?.funds) {
nearlyCompletedFunds.value = req?.funds;
}

const round = (float: number, digits = 1) => {
const factor = 10 ** digits;
return Math.round(float * factor) / factor;
};
</script>

<style scoped lang="stylus">


+ 161
- 85
src/views/FundView.vue View File

@@ -26,59 +26,43 @@
</div>
</div>
</section>
<section>
<section class="section is-small">
<div class="box">
<div class="title is-size-4 has-text-grey-dark has-text-centered">
Tracker
</div>
<div class="level" v-if="fund.fundInfo.bonuses.length > 0">
<div class="level-item has-text-centered"
v-for="bonus in fund.fundInfo.bonuses.sort((v1, v2) => v1.goal - v2.goal)"
v-bind:key="bonus.goal"
>
<div>
<p
class="heading"
:class="amountHeld >= bonus.goal
<div class="mb-4">
<div class="level" v-if="fund.fundInfo.bonuses.length > 0">
<div class="level-item has-text-centered"
v-for="bonus in fund.fundInfo.bonuses.sort((v1, v2) => v1.goal - v2.goal)"
v-bind:key="bonus.goal"
>
<div>
<p
class="heading"
:class="amountHeld >= bonus.goal
? 'has-text-success' : 'has-text-grey-dark'"
>
{{ bonus.goal.toLocaleString() }} XLM
</p>
<p
class="title"
:class="amountHeld >= bonus.goal
>
{{ bonus.goal.toLocaleString() }} XLM
</p>
<p
class="title"
:class="amountHeld >= bonus.goal
? 'has-text-success' : 'has-text-grey-dark'"
>
{{ bonus.percent }}%
</p>
>
{{ bonus.percent }}%
</p>
</div>
</div>
</div>
<div v-else>
<div class="has-text-centered">This group fund has no rewards available</div>
</div>
</div>
</div>
<div
class="is-flex
is-flex-direction-row
is-justify-content-space-around
is-size-4
has-text-white
mb-3"
>
<div class="total">
<span class="total-label is-size-3 pr-2 has-text-weight-light">
Raised
</span>
<span class="pl-3 has-text-weight-bold">
{{ amountHeld.toLocaleString() }} XLM
</span>
</div>
<div class="remaining">
<span class="remaining-label is-size-3 pr-2 has-text-weight-light">
Remaining
</span>
<span class="pl-3 has-text-weight-bold">
{{ amountAvailable.toLocaleString() }} XLM
</span>
</div>
<progress class="progress is-large is-info" :value="calcPctComplete()"
max="100">
{{ calcPctComplete() }}%
</progress>
</div>
</section>
<section class="section is-small">
@@ -96,7 +80,7 @@
type="number"
placeholder="Amount"
aria-label="Amount"
v-model="amt"
v-model="amount"
:max="acctBalance"
:disabled="loading.balance"
>
@@ -145,26 +129,56 @@
<th>Wallet</th>
<th>Amount</th>
<th v-if="!enableConsolidation">Time</th>
<th v-else>Tokens</th>
<template v-else>
<th>Bonus</th>
<th v-if="store.getters.getToken && hasPermission(Privileges.Admin)">Post</th>
</template>
</tr>
</thead>
<tbody>
<tr v-for="(contribution, i) in contributions" v-bind:key="i">
<td>{{ truncateWallet(contribution.wallet, 6, undefined) }}</td>
<td>{{ truncateWallet(contribution.wallet, calculateWalletChars(), undefined) }}</td>
<td>{{ contribution.amount }}</td>
<td v-if="!enableConsolidation">
<span class="transaction-date" :title="formatDate(contribution.createdAt, true)">
{{ formatDate(contribution.createdAt, true) }}
</span>
</td>
<td v-else>
<span>{{ calculateReward(contribution.amount / fund.fundInfo.price) }}</span>
</td>
<template v-else>
<td>
<span>{{ calculateReward(contribution.amount.div(fund.fundInfo.price)) }}</span>
</td>
<td v-if="store.getters.getToken && hasPermission(Privileges.Admin)">
<button class="button is-small is-outlined">Send</button>
</td>
</template>
</tr>
</tbody>
</table>
</div>
</section>
<section class="section is-small px-0"
v-if="store.getters.getToken && hasPermission(Privileges.Admin)">
<div class="title is-size-4 has-text-white-ter has-text-centered">
Submit Group Fund
</div>
<div class="box is-flex is-flex-direction-row is-justify-content-space-between">
<div class="my-auto">
<label class="checkbox" for="submit-confirm">
<input type="checkbox" id="submit-confirm" v-model="allowSubmit"> Allow Submit
</label>
</div>
<div>
<button
class="button is-success"
:disabled="!allowSubmit"
@click="submitFund"
>
Submit
</button>
</div>
</div>
</section>
<section v-if="store.getters.getToken && hasPermission(Privileges.AdminPlus)">
<div class="title is-size-4 has-text-white-ter has-text-centered">
Close Group Fund
@@ -172,7 +186,7 @@
<div class="box is-flex is-flex-direction-row is-justify-content-space-between">
<div class="my-auto">
<label class="checkbox" for="delete-confirm">
<input type="checkbox" id="delete-confirm" v-model="allowDelete"> Close
<input type="checkbox" id="delete-confirm" v-model="allowDelete"> Allow Close
</label>
</div>
<div>
@@ -207,7 +221,10 @@ import {
ref,
watch,
} from 'vue';
import { useWebSocket } from '@vueuse/core';
import {
useWebSocket,
useWindowSize,
} from '@vueuse/core';
import store from '@/store';
import {
sanitize,
@@ -222,7 +239,9 @@ import {
getBalance,
getContributions,
getRewardFund,
submitRewardFund,
} from '@/api/composed';
import Decimal from 'decimal.js';

const route = useRoute();
const router = useRouter();
@@ -238,10 +257,23 @@ const formatDate = (time: string, includeTime = false) => {
};

const pk = ref('');
const amt = ref(undefined as number | undefined);
const amt = ref(undefined as Decimal | undefined);
const selectableDates = ref([undefined] as (string | undefined)[]);
const selectedDate = ref(undefined as string | undefined);

const { width } = useWindowSize();

const amount = computed({
get: () => amt.value,
set: (v) => {
if (v) {
amt.value = new Decimal(v);
} else {
amt.value = undefined;
}
},
});

const allowDelete = ref(false);
const deleteFund = async () => {
const deleted = await deleteRewardFund(identifier, allowDelete.value);
@@ -249,6 +281,32 @@ const deleteFund = async () => {
await router.push('/');
}
};
const delTimeout = ref(undefined as number | undefined);

watch(allowDelete, () => {
if (delTimeout.value) window.clearTimeout(delTimeout.value);
delTimeout.value = window.setTimeout(() => {
allowDelete.value = false;
delTimeout.value = undefined;
}, 10000);
});

const allowSubmit = ref(false);
const submitFund = async () => {
const submitted = await submitRewardFund(identifier, allowSubmit.value);
if (submitted && submitted.success) {
console.log('submitted'); // TODO: provide feedback for submission
}
};
const subTimeout = ref(undefined as number | undefined);

watch(allowSubmit, () => {
if (subTimeout.value) window.clearTimeout(subTimeout.value);
subTimeout.value = window.setTimeout(() => {
allowSubmit.value = false;
subTimeout.value = undefined;
}, 10000);
});

const enableConsolidation = ref(false);
const fund = ref(
@@ -278,11 +336,11 @@ fundDetails.value = [
val: fund.value.fundInfo.asset,
},
{
title: 'Min',
title: 'Minimum',
val: `${fund.value.fundInfo.minContribution.toLocaleString()}`,
},
{
title: 'Goal',
title: 'Remaining',
val: `${fund.value.fundInfo.amountAvailable.toLocaleString()}`,
},
{
@@ -296,29 +354,47 @@ if (fund.value.contributions.dates) {
);
}

const reward = ref(0);
const processContributions = (contributions: Contribution[]) => contributions.map((c) => ({
...c,
amount: new Decimal(c.amount),
}));

const reward = ref(new Decimal(0));
const maxBonus = ref(0);
const bonus = ref(undefined as Bonus | undefined);
const amountHeld = ref(fund.value.total.amountHeld);
const amountAvailable = ref(fund.value.fundInfo.amountAvailable);
const contributions: Ref<Contribution[]> = ref(fund.value.contributions.list ?? []);
const amountHeld = ref(new Decimal(fund.value.total.amountHeld));
const amountAvailable = ref(new Decimal(fund.value.fundInfo.amountAvailable));
const contributions: Ref<Contribution[]> = ref(processContributions(
fund.value.contributions.list ?? [],
));
const offset = ref(contributions.value.length);
const total = ref(fund.value.contributions.total);
const contributionsLoading = ref(false);
const acctBalance = ref(undefined as number | undefined);
const acctBalance = ref(undefined as Decimal | undefined);
const unknownAcct = ref(true);
const loading = ref({
contribution: false,
balance: false,
});

const round = (num: number, figures = 1) => {
const factor = 10 ** figures;
return Math.round(num * factor) / factor;
};

const calcPctComplete = () => round(amountHeld.value.div(amountAvailable.value)
.mul(100)
.toNumber());

const calculateWalletChars = () => round(width.value / 114, 0);

const hasInvalidValues = () => {
if (!fund.value) throw new Error('Fund was not loaded!');
return [pk, amt].every((v) => v.value !== undefined && v.value !== '')
&& (amt.value === 0
|| amt.value! > amountAvailable.value
|| amt.value! < fund.value.fundInfo.minContribution
|| (acctBalance.value && amt.value! > acctBalance.value)
return [pk, amount].every((v) => v.value !== undefined && v.value !== '')
&& (amount.value!.isZero()
|| amount.value! > amountAvailable.value
|| amount.value!.lt(fund.value.fundInfo.minContribution)
|| (acctBalance.value && amt.value!.gt(acctBalance.value))
|| unknownAcct.value);
};

@@ -339,18 +415,17 @@ const getCurrentBonus = () => {
}
};

const calculateReward = (bought: number) => {
const calculateReward = (bought: Decimal) => {
if (bonus.value) {
if (!bonus.value.percent) throw new Error('Bonus did not have percent for some reason');
reward.value = bought + bought * (bonus.value.percent / 100);
reward.value = bought.add(bought)
.mul(new Decimal(bonus.value.percent / 100));
} else {
reward.value = bought;
}
return reward.value.toLocaleString();
};

const fixNewlines = (s: string) => s.replace('\n', '<br/>');

const errs: SignetError[] = [
{
text: 'Amount is required',
@@ -358,11 +433,11 @@ const errs: SignetError[] = [
},
{
text: 'Amount must be greater than 0',
condition: amt.value && amt.value === 0 && !fund.value.fundInfo.minContribution,
condition: amt.value && amt.value.isZero() && !fund.value.fundInfo.minContribution,
},
{
text: 'Amount is less than the minimum contribution',
condition: amt.value && amt.value < fund.value.fundInfo.minContribution,
condition: amt.value && amt.value.lt(fund.value.fundInfo.minContribution),
},
{
text: `Not enough ${fund.value.fundInfo.asset} for sale in ICO`,
@@ -370,7 +445,7 @@ const errs: SignetError[] = [
},
{
text: `Not enough XLM to send (${amt.value?.toLocaleString()})`,
condition: amt.value && acctBalance.value && amt.value > acctBalance.value,
condition: amt.value && acctBalance.value && amt.value.gt(acctBalance.value),
},
{
text: 'Could not find Stellar wallet',
@@ -385,7 +460,7 @@ watch(selectedDate, async (newVal) => {
const conts = await getContributions(identifier, offset.value, newVal, enableConsolidation.value);
if (!fund.value) throw new Error('Fund not found');
if (!conts) throw new Error('Contributions not found');
contributions.value = conts.list;
contributions.value = processContributions(conts.list);
offset.value = contributions.value.length;
total.value = fund.value.contributions.total;
});
@@ -407,7 +482,7 @@ const loadMoreIfNeeded = async (e: Event) => {
if (!moreContribs) throw new Error('Contributions not found');
offset.value += moreContribs.list.length;
total.value = moreContribs.total;
contributions.value = contributions.value.concat(moreContribs.list);
contributions.value = contributions.value.concat(processContributions(moreContribs.list));
contributionsLoading.value = false;
}
};
@@ -432,9 +507,9 @@ const queryAccount = async () => {
acctBalance.value = undefined;
} else {
unknownAcct.value = false;
if (resp?.balance) {
acctBalance.value = resp?.balance;
if (amt.value && amt.value > acctBalance.value) {
if (resp && resp.balance) {
acctBalance.value = new Decimal(resp.balance);
if (amt.value && amt.value.gt(acctBalance.value)) {
amt.value = acctBalance.value;
}
}
@@ -445,12 +520,13 @@ const queryAccount = async () => {

const makeContribution = async () => {
if (!fund.value) throw new Error('Fund not found');
if (!amt.value) return;
if (!/[^[0-9]+$/.test(amt.value.toString())) return;
if (!amount.value) return;
if (!/^[0-9]+$/.test(amount.value.toString())) return;
if (unknownAcct.value) return;
if (!loading.value.contribution && pk.value && amt.value && amt.value <= amountAvailable.value) {
if (!loading.value.contribution && pk.value
&& amount.value && amount.value <= amountAvailable.value) {
loading.value.contribution = true;
await contribute(sanitize(pk.value), amt.value, fund.value.fundInfo.id);
await contribute(sanitize(pk.value), amount.value!.toNumber(), fund.value.fundInfo.id);
loading.value.contribution = false;
pk.value = '';
amt.value = undefined;
@@ -467,7 +543,7 @@ watch(enableConsolidation, async () => {
);
if (!fund.value) throw new Error('Fund not found');
if (!conts) throw new Error('Contributions not found');
contributions.value = conts.list;
contributions.value = processContributions(conts.list);
offset.value = contributions.value.length;
total.value = fund.value.contributions.total;
});
@@ -488,13 +564,13 @@ watch(data, (newVal) => {
.includes(v.wallet)) {
const hasContribution = contributions.value.find((c: Contribution) => c.wallet === v.wallet);
if (!hasContribution) throw new Error('Something went wrong');
hasContribution.amount += v.amount;
hasContribution.amount = new Decimal(hasContribution.amount).add(v.amount);
} else {
contributions.value.splice(0, 0, v);
offset.value += 1;
}
amountHeld.value += v.amount;
amountAvailable.value -= v.amount;
amountHeld.value = new Decimal(amountHeld.value).add(v.amount);
amountAvailable.value = new Decimal(amountAvailable.value).sub(v.amount);
}
});
</script>


+ 6710
- 6542
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save