pixelfed/resources/assets/components/invite/AdminInvite.vue
2022-12-29 04:46:18 -07:00

489 lines
20 KiB
Vue

<template>
<div class="admin-invite-component">
<div class="admin-invite-component-inner">
<div class="card bg-dark">
<div v-if="tabIndex === 0" class="card-body d-flex align-items-center justify-content-center">
<div class="text-center">
<b-spinner variant="muted" />
<p class="text-muted mb-0">Loading...</p>
</div>
</div>
<div v-else-if="tabIndex === 1" class="card-body">
<div class="d-flex justify-content-center my-3">
<img src="/img/pixelfed-icon-color.png" width="60" alt="Pixelfed logo" />
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<p class="lead mb-1 text-muted">You've been invited to join</p>
<p class="h3 mb-2">{{ instance.uri }}</p>
<p class="mb-0 text-muted">
<span>{{ instance.stats.user_count.toLocaleString('en-CA', { compactDisplay: "short", notation: "compact"}) }} users</span>
<span>·</span>
<span>{{ instance.stats.status_count.toLocaleString('en-CA', { compactDisplay: "short", notation: "compact"}) }} posts</span>
</p>
<div v-if="inviteConfig.message != 'You\'ve been invited to join'">
<div class="admin-message">
<p class="small text-light mb-0">Message from admin(s):</p>
{{ inviteConfig.message }}
</div>
</div>
</div>
<div class="mt-5">
<div class="form-group">
<label for="username">Username</label>
<input
type="text"
class="form-control form-control-lg"
placeholder="What should everyone call you?"
minlength="2"
maxlength="15"
v-model="form.username" />
<p v-if="errors.username" class="form-text text-danger">
<i class="far fa-exclamation-triangle mr-1"></i>
{{ errors.username }}
</p>
</div>
<button
class="btn btn-primary btn-block font-weight-bold"
@click="proceed(tabIndex)"
:disabled="isProceeding || !form.username || form.username.length < 2">
<template v-if="isProceeding">
<b-spinner small />
</template>
<template v-else>
Continue
</template>
</button>
<p class="login-link">
<a href="/login">Already have an account?</a>
</p>
<p class="register-terms">
By registering, you agree to our <a href="/site/terms">Terms of Service</a> and <a href="/site/privacy">Privacy Policy</a>.
</p>
</div>
</div>
<div v-else-if="tabIndex === 2" class="card-body">
<div class="d-flex justify-content-center my-3">
<img src="/img/pixelfed-icon-color.png" width="60" alt="Pixelfed logo" />
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<p class="lead mb-1 text-muted">You've been invited to join</p>
<p class="h3 mb-2">{{ instance.uri }}</p>
</div>
<div class="mt-5">
<div class="form-group">
<label for="username">Email Address</label>
<input
type="email"
class="form-control form-control-lg"
placeholder="Your email address"
v-model="form.email" />
<p v-if="errors.email" class="form-text text-danger">
<i class="far fa-exclamation-triangle mr-1"></i>
{{ errors.email }}
</p>
</div>
<button
class="btn btn-primary btn-block font-weight-bold"
@click="proceed(tabIndex)"
:disabled="isProceeding || !form.email || !validateEmail()">
<template v-if="isProceeding">
<b-spinner small />
</template>
<template v-else>
Continue
</template>
</button>
</div>
</div>
<div v-else-if="tabIndex === 3" class="card-body">
<div class="d-flex justify-content-center my-3">
<img src="/img/pixelfed-icon-color.png" width="60" alt="Pixelfed logo" />
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<p class="lead mb-1 text-muted">You've been invited to join</p>
<p class="h3 mb-2">{{ instance.uri }}</p>
</div>
<div class="mt-5">
<div class="form-group">
<label for="username">Password</label>
<input
type="password"
class="form-control form-control-lg"
placeholder="Use a secure password"
minlength="8"
v-model="form.password" />
<p v-if="errors.password" class="form-text text-danger">
<i class="far fa-exclamation-triangle mr-1"></i>
{{ errors.password }}
</p>
</div>
<button
class="btn btn-primary btn-block font-weight-bold"
@click="proceed(tabIndex)"
:disabled="isProceeding || !form.password || form.password.length < 8">
<template v-if="isProceeding">
<b-spinner small />
</template>
<template v-else>
Continue
</template>
</button>
</div>
</div>
<div v-else-if="tabIndex === 4" class="card-body">
<div class="d-flex justify-content-center my-3">
<img src="/img/pixelfed-icon-color.png" width="60" alt="Pixelfed logo" />
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<p class="lead mb-1 text-muted">You've been invited to join</p>
<p class="h3 mb-2">{{ instance.uri }}</p>
</div>
<div class="mt-5">
<div class="form-group">
<label for="username">Confirm Password</label>
<input
type="password"
class="form-control form-control-lg"
placeholder="Use a secure password"
minlength="8"
v-model="form.password_confirm" />
<p v-if="errors.password_confirm" class="form-text text-danger">
<i class="far fa-exclamation-triangle mr-1"></i>
{{ errors.password_confirm }}
</p>
</div>
<button
class="btn btn-primary btn-block font-weight-bold"
@click="proceed(tabIndex)"
:disabled="isProceeding || !form.password_confirm || form.password !== form.password_confirm">
<template v-if="isProceeding">
<b-spinner small />
</template>
<template v-else>
Continue
</template>
</button>
</div>
</div>
<div v-else-if="tabIndex === 5" class="card-body">
<div class="d-flex justify-content-center my-3">
<img src="/img/pixelfed-icon-color.png" width="60" alt="Pixelfed logo" />
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<p class="lead mb-1 text-muted">You've been invited to join</p>
<p class="h3 mb-2">{{ instance.uri }}</p>
</div>
<div class="mt-5">
<div class="form-group">
<label for="username">Display Name</label>
<input
type="text"
class="form-control form-control-lg"
placeholder="Add an optional display name"
minlength="8"
v-model="form.display_name" />
<p v-if="errors.display_name" class="form-text text-danger">
<i class="far fa-exclamation-triangle mr-1"></i>
{{ errors.display_name }}
</p>
</div>
<button
class="btn btn-primary btn-block font-weight-bold"
@click="proceed(tabIndex)"
:disabled="isProceeding">
<template v-if="isProceeding">
<b-spinner small />
</template>
<template v-else>
Continue
</template>
</button>
</div>
</div>
<div v-else-if="tabIndex === 6" class="card-body d-flex flex-column">
<div class="d-flex justify-content-center my-3">
<img src="/img/pixelfed-icon-color.png" width="60" alt="Pixelfed logo" />
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<p class="lead mb-1 text-muted">You've been invited to join</p>
<p class="h3 mb-2">{{ instance.uri }}</p>
</div>
<div class="mt-5 d-flex align-items-center justify-content-center flex-column flex-grow-1">
<b-spinner variant="muted" />
<p class="text-muted">Registering...</p>
</div>
</div>
<div v-else-if="tabIndex === 'invalid-code'" class="card-body d-flex align-items-center justify-content-center">
<div>
<h1 class="text-center">Invalid Invite Code</h1>
<hr>
<p class="text-muted mb-1">The invite code you were provided is not valid, this can happen when:</p>
<ul class="text-muted">
<li>Invite code has typos</li>
<li>Invite code was already used</li>
<li>Invite code has reached max uses</li>
<li>Invite code has expired</li>
<li>You have been rate limited</li>
</ul>
<hr>
<a href="/" class="btn btn-primary btn-block rounded-pill font-weight-bold">Go back home</a>
</div>
</div>
<div v-else class="card-body">
<p>An error occured.</p>
</div>
</div>
</div>
</div>
</template>
<script type="text/javascript">
export default {
props: ['code'],
data() {
return {
instance: {},
inviteConfig: {},
tabIndex: 0,
isProceeding: false,
errors: {
username: undefined,
email: undefined,
password: undefined,
password_confirm: undefined
},
form: {
username: undefined,
email: undefined,
password: undefined,
password_confirm: undefined,
display_name: undefined,
}
}
},
mounted() {
this.fetchInstanceData();
},
methods: {
fetchInstanceData() {
axios.get('/api/v1/instance')
.then(res => {
this.instance = res.data;
})
.then(res => {
this.verifyToken();
})
.catch(err => {
console.log(err);
})
},
verifyToken() {
axios.post('/api/v1.1/auth/invite/admin/verify', {
token: this.code,
})
.then(res => {
this.tabIndex = 1;
this.inviteConfig = res.data;
})
.catch(err => {
this.tabIndex = 'invalid-code';
})
},
checkUsernameAvailability() {
axios.post('/api/v1.1/auth/invite/admin/uc', {
token: this.code,
username: this.form.username
})
.then(res => {
if(res && res.data) {
this.isProceeding = false;
this.tabIndex = 2;
} else {
this.tabIndex = 'invalid-code';
this.isProceeding = false;
}
})
.catch(err => {
if(err.response.data && err.response.data.username) {
this.errors.username = err.response.data.username[0];
this.isProceeding = false;
} else {
this.tabIndex = 'invalid-code';
this.isProceeding = false;
}
})
},
checkEmailAvailability() {
axios.post('/api/v1.1/auth/invite/admin/ec', {
token: this.code,
email: this.form.email
})
.then(res => {
if(res && res.data) {
this.isProceeding = false;
this.tabIndex = 3;
} else {
this.tabIndex = 'invalid-code';
this.isProceeding = false;
}
})
.catch(err => {
if(err.response.data && err.response.data.email) {
this.errors.email = err.response.data.email[0];
this.isProceeding = false;
} else {
this.tabIndex = 'invalid-code';
this.isProceeding = false;
}
})
},
validateEmail() {
if(!this.form.email || !this.form.email.length) {
return false;
}
return /^[a-zA-Z]+[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+[a-zA-Z]$/i.test(this.form.email);
},
handleRegistration() {
var $form = $('<form>', {
action: '/api/v1.1/auth/invite/admin/re',
method: 'post'
});
let fields = {
'_token': document.head.querySelector('meta[name="csrf-token"]').content,
token: this.code,
username: this.form.username,
name: this.form.display_name,
email: this.form.email,
password: this.form.password,
password_confirm: this.form.password_confirm
};
$.each(fields, function(key, val) {
$('<input>').attr({
type: "hidden",
name: key,
value: val
}).appendTo($form);
});
$form.appendTo('body').submit();
},
proceed(cur) {
this.isProceeding = true;
event.currentTarget.blur();
switch(cur) {
case 1:
this.checkUsernameAvailability();
break;
case 2:
this.checkEmailAvailability();
break;
case 3:
this.isProceeding = false;
this.tabIndex = 4;
break;
case 4:
this.isProceeding = false;
this.tabIndex = 5;
break;
case 5:
this.tabIndex = 6;
this.handleRegistration();
break;
}
}
}
}
</script>
<style lang="scss">
.admin-invite-component {
font-family: var(--font-family-sans-serif);
&-inner {
display: flex;
width: 100wv;
height: 100vh;
justify-content: center;
align-items: center;
.card {
width: 100%;
color: #fff;
padding: 1.25rem 2.5rem;
border-radius: 10px;
min-height: 530px;
@media(min-width: 768px) {
width: 30%;
}
label {
color: var(--muted);
font-weight: bold;
text-transform: uppercase;
}
.login-link {
margin-top: 10px;
font-weight: 600;
}
.register-terms {
font-size: 12px;
color: var(--muted);
}
.form-control {
color: #fff;
}
.admin-message {
margin-top: 20px;
border: 1px solid var(--dropdown-item-hover-color);
color: var(--text-lighter);
padding: 1rem;
border-radius: 5px;
}
}
}
}
</style>