summaryrefslogtreecommitdiff
path: root/packages/frontend/src/pages/invite.vue
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/pages/invite.vue')
-rw-r--r--packages/frontend/src/pages/invite.vue114
1 files changed, 114 insertions, 0 deletions
diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue
new file mode 100644
index 0000000000..c893ad51e8
--- /dev/null
+++ b/packages/frontend/src/pages/invite.vue
@@ -0,0 +1,114 @@
+<template>
+<MkStickyContainer>
+ <template #header>
+ <MkPageHeader/>
+ </template>
+ <MKSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200">
+ <div :class="$style.root">
+ <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
+ <div :class="$style.text">
+ <i class="ti ti-alert-triangle"></i>
+ {{ i18n.ts.nothing }}
+ </div>
+ </div>
+ </MKSpacer>
+ <MkSpacer v-else :contentMax="800">
+ <div class="_gaps_m" style="text-align: center;">
+ <div v-if="resetCycle && inviteLimit">{{ i18n.t('inviteLimitResetCycle', { time: resetCycle, limit: inviteLimit }) }}</div>
+ <MkButton inline primary rounded :disabled="currentInviteLimit !== null && currentInviteLimit <= 0" @click="create"><i class="ti ti-user-plus"></i> {{ i18n.ts.createInviteCode }}</MkButton>
+ <div v-if="currentInviteLimit !== null">{{ i18n.t('createLimitRemaining', { limit: currentInviteLimit }) }}</div>
+
+ <MkPagination ref="pagingComponent" :pagination="pagination">
+ <template #default="{ items }">
+ <div class="_gaps_s">
+ <MkInviteCode v-for="item in (items as Invite[])" :key="item.id" :invite="item" :onDeleted="deleted"/>
+ </div>
+ </template>
+ </MkPagination>
+ </div>
+ </MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, shallowRef } from 'vue';
+import type { Invite } from 'misskey-js/built/entities';
+import { i18n } from '@/i18n';
+import * as os from '@/os';
+import MkButton from '@/components/MkButton.vue';
+import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkInviteCode from '@/components/MkInviteCode.vue';
+import { definePageMetadata } from '@/scripts/page-metadata';
+import { serverErrorImageUrl, instance } from '@/instance';
+import { $i } from '@/account';
+
+const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const currentInviteLimit = ref<null | number>(null);
+const inviteLimit = (($i != null && $i.policies.inviteLimit) || (($i == null && instance.policies.inviteLimit))) as number;
+const inviteLimitCycle = (($i != null && $i.policies.inviteLimitCycle) || ($i == null && instance.policies.inviteLimitCycle)) as number;
+
+const pagination: Paging = {
+ endpoint: 'invite/list' as const,
+ limit: 10,
+};
+
+const resetCycle = computed<null | string>(() => {
+ if (!inviteLimitCycle) return null;
+
+ const minutes = inviteLimitCycle;
+ if (minutes < 60) return minutes + i18n.ts._time.minute;
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return hours + i18n.ts._time.hour;
+ return Math.floor(hours / 24) + i18n.ts._time.day;
+});
+
+async function create() {
+ const ticket = await os.api('invite/create');
+ os.alert({
+ type: 'success',
+ title: i18n.ts.inviteCodeCreated,
+ text: ticket.code,
+ });
+
+ pagingComponent.value?.prepend(ticket);
+ update();
+}
+
+function deleted(id: string) {
+ if (pagingComponent.value) {
+ pagingComponent.value.items.delete(id);
+ }
+ update();
+}
+
+async function update() {
+ currentInviteLimit.value = (await os.api('invite/limit')).remaining;
+}
+
+update();
+
+definePageMetadata({
+ title: i18n.ts.invite,
+ icon: 'ti ti-user-plus',
+});
+</script>
+
+<style lang="scss" module>
+.root {
+ padding: 32px;
+ text-align: center;
+ align-items: center;
+}
+
+.text {
+ margin: 0 0 8px 0;
+}
+
+.img {
+ vertical-align: bottom;
+ width: 128px;
+ height: 128px;
+ margin-bottom: 16px;
+ border-radius: 16px;
+}
+</style>