hippofish/packages/client/src/components/MkSignup.vue

504 lines
12 KiB
Vue
Raw Normal View History

2018-02-10 08:22:14 +01:00
<template>
2023-04-08 02:01:42 +02:00
<form
class="qlvuhzng _formRoot"
autocomplete="new-password"
@submit.prevent="onSubmit"
>
<p>{{ i18n.ts.signupsDisabled }}</p>
<MkButton rounded gradate link to="https://calckey.org/join"
>{{ i18n.ts.findOtherInstance }}
</MkButton>
2023-04-08 02:01:42 +02:00
<MkInput
v-if="instance.disableRegistration"
v-model="invitationCode"
class="_formBlock"
type="text"
:spellcheck="false"
required
data-cy-signup-invitation-code
@update:modelValue="onChangeInvitationCode"
>
<template #label>{{ i18n.ts.invitationCode }}</template>
<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
2022-12-06 07:02:57 +01:00
</MkInput>
2023-04-08 02:01:42 +02:00
<div
v-if="
!instance.disableRegistration ||
(instance.disableRegistration && invitationState === 'entered')
"
>
<MkInput
v-model="username"
class="_formBlock"
type="text"
pattern="^[a-zA-Z0-9_]{1,20}$"
:spellcheck="false"
required
data-cy-signup-username
@update:modelValue="onChangeUsername"
>
<template #label
>{{ i18n.ts.username }}
<div
v-tooltip:dialog="i18n.ts.usernameInfo"
class="_button _help"
>
<i class="ph-question ph-bold"></i></div
></template>
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
<template #caption>
<span v-if="usernameState === 'wait'" style="color: #6e6a86"
><i
class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"
></i>
{{ i18n.ts.checking }}</span
>
<span
v-else-if="usernameState === 'ok'"
style="color: var(--success)"
><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.available }}</span
>
<span
v-else-if="usernameState === 'unavailable'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.unavailable }}</span
>
<span
v-else-if="usernameState === 'error'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.error }}</span
>
<span
v-else-if="usernameState === 'invalid-format'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.usernameInvalidFormat }}</span
>
<span
v-else-if="usernameState === 'min-range'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.tooShort }}</span
>
<span
v-else-if="usernameState === 'max-range'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.tooLong }}</span
>
</template>
</MkInput>
<MkInput
v-if="instance.emailRequiredForSignup"
v-model="email"
class="_formBlock"
:debounce="true"
type="email"
:spellcheck="false"
required
data-cy-signup-email
@update:modelValue="onChangeEmail"
>
<template #label
>{{ i18n.ts.emailAddress }}
<div
v-tooltip:dialog="i18n.ts._signup.emailAddressInfo"
class="_button _help"
>
<i class="ph-question ph-bold"></i></div
></template>
<template #prefix
><i class="ph-envelope-simple-open ph-bold ph-lg"></i
></template>
<template #caption>
<span v-if="emailState === 'wait'" style="color: #6e6a86"
><i
class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"
></i>
{{ i18n.ts.checking }}</span
>
<span
v-else-if="emailState === 'ok'"
style="color: var(--success)"
><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.available }}</span
>
<span
v-else-if="emailState === 'unavailable:used'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts._emailUnavailable.used }}</span
>
<span
v-else-if="emailState === 'unavailable:format'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts._emailUnavailable.format }}</span
>
<span
v-else-if="emailState === 'unavailable:disposable'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts._emailUnavailable.disposable }}</span
>
<span
v-else-if="emailState === 'unavailable:mx'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts._emailUnavailable.mx }}</span
>
<span
v-else-if="emailState === 'unavailable:smtp'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts._emailUnavailable.smtp }}</span
>
<span
v-else-if="emailState === 'unavailable'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.unavailable }}</span
>
<span
v-else-if="emailState === 'error'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.error }}</span
>
2022-12-06 07:02:57 +01:00
</template>
2023-04-08 02:01:42 +02:00
</MkInput>
<MkInput
v-model="password"
class="_formBlock"
type="password"
autocomplete="new-password"
required
data-cy-signup-password
@update:modelValue="onChangePassword"
>
<template #label>{{ i18n.ts.password }}</template>
<template #prefix
><i class="ph-lock ph-bold ph-lg"></i
></template>
<template #caption>
<span
v-if="passwordStrength == 'low'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.weakPassword }}</span
>
<span
v-if="passwordStrength == 'medium'"
style="color: var(--warn)"
><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.normalPassword }}</span
>
<span
v-if="passwordStrength == 'high'"
style="color: var(--success)"
><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.strongPassword }}</span
>
</template>
</MkInput>
<MkInput
v-model="retypedPassword"
class="_formBlock"
type="password"
autocomplete="new-password"
required
data-cy-signup-password-retype
@update:modelValue="onChangePasswordRetype"
>
<template #label
>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template
>
<template #prefix
><i class="ph-lock ph-bold ph-lg"></i
></template>
<template #caption>
<span
v-if="passwordRetypeState == 'match'"
style="color: var(--success)"
><i class="ph-check ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.passwordMatched }}</span
>
<span
v-if="passwordRetypeState == 'not-match'"
style="color: var(--error)"
><i class="ph-warning ph-bold ph-lg ph-fw ph-lg"></i>
{{ i18n.ts.passwordNotMatched }}</span
>
</template>
</MkInput>
<MkSwitch
v-if="instance.tosUrl"
v-model="ToSAgreement"
class="_formBlock tou"
>
<I18n :src="i18n.ts.agreeTo">
<template #0>
<a
:href="instance.tosUrl"
class="_link"
target="_blank"
>{{ i18n.ts.tos }}</a
>
</template>
</I18n>
</MkSwitch>
<MkCaptcha
v-if="instance.enableHcaptcha"
ref="hcaptcha"
v-model="hCaptchaResponse"
class="_formBlock captcha"
provider="hcaptcha"
:sitekey="instance.hcaptchaSiteKey"
/>
<MkCaptcha
v-if="instance.enableRecaptcha"
ref="recaptcha"
v-model="reCaptchaResponse"
class="_formBlock captcha"
provider="recaptcha"
:sitekey="instance.recaptchaSiteKey"
/>
<MkButton
class="_formBlock"
type="submit"
:disabled="shouldDisableSubmitting"
gradate
data-cy-signup-submit
>{{ i18n.ts.start }}</MkButton
>
</div>
</form>
2018-02-10 08:22:14 +01:00
</template>
<script lang="ts" setup>
2023-04-08 02:01:42 +02:00
import {} from "vue";
import getPasswordStrength from "syuilo-password-strength";
import { toUnicode } from "punycode/";
import MkButton from "./MkButton.vue";
import MkInput from "./form/input.vue";
import MkSwitch from "./form/switch.vue";
import MkCaptcha from "@/components/MkCaptcha.vue";
import * as config from "@/config";
import * as os from "@/os";
import { login } from "@/account";
import { instance } from "@/instance";
import { i18n } from "@/i18n";
const props = withDefaults(
defineProps<{
autoSet?: boolean;
}>(),
{
autoSet: false,
}
);
Migrate to Vue3 (#6587) * Update reaction.vue * fix bug * wip * wip * wjio * wip * Revert "wip" This reverts commit e427f2160adf4e8a4147006e25a89854edab0033. * wip * wip * wip * Update init.ts * Update drive-window.vue * wip * wip * Use PascalCase for components * Use PascalCase for components * update dep * wip * wip * wip * Update init.ts * wip * Update paging.ts * Update test.vue * watch deep * wip * lint * wip * wip * wip * wip * wiop * wip * Update webpack.config.ts * alllow null poll * wip * wip * wip * wiop * UI redesign & refactor (#6714) * wip * wip * wip * wip * wip * Update drive.vue * Update word-mute.vue * wip * wip * wip * clean up * wip * Update default.vue * wip * Update notes.vue * Update mfm.ts * Update index.home.vue * Update post-form.vue * Update post-form-attaches.vue * wip * Update post-form.vue * Update sidebar.vue * wip * wip * Update index.vue * wip * Update default.vue * Update index.vue * Update index.vue * wip * Update post-form-attaches.vue * Update note.vue * wip * clean up * Update notes.vue * wip * wip * Update ja-JP.yml * wip * wip * Update index.vue * wip * wip * wip * wip * wip * wip * wip * wip * Update default.vue * wip * Update _dark.json5 * wip * wip * wip * clean up * wip * wip * Update index.vue * Update test.vue * wip * wip * fix * wip * wip * wip * wip * clena yop * wip * wip * Update store.ts * Update messaging-room.vue * Update default.widgets.vue * fix * wip * wip * Update modal.vue * wip * Update os.ts * Update os.ts * Update deck.vue * Update init.ts * wip * Update ja-JP.yml * v-sizeは単にwindowのresizeを監視するだけで良いかもしれない * Update modal.vue * wip * Update tooltip.ts * wip * wip * wip * wip * wip * Update image-viewer.vue * wip * wip * Update style.scss * Update style.scss * Update visitor.vue * wip * Update init.ts * Update init.ts * wip * wip * Update visitor.vue * Update visitor.vue * Update visitor.vue * Update visitor.vue * wip * wip * Update modal.vue * Update header.vue * Update menu.vue * Update about.vue * Update about-misskey.vue * wip * wip * Update visitor.vue * Update tooltip.ts * wip * Update drive.vue * wip * Update style.scss * Update header.vue * wip * wip * Update users.user.vue * Update announcements.vue * wip * wip * wip * Update emojis.vue * wip * Update emojis.vue * Update style.scss * Update users.vue * wip * Update style.scss * wip * Update welcome.entrance.vue * Update radio.vue * Update size.ts * Update emoji-edit-dialog.vue * wip * Update emojis.vue * wip * Update emojis.vue * Update emojis.vue * Update emojis.vue * wip * wip * wip * wip * Update file-dialog.vue * wip * wip * Update token-generate-window.vue * Update notification-setting-window.vue * wip * wip * Update _error_.vue * Update ja-JP.yml * wip * wip * Update store.ts * Update emojis.vue * Update emojis.vue * Update emojis.vue * Update announcements.vue * Update store.ts * wip * Update page-editor.vue * wip * wip * Update modal.vue * wip * Update select-file.ts * Update timeline.vue * Update emojis.vue * Update os.ts * wip * Update user-select.vue * Update mfm.ts * Update get-file-info.ts * Update drive.vue * Update init.ts * Update mfm.ts * wip * wip * Update window.vue * Update note.vue * wip * wip * Update user-info.vue * wip * wip * wip * wip * wip * Update header.vue * Update header.vue * wip * Update explore.vue * wip * wip * wip * Update webpack.config.ts * wip * wip * wip * wip * wip * wip * Update autocomplete.ts * wip * wip * wip * Update toast.vue * wip * Update post-form-dialog.vue * wip * wip * wip * wip * wip * Update users.vue * wip * Update explore.vue * wip * wip * wip * Update package.json * wip * Update icon-dialog.vue * wip * wip * Update user-preview.ts * wip * wip * wip * wip * wip * Update instance.vue * Update user-name.vue * Update federation.vue * Update instance.vue * wip * wip * Update tag.vue * wip * wip * wip * wip * wip * Update instance.vue * wip * Update os.ts * Update os.ts * wip * wip * wip * Update router.ts * wip * Update init.ts * Update note.vue * Update messages.vue * wip * wip * wip * wip * wip * google * wip * wip * wip * wip * Update theme-editor.vue * wip * wip * Update room.vue * Update channel-editor.vue * wip * Update window.vue * Update window.vue * wip * Update window.vue * Update window.vue * wip * Update menu.vue * wip * wip * wip * wip * Update messaging-room.vue * wip * Update post-form.vue * Update default.widgets.vue * Update window.vue * wip
2020-10-17 13:12:00 +02:00
const emit = defineEmits<{
2023-04-08 02:01:42 +02:00
(ev: "signup", user: Record<string, any>): void;
(ev: "signupEmailPending"): void;
}>();
const host = toUnicode(config.host);
let hcaptcha = $ref();
let recaptcha = $ref();
2023-04-08 02:01:42 +02:00
let username: string = $ref("");
let password: string = $ref("");
let retypedPassword: string = $ref("");
let invitationCode: string = $ref("");
let email = $ref("");
let usernameState:
| null
| "wait"
| "ok"
| "unavailable"
| "error"
| "invalid-format"
| "min-range"
| "max-range" = $ref(null);
let invitationState: null | "entered" = $ref(null);
let emailState:
| null
| "wait"
| "ok"
| "unavailable:used"
| "unavailable:format"
| "unavailable:disposable"
| "unavailable:mx"
| "unavailable:smtp"
| "unavailable"
| "error" = $ref(null);
let passwordStrength: "" | "low" | "medium" | "high" = $ref("");
let passwordRetypeState: null | "match" | "not-match" = $ref(null);
let submitting: boolean = $ref(false);
let ToSAgreement: boolean = $ref(false);
let hCaptchaResponse = $ref(null);
let reCaptchaResponse = $ref(null);
const shouldDisableSubmitting = $computed((): boolean => {
2023-04-08 02:01:42 +02:00
return (
submitting ||
(instance.tosUrl && !ToSAgreement) ||
(instance.enableHcaptcha && !hCaptchaResponse) ||
(instance.enableRecaptcha && !reCaptchaResponse) ||
passwordRetypeState === "not-match"
);
});
2018-12-18 16:39:28 +01:00
2022-12-06 07:02:57 +01:00
function onChangeInvitationCode(): void {
2023-04-08 02:01:42 +02:00
if (invitationCode === "") {
2022-12-06 07:02:57 +01:00
invitationState = null;
return;
}
2023-04-08 02:01:42 +02:00
invitationState = "entered";
2022-12-06 07:02:57 +01:00
}
function onChangeUsername(): void {
2023-04-08 02:01:42 +02:00
if (username === "") {
usernameState = null;
return;
}
2020-04-28 07:29:33 +02:00
{
2023-04-08 02:01:42 +02:00
const err = !username.match(/^[a-zA-Z0-9_]+$/)
? "invalid-format"
: username.length < 1
? "min-range"
: username.length > 20
? "max-range"
: null;
if (err) {
usernameState = err;
return;
}
}
2020-04-28 07:29:33 +02:00
2023-04-08 02:01:42 +02:00
usernameState = "wait";
2018-12-18 16:39:28 +01:00
2023-04-08 02:01:42 +02:00
os.api("username/available", {
username,
2023-04-08 02:01:42 +02:00
})
.then((result) => {
usernameState = result.available ? "ok" : "unavailable";
})
.catch(() => {
usernameState = "error";
});
}
2018-02-10 08:22:14 +01:00
function onChangeEmail(): void {
2023-04-08 02:01:42 +02:00
if (email === "") {
emailState = null;
return;
}
2018-02-10 08:22:14 +01:00
2023-04-08 02:01:42 +02:00
emailState = "wait";
2023-04-08 02:01:42 +02:00
os.api("email-address/available", {
emailAddress: email,
2023-04-08 02:01:42 +02:00
})
.then((result) => {
emailState = result.available
? "ok"
: result.reason === "used"
? "unavailable:used"
: result.reason === "format"
? "unavailable:format"
: result.reason === "disposable"
? "unavailable:disposable"
: result.reason === "mx"
? "unavailable:mx"
: result.reason === "smtp"
? "unavailable:smtp"
: "unavailable";
})
.catch(() => {
emailState = "error";
});
}
2018-02-10 08:22:14 +01:00
function onChangePassword(): void {
2023-04-08 02:01:42 +02:00
if (password === "") {
passwordStrength = "";
return;
}
2018-02-10 08:22:14 +01:00
const strength = getPasswordStrength(password);
2023-04-08 02:01:42 +02:00
passwordStrength =
strength > 0.7 ? "high" : strength > 0.3 ? "medium" : "low";
}
2018-12-18 16:39:28 +01:00
function onChangePasswordRetype(): void {
2023-04-08 02:01:42 +02:00
if (retypedPassword === "") {
passwordRetypeState = null;
return;
}
2023-04-08 02:01:42 +02:00
passwordRetypeState = password === retypedPassword ? "match" : "not-match";
}
function onSubmit(): void {
if (submitting) return;
submitting = true;
2023-04-08 02:01:42 +02:00
os.api("signup", {
username,
password,
emailAddress: email,
invitationCode,
2023-04-08 02:01:42 +02:00
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
})
.then(() => {
if (instance.emailRequiredForSignup) {
os.alert({
type: "success",
title: i18n.ts._signup.almostThere,
text: i18n.t("_signup.emailSent", { email }),
});
emit("signupEmailPending");
} else {
os.api("signin", {
username,
password,
}).then((res) => {
emit("signup", res);
if (props.autoSet) {
login(res.i);
}
});
}
})
.catch(() => {
submitting = false;
hcaptcha.reset?.();
recaptcha.reset?.();
os.alert({
2023-04-08 02:01:42 +02:00
type: "error",
text: i18n.ts.somethingHappened,
});
});
}
2018-02-10 09:01:32 +01:00
</script>
2020-04-29 02:15:18 +02:00
<style lang="scss" scoped>
2021-08-22 09:15:40 +02:00
.qlvuhzng {
2020-04-29 02:15:18 +02:00
.captcha {
margin: 16px 0;
}
}
</style>