From 02957a1b5daaaf821ce21c11cc47cf169c4fc535 Mon Sep 17 00:00:00 2001
From: yukineko <27853966+hideki0403@users.noreply.github.com>
Date: Sat, 15 Jul 2023 09:57:58 +0900
Subject: enhance: 招待機能の改善 (#11195)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* refactor(backend): 招待機能を改修

* feat(backend): 招待コードのcreate/delete/listエンドポイントを追加

* add(misskey-js): エンドポイントと型を追加

* change(backend): metaでinvite関連の情報も返すように

* add(misskey-js): エンドポイントと型を追加

* add(backend): `/endpoints/invite/limit`を追加

* fix: createdByがnullableではなかったのを修正

* fix: relationが取得できていなかった問題を修正

* fix: パラメータを間違えていたのを修正

* feat(client): 招待ページを実装

* change(client): インスタンスメニューの「招待」押した場合に招待ページに飛ぶように変更

* feat: 招待コードをコピーできるように

* change(backend): metaに招待コード発行に関する情報を持たせるのをやめる

* feat: ロールごとに招待コードの発行上限数などを設定できるように

* change(client): 招待コードをコピーしたときにダイアログを出すように

* add: 招待に関する管理者用のエンドポイントを追加

* change(backend): モデレーターであれば作成者以外でも招待コードを削除できるように

* change(backend): admin/invite/listはオフセットでページネーションするように

* feat(client): 招待コードの管理ページを追加

* feat(client): 招待コードのリストをソートできるように

* change: `admin/invite/create`のレスポンスを修正

* fix(client): 有効期限を指定できていなかった問題を修正

* refactor: 必要のない箇所を削除

* perf(backend): use limit() instead of take()

* change(client): 作成ボタンを見た目を変更

* refactor: 招待コードの生成部分を共通化し、コード内に"01OI"のいずれかの文字を含まないように

* fix(client): paginationの仕様が変わっていたので修正

* change(backend): expiresAtパラメータのnullを許容

* change(client): 有効期限を設けないときは日付の入力欄を非表示に

* fix: 自身のポリシーよりもインスタンス側のポリシーが優先表示される問題を修正

* fix: n時間のときに「n時間間」となってしまうのを修正

* fix(backend): ポリシーが途中で変更されたときに作成可能数がマイナス表記になってしまうのを修正

* change(client): 招待コードのユーザー名が不明な理由を表示するように

* update: CHANGELOG.md

* lint

* refactor

* refactor

* tweak ui

* :art:

* :art:

* add(backend): indexを追加

* change(backend): indexの追加に伴う変更

* change(client): インスタンスメニューの「招待」の場所を変更

* add(frontend): MkInviteCode用のstorybookを追加

* Update misskey-js.api.md

* fix(misskey-js): InviteのcreatedByの型が間違っていたのを修正

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: tamaina <tamaina@hotmail.co.jp>
---
 packages/frontend/src/components/MkInviteCode.vue | 123 ++++++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 packages/frontend/src/components/MkInviteCode.vue

(limited to 'packages/frontend/src/components/MkInviteCode.vue')

diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue
new file mode 100644
index 0000000000..fdde79b178
--- /dev/null
+++ b/packages/frontend/src/components/MkInviteCode.vue
@@ -0,0 +1,123 @@
+<template>
+<MkFolder>
+	<template #label>{{ invite.code }}</template>
+	<template #suffix>
+		<span v-if="invite.used">{{ i18n.ts.used }}</span>
+		<span v-else-if="isExpired" style="color: var(--error)">{{ i18n.ts.expired }}</span>
+		<span v-else style="color: var(--success)">{{ i18n.ts.unused }}</span>
+	</template>
+
+	<div class="_gaps_s" :class="$style.root">
+		<div :class="$style.items">
+			<div>
+				<div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
+				<div>{{ invite.code }}</div>
+			</div>
+			<div v-if="moderator">
+				<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
+				<div v-if="invite.createdBy" :class="$style.user">
+					<MkAvatar :user="invite.createdBy" :class="$style.avatar" link preview/>
+					<MkUserName :user="invite.createdBy" :nowrap="false"/>
+					<div v-if="moderator">({{ invite.createdBy.id }})</div>
+				</div>
+				<div v-else>system</div>
+			</div>
+			<div v-if="invite.used">
+				<div :class="$style.label">{{ i18n.ts.registeredUserUsingInviteCode }}</div>
+				<div v-if="invite.usedBy" :class="$style.user">
+					<MkAvatar :user="invite.usedBy" :class="$style.avatar" link preview/>
+					<MkUserName :user="invite.usedBy" :nowrap="false"/>
+					<div v-if="moderator">({{ invite.usedBy.id }})</div>
+				</div>
+				<div v-else>{{ i18n.ts.unknown }} ({{ i18n.ts.waitingForMailAuth }})</div>
+			</div>
+			<div v-if="invite.expiresAt && !invite.used">
+				<div :class="$style.label">{{ i18n.ts.expirationDate }}</div>
+				<div><MkTime :time="invite.expiresAt" mode="absolute"/></div>
+			</div>
+			<div v-if="invite.usedAt">
+				<div :class="$style.label">{{ i18n.ts.inviteCodeUsedAt }}</div>
+				<div><MkTime :time="invite.usedAt" mode="absolute"/></div>
+			</div>
+			<div v-if="moderator">
+				<div :class="$style.label">{{ i18n.ts.createdAt }}</div>
+				<div><MkTime :time="invite.createdAt" mode="absolute"/></div>
+			</div>
+		</div>
+		<div :class="$style.buttons">
+			<MkButton v-if="!invite.used && !isExpired" primary rounded @click="copyInviteCode()">{{ i18n.ts.copy }}</MkButton>
+			<MkButton v-if="!invite.used || moderator" danger rounded @click="deleteCode()">{{ i18n.ts.delete }}</MkButton>
+		</div>
+	</div>
+</MkFolder>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as misskey from 'misskey-js';
+import MkFolder from '@/components/MkFolder.vue';
+import MkButton from '@/components/MkButton.vue';
+import copyToClipboard from '@/scripts/copy-to-clipboard';
+import { i18n } from '@/i18n';
+import * as os from '@/os';
+
+const props = defineProps<{
+	invite: misskey.entities.Invite;
+	moderator?: boolean;
+}>();
+
+const emits = defineEmits<{
+	(event: 'deleted', value: string): void;
+}>();
+
+const isExpired = computed(() => {
+	return props.invite.expiresAt && new Date(props.invite.expiresAt) < new Date();
+});
+
+function deleteCode() {
+	os.apiWithDialog('invite/delete', {
+		inviteId: props.invite.id,
+	});
+	emits('deleted', props.invite.id);
+}
+
+function copyInviteCode() {
+	copyToClipboard(props.invite.code);
+	os.success();
+}
+</script>
+
+<style lang="scss" module>
+.root {
+	text-align: left;
+}
+
+.items {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+	grid-gap: 12px;
+}
+
+.label {
+	font-size: 0.85em;
+	padding: 0 0 8px 0;
+	user-select: none;
+}
+
+.user {
+	display: flex;
+	align-items: center;
+	gap: 8px;
+}
+
+.avatar {
+	--height: 24px;
+	width: var(--height);
+	height: var(--height);
+}
+
+.buttons {
+	display: flex;
+	gap: 8px;
+}
+</style>
-- 
cgit v1.2.3-freya

