Browse Source

Queue editor sends new order to server

master
Jared 1 year ago
parent
commit
24d9a78182
11 changed files with 261 additions and 57 deletions
  1. +2
    -2
      src/App.vue
  2. +13
    -4
      src/api/composed.ts
  3. +32
    -6
      src/api/types.ts
  4. +38
    -17
      src/components/EditQueue.vue
  5. +21
    -7
      src/components/FundLink.vue
  6. +55
    -4
      src/router/index.ts
  7. +1
    -0
      src/views/AddFundView.vue
  8. +39
    -0
      src/views/AdminDashboardView.vue
  9. +28
    -12
      src/views/AdminView.vue
  10. +8
    -5
      src/views/FundView.vue
  11. +24
    -0
      src/views/ModifyQueueView.vue

+ 2
- 2
src/App.vue View File

@@ -19,8 +19,8 @@
</RouterLink> </RouterLink>
</div> </div>
<div v-else> <div v-else>
<RouterLink to="/addfund" class="button is-primary">
Add Fund
<RouterLink to="/admin/dashboard" class="button is-primary">
Admin
</RouterLink> </RouterLink>
<RouterLink to="/register" class="button is-white"> <RouterLink to="/register" class="button is-white">
Register Register


+ 13
- 4
src/api/composed.ts View File

@@ -7,6 +7,7 @@ import {
CreateQueueRequest, CreateQueueRequest,
CreateQueueResponse, CreateQueueResponse,
CreateRewardFundRequest, CreateRewardFundRequest,
EditQueueRequest,
GetBalanceRequest, GetBalanceRequest,
GetBalanceResponse, GetBalanceResponse,
GetContributionsRequest, GetContributionsRequest,
@@ -19,6 +20,9 @@ import {
GetRewardFundsRequest, GetRewardFundsRequest,
GetRewardFundsResponse, GetRewardFundsResponse,
LoginResponse, LoginResponse,
NearlyCompleteFundsRequest,
NearlyCompleteFundsResponse,
QueueMember,
SuccessResponse, SuccessResponse,
} from '@/api/types'; } from '@/api/types';


@@ -49,8 +53,7 @@ export const createRewardFund = (
issuerWallet: string, issuerWallet: string,
memo: string, memo: string,
minContribution: number, minContribution: number,
title: string,
description: string,
telegramLink: string,
bonuses: Bonus[], bonuses: Bonus[],
queueID?: number | null | undefined, queueID?: number | null | undefined,
) => controller.post<SuccessResponse, CreateRewardFundRequest>('CreateRewardFund', { ) => controller.post<SuccessResponse, CreateRewardFundRequest>('CreateRewardFund', {
@@ -60,8 +63,7 @@ export const createRewardFund = (
issuerWallet, issuerWallet,
memo, memo,
minContribution, minContribution,
title,
description,
telegramLink,
bonuses, bonuses,
queueID, queueID,
}); });
@@ -103,3 +105,10 @@ export const contribute = (privateKey: string, amount: number, rewardFund: numbe
amount, amount,
rewardFund, rewardFund,
}); });

export const getNearlyCompletedFunds = (threshold: number) => controller.post<NearlyCompleteFundsResponse, NearlyCompleteFundsRequest>('NearlyCompleteFunds', { threshold });

export const reorderQueue = (queueID: number, fundOrders: QueueMember[]) => controller.post<SuccessResponse, EditQueueRequest>('EditQueue', {
queueID,
fundOrders,
});

+ 32
- 6
src/api/types.ts View File

@@ -28,8 +28,7 @@ export interface RewardFund {
amountGoal: number; amountGoal: number;
minContribution: number; minContribution: number;
contributions: Contribution[] | null; contributions: Contribution[] | null;
title: string;
description: string;
telegramLink: string;
} }


