Feat: #10 サインアップ / Chg: server-infoでconfigの取得にconfigRepo.get()を使うように / Fix: アカウントが無効でもコミュニティを作成ボタンがある問題
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="welcome">
|
||||
<p><!--
|
||||
-->ここでは、サインアップを行います。<!--
|
||||
-->アカウントは、端末に1つのものではなく、IDとパスワードでどんな端末でも使いまわすことが出来ます。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form novalidate @submit="submit">
|
||||
<Input
|
||||
label="ユーザーID"
|
||||
autocomplete="username"
|
||||
v-model="userid"
|
||||
>
|
||||
<span
|
||||
class="input-issue"
|
||||
v-if="useridIssue"
|
||||
>
|
||||
<Icon icon="material-symbols:error-outline-rounded" />
|
||||
{{ useridIssue.message }}
|
||||
</span>
|
||||
</Input>
|
||||
|
||||
<Input
|
||||
label="メールアドレス"
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
v-model="email"
|
||||
>
|
||||
<span
|
||||
class="input-issue"
|
||||
v-if="emailIssue"
|
||||
>
|
||||
<Icon icon="material-symbols:error-outline-rounded" />
|
||||
{{ emailIssue.message }}
|
||||
</span>
|
||||
</Input>
|
||||
|
||||
<InputPassword
|
||||
label="パスワード"
|
||||
v-model="password"
|
||||
>
|
||||
<span
|
||||
class="input-issue"
|
||||
v-if="passwordIssue"
|
||||
>
|
||||
<Icon icon="material-symbols:error-outline-rounded" />
|
||||
{{ passwordIssue.message }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="input-issue password-strength"
|
||||
v-if="passwordStrength.message"
|
||||
>
|
||||
<Icon :icon="passwordStrength.icon" />
|
||||
{{ passwordStrength.message }}
|
||||
</span>
|
||||
</InputPassword>
|
||||
|
||||
<Button
|
||||
name="サインアップ"
|
||||
type="submit"
|
||||
color="accent"
|
||||
:disabled="!result.success || isProcessing"
|
||||
/>
|
||||
|
||||
<RouterLink to="/signin">アカウントをお持ちですか?</RouterLink>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
max-width: 30rem;
|
||||
}
|
||||
|
||||
.input-issue {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--error-color);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.input-issue svg {
|
||||
font-size: 1rem;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.password-strength {
|
||||
color: v-bind(passwordStrengthColor);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { serverInfo, reloadServerInfo } from "@/lib/account";
|
||||
import { useRouter } from "vue-router";
|
||||
import Input from "@/components/Input.vue";
|
||||
import Button from "@/components/Button.vue";
|
||||
import { ref } from "vue";
|
||||
import z from "zod/v3";
|
||||
import { computed } from "vue";
|
||||
import zxcvbn from "zxcvbn";
|
||||
import { createModal } from "@/lib/modal";
|
||||
import Loading from "@/components/Modal/Loading.vue";
|
||||
import ErrorModal from "@/components/Modal/Error.vue";
|
||||
import Success from "@/components/Modal/Success.vue";
|
||||
import InputPassword from "@/components/InputPassword.vue";
|
||||
import client from "@/lib/client";
|
||||
import { getIssueFromPath } from "@/lib/validation";
|
||||
import { Icon } from "@iconify/vue";
|
||||
|
||||
const router = useRouter();
|
||||
const isProcessing = ref<boolean>(false);
|
||||
|
||||
if (!serverInfo.value?.success) {
|
||||
throw new Error("サーバー情報の取得に失敗しました。");
|
||||
}
|
||||
|
||||
if (!serverInfo.value.isInitialized) {
|
||||
router.replace("/setup/initialization");
|
||||
}
|
||||
|
||||
const userid = ref<string>("");
|
||||
const email = ref<string>("");
|
||||
const password = ref<string>("");
|
||||
const useridIssue = computed(() => getIssueFromPath("userid", result.value));
|
||||
const emailIssue = computed(() => getIssueFromPath("email", result.value));
|
||||
const passwordIssue = computed(() => getIssueFromPath("password", result.value));
|
||||
|
||||
const passwordScores = [
|
||||
["危険なパスワード", "error-outline-rounded", "var(--error-color)"],
|
||||
["推測可能なパスワード", "warning-rounded", "var(--warn-color)"],
|
||||
["推測しやすいパスワード", "warning-rounded", "var(--warn-color)"],
|
||||
["安全なパスワード", "check-circle", "var(--success-color)"],
|
||||
["強力なパスワード", "check-circle", "var(--success-color)"],
|
||||
];
|
||||
const passwordStrength = computed(() => {
|
||||
const evaluation = zxcvbn(password.value);
|
||||
return {
|
||||
message: passwordScores[evaluation.score]![0],
|
||||
score: evaluation.score,
|
||||
icon: "material-symbols:" + passwordScores[evaluation.score]![1],
|
||||
color: passwordScores[evaluation.score]![2],
|
||||
}
|
||||
});
|
||||
const passwordStrengthColor = computed(() => passwordStrength.value.color);
|
||||
|
||||
const EmailRegex = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:([0-9]{1,3}\.){3}[0-9]{1,3})\])$/i;
|
||||
const schema = z.object({
|
||||
userid: z.string({ message: "文字列で入力してください。" })
|
||||
.trim().min(3, "3文字以上で入力してください。")
|
||||
.max(20, "20文字以内で入力してください。"),
|
||||
email: z.string({ message: "文字列で入力してください。" })
|
||||
.trim().min(6, "6文字以上で入力してください。")
|
||||
.max(254, "254文字以内で入力してください。")
|
||||
.regex(EmailRegex, "形式が異なります。"),
|
||||
password: z.string({ message: "文字列で入力してください。" })
|
||||
.trim().min(8, "8文字以上で入力してください。"),
|
||||
});
|
||||
|
||||
const result = computed(() => schema.safeParse({
|
||||
userid: userid.value,
|
||||
email: email.value,
|
||||
password: password.value,
|
||||
}));
|
||||
|
||||
const submit = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
isProcessing.value = true;
|
||||
|
||||
const closeLoadingModal = createModal({
|
||||
component: Loading,
|
||||
});
|
||||
|
||||
if (!result.value.success) {
|
||||
const messages = result.value.error.issues.map(issue => issue.message);
|
||||
|
||||
closeLoadingModal();
|
||||
|
||||
return createModal({
|
||||
component: ErrorModal,
|
||||
onClose: () => isProcessing.value = false,
|
||||
props: {
|
||||
error: `不正な入力です。<br>${messages.join("<br>")}`,
|
||||
canClose: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const response = await client.value.request("setup/create-admin", result.value.data);
|
||||
|
||||
if (!response.success) {
|
||||
closeLoadingModal();
|
||||
|
||||
return createModal({
|
||||
component: ErrorModal,
|
||||
onClose: () => isProcessing.value = false,
|
||||
props: {
|
||||
error: response.error.message,
|
||||
canClose: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
closeLoadingModal();
|
||||
|
||||
return createModal({
|
||||
component: Success,
|
||||
onClose: async () => {
|
||||
isProcessing.value = false;
|
||||
await reloadServerInfo();
|
||||
router.push("/signin");
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user