mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-23 21:43:17 +00:00
488 lines
20 KiB
Vue
488 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>
|