export interface Queue { export interface Queue {
@@ -37,6 +36,12 @@ export interface Queue {
name: string; name: string;
} }


export interface QueueMember {
id?: number;
asset: string;
order: number;
}

export interface CreateQueueRequest { export interface CreateQueueRequest {
name: string; name: string;
} }
@@ -70,8 +75,7 @@ export interface CreateRewardFundRequest {
issuerWallet: string; issuerWallet: string;
memo: string; memo: string;
minContribution: number; minContribution: number;
title: string;
description: string;
telegramLink: string;
queueID?: number | null; queueID?: number | null;
bonuses: Bonus[]; bonuses: Bonus[];
} }
@@ -87,8 +91,7 @@ export interface FundInfo {
amountAvailable: number; amountAvailable: number;
amountGoal: number; amountGoal: number;
minContribution: number; minContribution: number;
title: string;
description: string;
telegramLink: string;
bonuses: Bonus[]; bonuses: Bonus[];
queueID: number | null; queueID: number | null;
} }
@@ -149,6 +152,24 @@ export interface Claims {
exp: number; exp: number;
} }


export interface NearlyCompletedFund {
id: number;
asset: string;
minContribution: number;
amountAvailable: number;
memo: string;
fundWallet: string;
raised: number;
}

export interface NearlyCompleteFundsRequest {
threshold: number;
}

export interface NearlyCompleteFundsResponse {
funds: NearlyCompletedFund[];
}

export interface GetContributionsRequest { export interface GetContributionsRequest {
id: number; id: number;
offset: number; offset: number;
@@ -162,3 +183,8 @@ export interface CloseRewardFundRequest {
id: number; id: number;
close: boolean; close: boolean;
} }

export interface EditQueueRequest {
queueID: number;
fundOrders: QueueMember[];
}

+ 38
- 17
src/components/EditQueue.vue View File

@@ -1,7 +1,18 @@
<template> <template>
<div class="is-flex is-flex-direction-row is-justify-content-space-between">
<div class="select mr-1">
<div
class="is-flex is-flex-direction-row is-justify-content-space-between"
:class="props.orientation === 'horizontal'
? 'is-flex-direction-row'
: 'is-flex-direction-column'"
>
<div class="select"
:class="props.orientation === 'horizontal' ? 'mr-1' : 'mr-1 mb-2'"
:style="props.orientation === 'vertical'
? 'width: 28%; min-width: 180px; align-self: end'
: undefined"
>
<select <select
style="width: 100%"
ref="queueOptions" ref="queueOptions"
v-model="queueSelection" v-model="queueSelection"
aria-label="Queue Selection" aria-label="Queue Selection"
@@ -14,6 +25,12 @@
</option> </option>
</select> </select>
</div> </div>
<div v-if="queueSelection === undefined" class="is-flex-grow-1">
<div class="is-size-6 has-text-centered is-italic py-4">
Select an option from the menu
{{ props.orientation === 'vertical' ? 'above' : 'to the left' }}
</div>
</div>
<div v-if="queueSelection === -1" class="is-flex is-flex-direction-row is-flex-grow-1 ml-1"> <div v-if="queueSelection === -1" class="is-flex is-flex-direction-row is-flex-grow-1 ml-1">
<input <input
v-model="queueName" v-model="queueName"
@@ -32,7 +49,7 @@
:key="element.order" :key="element.order"
> >
<div class="is-flex is-flex-direction-row is-justify-content-space-between"> <div class="is-flex is-flex-direction-row is-justify-content-space-between">
<div>{{ element.title }} ({{ element.asset }})</div>
<div>{{ element.asset }}</div>
<div>{{ element.order }}</div> <div>{{ element.order }}</div>
</div> </div>
</div> </div>
@@ -49,23 +66,22 @@ import {
} from 'vue'; } from 'vue';
import { import {
Queue, Queue,
QueueMember,
RewardFund, RewardFund,
} from '@/api/types'; } from '@/api/types';
import { import {
getQueueMembers, getQueueMembers,
getQueues, getQueues,
reorderQueue,
} from '@/api/composed'; } from '@/api/composed';
import { VueDraggableNext as Draggable } from 'vue-draggable-next'; import { VueDraggableNext as Draggable } from 'vue-draggable-next';

interface QueueMember {
id?: number;
title: string;
asset: string;
order: number;
}
import { useDebounceFn } from '@vueuse/core';


// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const props = defineProps<{ newMember: RewardFund & { order: number; } }>();
const props = defineProps<{
newMember: RewardFund & { order: number; },
orientation: 'horizontal' | 'vertical'
}>();


// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const emits = defineEmits(['selected', 'created']); const emits = defineEmits(['selected', 'created']);
@@ -75,13 +91,21 @@ const queueName = ref(undefined as string | undefined);
const queueMembers = ref(undefined as QueueMember[] | undefined); const queueMembers = ref(undefined as QueueMember[] | undefined);
const serverQueues = ref(0); const serverQueues = ref(0);


const reorder = () => {
const sendQueueOrder = useDebounceFn(async () => {
if (queueSelection.value && queueMembers.value) {
await reorderQueue(queueSelection.value, queueMembers.value);
}
}, 2000);

const reorder = async () => {
if (queueMembers.value) { if (queueMembers.value) {
for (let i = 0; i < queueMembers.value.length; i += 1) { for (let i = 0; i < queueMembers.value.length; i += 1) {
if (queueMembers.value[i]) { if (queueMembers.value[i]) {
queueMembers.value[i].order = (i + 1); queueMembers.value[i].order = (i + 1);
} }
} }

await sendQueueOrder();
} }
}; };


@@ -107,16 +131,14 @@ const populateQueueMembers = async (id: number) => {
const resp = await getQueueMembers(id) as { members: (RewardFund & { order: number; })[] }; const resp = await getQueueMembers(id) as { members: (RewardFund & { order: number; })[] };
queueMembers.value = resp?.members.map((m) => ({ queueMembers.value = resp?.members.map((m) => ({
id: m.id, id: m.id,
title: m.title,
asset: m.asset, asset: m.asset,
order: m.order, order: m.order,
})); }));
serverQueues.value = queueMembers.value.length; serverQueues.value = queueMembers.value.length;


if (queueMembers.value && props.newMember.title && props.newMember.asset) {
if (queueMembers.value && props.newMember.asset) {
const newMember = { const newMember = {
id: undefined, id: undefined,
title: props.newMember.title,
asset: props.newMember.asset, asset: props.newMember.asset,
order: queueMembers.value.length + 1, order: queueMembers.value.length + 1,
}; };
@@ -134,10 +156,9 @@ watch(queueSelection, async (newValue) => {
const addedMember = computed(() => props.newMember); const addedMember = computed(() => props.newMember);


watch(addedMember, (newVal) => { watch(addedMember, (newVal) => {
if (newVal.title && newVal.asset) {
if (newVal.asset) {
const assembleNewMember = (order: number) => ({ const assembleNewMember = (order: number) => ({
id: undefined, id: undefined,
title: newVal.title,
asset: newVal.asset, asset: newVal.asset,
order, order,
}); });


+ 21
- 7
src/components/FundLink.vue View File

@@ -1,8 +1,19 @@
<template> <template>
<div class="card py-2 px-4 has-text-dark" <div class="card py-2 px-4 has-text-dark"
:style="generateBackgroundStyle(`${fund.asset} ${fund.title}`)"> :style="generateBackgroundStyle(`${fund.asset} ${fund.title}`)">
<div class="is-size-2-desktop is-size-2-tablet is-size-3-mobile">
{{ fund.asset }}
<div class="is-flex
is-flex-direction-row
is-justify-content-space-between
is-size-2-desktop
is-size-2-tablet
is-size-3-mobile"
>
<div>
{{ fund.asset }}
</div>
<div v-if="props.aside">
{{ props.aside }}
</div>
</div> </div>
<div> <div>
<ul class="is-flex is-flex-direction-row is-justify-content-space-between"> <ul class="is-flex is-flex-direction-row is-justify-content-space-between">
@@ -30,15 +41,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { truncateWallet } from '@/lib/helpers'; import { truncateWallet } from '@/lib/helpers';
import {
FundInfo,
} from '@/api/types';
import { FundInfo } from '@/api/types';
import { PropType } from 'vue'; import { PropType } from 'vue';


// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const props = defineProps({ fund: Object as PropType<FundInfo> });
const props = defineProps({
fund: Object as PropType<FundInfo>,
aside: Object as PropType<string | number | undefined>,
});


const generateHue = (seed: string) => seed.split('').map((c) => c.charCodeAt(0)).reduce((v1, v2) => v1 + v2) % 256;
const generateHue = (seed: string) => seed.split('')
.map((c) => c.charCodeAt(0))
.reduce((v1, v2) => v1 + v2) % 256;


const generateBackgroundStyle = (hueSeed: string) => { const generateBackgroundStyle = (hueSeed: string) => {
const hue = generateHue(hueSeed); const hue = generateHue(hueSeed);


+ 55
- 4
src/router/index.ts View File

@@ -14,6 +14,9 @@ import FundView from '@/views/FundView.vue';
import AddFundView from '@/views/AddFundView.vue'; import AddFundView from '@/views/AddFundView.vue';
import hasPermission from '@/lib/auth'; import hasPermission from '@/lib/auth';
import SignetRequestController from '@/api/requests'; import SignetRequestController from '@/api/requests';
import AdminView from '@/views/AdminView.vue';
import ModifyQueueView from '@/views/ModifyQueueView.vue';
import AdminDashboardView from '@/views/AdminDashboardView.vue';


const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
{ {
@@ -49,12 +52,60 @@ const routes: Array<RouteRecordRaw> = [
}, },
}, },
{ {
path: '/addfund',
name: 'addfund',
component: AddFundView,
path: '/admin',
name: 'admin',
component: AdminView,
redirect: '/admin/dashboard',
children: [
{
path: 'dashboard',
name: 'admindashboard',
component: AdminDashboardView,
meta: {
requiredRights: Privileges.Admin,
title: 'Admin',
},
},
{
path: 'addfund',
name: 'addfund',
component: AddFundView,
meta: {
requiredRights: Privileges.Admin,
title: 'Add Group Fund',
},
},
{
path: 'modifyfund',
name: 'modifyfund',
component: AddFundView,
meta: {
requiredRights: Privileges.Admin,
title: 'Add Group Fund',
},
},
{
path: 'addqueue',
name: 'addqueue',
component: AddFundView,
meta: {
requiredRights: Privileges.Admin,
title: 'Add Group Fund',
},
},
{
path: 'modifyqueue',
name: 'modifyqueue',
component: ModifyQueueView,
meta: {
requiredRights: Privileges.Admin,
title: 'Add Group Fund',
},
},
],
meta: { meta: {
requiredRights: Privileges.Admin, requiredRights: Privileges.Admin,
title: 'Add Group Fund',
title: 'Administrator',
}, },
}, },
]; ];


+ 1
- 0
src/views/AddFundView.vue View File

@@ -49,6 +49,7 @@
<div class="title is-5 has-text-white-ter">Queue</div> <div class="title is-5 has-text-white-ter">Queue</div>
<EditQueue <EditQueue
:new-member="constructFund()" :new-member="constructFund()"
orientation="horizontal"
@created="setQueueName" @created="setQueueName"
@selected="setQueueSelection" @selected="setQueueSelection"
/> />


+ 39
- 0
src/views/AdminDashboardView.vue View File

@@ -0,0 +1,39 @@
<template>
<div class="container is-max-desktop">
<section class="section is-small px-0">
<template v-if="nearlyCompletedFunds.length > 0">
<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)}%`"/>
</RouterLink>
</div>
</template>
</section>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { getNearlyCompletedFunds } from '@/api/composed';
import FundLink from '@/components/FundLink.vue';
import { NearlyCompletedFund } from '@/api/types';

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

const pctThreshold = 85;

const req = await getNearlyCompletedFunds(pctThreshold);
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">

</style>

+ 28
- 12
src/views/AdminView.vue View File

@@ -1,15 +1,17 @@
<template> <template>
<!-- Unused --> <!-- Unused -->
<div class="is-flex is-flex-direction-row"> <div class="is-flex is-flex-direction-row">
<nav class="is-hidden-tablet-only is-hidden-mobile has-background-white-ter">
<div class="has-background-primary">
<span class="has-text-white has-text-weight-bold my-4 mx-2">Administration</span>
<nav class="is-hidden-tablet-only is-hidden-mobile">
<div class="mt-6">
<div>
<span class="has-text-white has-text-weight-bold my-4 mx-2">Administration</span>
</div>
<ul class="p-2">
<li v-for="(link, i) in links" v-bind:key="i">
<RouterLink :to="link.to" class="has-text-grey-light">{{ link.text }}</RouterLink>
</li>
</ul>
</div> </div>
<ul class="p-2">
<li v-for="(link, i) in links" v-bind:key="i">
<RouterLink :to="link.to">{{ link.text }}</RouterLink>
</li>
</ul>
</nav> </nav>
<div class="container is-max-desktop"> <div class="container is-max-desktop">
<RouterView></RouterView> <RouterView></RouterView>
@@ -19,16 +21,30 @@


<script setup lang="ts"> <script setup lang="ts">
const links = [ const links = [
{ text: 'Add Fund', to: '/admin/addfund' },
{ text: 'Modify Fund', to: '' },
{ text: 'Create Tag', to: '' },
{
text: 'Dashboard',
to: '/admin/dashboard',
},
{
text: 'Add Fund',
to: '/admin/addfund',
},
{
text: 'Modify Fund',
to: '/admin/modifyfund',
},
{
text: 'Add/Modify Queue',
to: '/admin/modifyqueue',
},
]; ];

</script> </script>


<style scoped lang="stylus"> <style scoped lang="stylus">
nav nav
min-width 134px min-width 134px
min-height 100vh
height calc(100vh - 56px)


li li
font-variant all-petite-caps font-variant all-petite-caps


+ 8
- 5
src/views/FundView.vue View File

@@ -1,16 +1,19 @@
<template> <template>
<div class="container is-max-desktop pb-4"> <div class="container is-max-desktop pb-4">
<section class="section is-small"> <section class="section is-small">
<div class="title is-size-4 has-text-white-ter has-text-centered">
{{ fund.fundInfo.title }}
</div>
<div <div
class="is-block-mobile class="is-block-mobile
is-flex-tablet-only is-flex-tablet-only
is-flex-desktop is-flex-desktop
is-flex-direction-row is-flex-direction-row
is-justify-content-space-between"> is-justify-content-space-between">
<div class="fund-description pr-5" v-html="fixNewlines(fund.fundInfo.description)">
<div class="my-auto">
<div class="is-size-1">{{ fund.fundInfo.asset }}</div>
<div>
<a :href="fund.fundInfo.telegramLink" target="_blank" class="has-text-grey-light">
{{ fund.fundInfo.telegramLink }}
</a>
</div>
</div> </div>
<div <div
class="fund-details is-flex is-flex-direction-row is-justify-content-end my-auto py-6"> class="fund-details is-flex is-flex-direction-row is-justify-content-end my-auto py-6">
@@ -375,7 +378,7 @@ const errs: SignetError[] = [
}, },
]; ];


document.title = `Beignet - ${fund.value.fundInfo.title}`;
document.title = `Beignet - ${fund.value.fundInfo.asset}`;


watch(selectedDate, async (newVal) => { watch(selectedDate, async (newVal) => {
offset.value = 0; offset.value = 0;


+ 24
- 0
src/views/ModifyQueueView.vue View File

@@ -0,0 +1,24 @@
<template>
<div class="container is-max-desktop">
<section class="section is-small">
<div class="title is-4 has-text-white-ter has-text-centered">
Add/Modify Queue
</div>
<section class="section px-0 py-4">
<EditQueue :new-member="undefined" orientation="vertical" @created="createQueue"/>
</section>
</section>
</div>
</template>

<script setup lang="ts">

import EditQueue from '@/components/EditQueue.vue';
import { createQueue } from '@/api/composed';
//

</script>

<style scoped lang="stylus">

</style>

Loading…
Cancel
Save