Base Login
This commit is contained in:
150
pages/account/auth.vue
Normal file
150
pages/account/auth.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div class="columns is-centered mt-6 mx-3">
|
||||
<div class="column is-5 mx-0">
|
||||
<Logo class="mb-5"></Logo>
|
||||
<template v-if="!userAuth">
|
||||
<article class="message is-primary">
|
||||
<div class="message-body has-background-white py-3">
|
||||
<strong> Mã xác thực tài khoản </strong> là chuỗi gồm <strong> 9 kí tự, </strong> chúng tôi đã gửi cho bạn
|
||||
qua email <b>{{ $route.query.email ? ' ' + $route.query.email : '' }}</b
|
||||
>. Hãy nhập dãy số đó vào ô dưới đây. Hoặc click vào đường link có trong email.
|
||||
</div>
|
||||
</article>
|
||||
<div class="field mt-4">
|
||||
<div class="control">
|
||||
<input class="input is-primary" type="text" placeholder="Nhập mã xác thực" v-model="code" id="inputcode" />
|
||||
</div>
|
||||
<p class="help is-danger mt5 fs13" v-if="errors.find((v) => v.name === 'code')">
|
||||
{{ errors.find((v) => v.name === 'code').text }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field mt-5">
|
||||
<p class="control">
|
||||
<a class="button is-primary" @click="checkCode()">Xác thực tài khoản</a>
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-5 pt-4 has-text-danger" v-if="$route.query.id">
|
||||
<span>Không nhận được mã xác thực?</span>
|
||||
<button :class="`button is-dark is-light ${loading ? 'is-loading' : ''} ml-4`" @click="sendCode()">
|
||||
Gửi lại mã
|
||||
</button>
|
||||
</p>
|
||||
</template>
|
||||
<template v-else-if="userAuth">
|
||||
<article class="message" :class="success ? 'is-primary' : 'is-danger'" v-if="success !== undefined">
|
||||
<div class="message-body has-background-white py-3">
|
||||
{{ message }}
|
||||
</div>
|
||||
</article>
|
||||
<div class="field mt-5" v-if="action">
|
||||
<p class="control">
|
||||
<nuxt-link class="button is-primary" :to="action.to">
|
||||
{{ action.text }}
|
||||
</nuxt-link>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
code: undefined,
|
||||
errors: [],
|
||||
userAuth: undefined,
|
||||
message: undefined,
|
||||
success: undefined,
|
||||
action: undefined,
|
||||
code: undefined,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.$route.query.code) {
|
||||
this.code = this.$route.query.code;
|
||||
this.checkCode();
|
||||
} else if (this.$route.query.action === 'sendcode') this.sendCode();
|
||||
},
|
||||
mounted() {
|
||||
let doc = document.getElementById('inputcode');
|
||||
if (doc) doc.focus();
|
||||
window.addEventListener('keyup', (ev) => (ev.key === 'Enter' && !this.success ? this.checkCode() : false));
|
||||
},
|
||||
computed: {
|
||||
login: {
|
||||
get: function () {
|
||||
return this.$store.state.login;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateLogin', { login: val });
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async checkCode() {
|
||||
this.success = undefined;
|
||||
this.message = undefined;
|
||||
this.action = undefined;
|
||||
this.errors = [];
|
||||
if (this.$empty(this.code)) this.errors.push({ name: 'code', text: 'Mã xác thực không được bỏ trống' });
|
||||
else if (this.code.length !== 9) this.errors.push({ name: 'code', text: 'Mã xác thực phải là 9 kí tự' });
|
||||
if (this.errors.length > 0) return;
|
||||
let found = { name: 'userauth', url: 'data/User_Auth', params: { filter: { code: this.code } } };
|
||||
let result = await this.$getapi([found]);
|
||||
let data = result[0].data.rows;
|
||||
if (data.length > 0) {
|
||||
this.userAuth = data[0];
|
||||
if (this.userAuth.expiry) {
|
||||
this.message = 'Bạn đã xác thực tài khoản thành công.';
|
||||
this.success = true;
|
||||
this.action = { name: 'signin', to: { path: '/signin' }, text: 'Đi tới trang đăng nhập' };
|
||||
} else {
|
||||
let found = { name: 'user', url: 'data/User', params: { filter: { id: this.userAuth.user } } };
|
||||
result = await this.$getapi([found]);
|
||||
let user = result[0].data.rows[0];
|
||||
user.auth_status = 2;
|
||||
user.update_time = new Date();
|
||||
result = await this.$updateapi('user', user);
|
||||
this.processAuth(result);
|
||||
}
|
||||
} else this.errors.push({ name: 'code', text: 'mã xác thực không hợp lệ' });
|
||||
},
|
||||
async processAuth(newVal) {
|
||||
if (newVal !== 'error') {
|
||||
this.message = 'Xác thực tài khoản thành công.';
|
||||
this.success = true;
|
||||
this.action = { name: 'signin', to: { path: '/signin' }, text: 'Đi tới trang đăng nhập' };
|
||||
this.userAuth.expiry = true;
|
||||
this.userAuth.update_time = new Date();
|
||||
let result = await this.$updateapi('userauth', this.userAuth);
|
||||
} else if (newVal === false) {
|
||||
this.message = 'Có lỗi xẩy ra. Xác thực tài khoản thành công.';
|
||||
this.success = false;
|
||||
}
|
||||
},
|
||||
async sendCode() {
|
||||
let code = this.$id();
|
||||
let data = { user: this.$route.query.id, code: code };
|
||||
let result = await this.$insertapi('userauth', data);
|
||||
let query = this.$store.state.link ? { code: code, link: this.$store.state.link } : { code: code };
|
||||
let routeData = this.$router.resolve({ path: '/account/auth', query: query });
|
||||
let path = window.location.origin + routeData.href;
|
||||
let conn = this.$findapi('notiform');
|
||||
conn.params.filter = { code: 'account-auth' };
|
||||
result = await this.$getapi([conn]);
|
||||
let msg = result[0].data.rows[0].detail;
|
||||
msg = msg.replace(' [1]', '');
|
||||
msg = msg.replace('[2]', code);
|
||||
msg = msg.replace('[3]', path);
|
||||
data = { subject: 'Xác thực tài khoản BigDataTechCloud', content: msg, to: this.$route.query.email, sender: 2 };
|
||||
this.loading = true;
|
||||
result = await this.$insertapi('sendemailnow', data);
|
||||
let text = `Hãy mở email <b>${this.$route.query.email}</b> để nhận mã xác thực`;
|
||||
this.$dialog(text, 'Mã xác thực', undefined, 10);
|
||||
this.loading = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
194
pages/account/recovery.vue
Normal file
194
pages/account/recovery.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div class="columns is-centered mt-6 mx-0">
|
||||
<div class="column is-5">
|
||||
<div class="mb-5">
|
||||
<Logo></Logo>
|
||||
</div>
|
||||
<section class="hero">
|
||||
<div class="hero-body px-3 pt-3">
|
||||
<template v-if="!action">
|
||||
<article class="message is-primary">
|
||||
<div class="message-body has-background-white py-1">
|
||||
<strong> Để lấy lại mật khẩu </strong> vui lòng nhập email và mã kiểm tra. Nếu thông tin là hợp lệ chúng
|
||||
tôi sẽ gửi cho bạn một email chứa đường link để thay đổi mật khẩu.
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="field is-horizontal mt-2">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label has-text-dark">Nhập email</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input is-primary"
|
||||
type="text"
|
||||
placeholder="Email đã dùng để mở tài khoản"
|
||||
v-model="email"
|
||||
ref="inputcode"
|
||||
@change="checkInfo()"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'email')">
|
||||
{{ errors.find((v) => v.name === 'email').text }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<label class="label has-text-dark"> Mã kiểm tra : {{ refcode }} </label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input is-primary"
|
||||
type="text"
|
||||
:placeholder="'Nhập ' + refcode + ' vào đây'"
|
||||
v-model="code"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'code')">
|
||||
{{ errors.find((v) => v.name === 'code').text }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field mt-4" v-if="isPhone">
|
||||
<label class="label has-text-dark"> Vui lòng cung cấp email để nhận link </label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input is-primary"
|
||||
type="text"
|
||||
placeholder="Nhập email"
|
||||
v-model="email"
|
||||
@change="checkEmail()"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger mt5 fs13" v-if="errors.find((v) => v.name === 'email')">
|
||||
{{ errors.find((v) => v.name === 'email').text }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field mt-5">
|
||||
<p class="control">
|
||||
<a class="button is-primary" :class="loading ? 'is-loading' : ''" @click="getPassword()">
|
||||
Lấy lại mật khẩu</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<article class="message" :class="success ? 'is-primary' : 'is-danger'" v-if="success !== undefined">
|
||||
<div class="message-body fs18 has-background-white py-2" v-html="message"></div>
|
||||
</article>
|
||||
<div class="field mt-5">
|
||||
<p class="control">
|
||||
<nuxt-link class="button is-primary" :to="action.to">
|
||||
{{ action.text }}
|
||||
</nuxt-link>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
code: undefined,
|
||||
errors: [],
|
||||
user: undefined,
|
||||
message: undefined,
|
||||
success: undefined,
|
||||
action: undefined,
|
||||
code: undefined,
|
||||
refcode: undefined,
|
||||
username: undefined,
|
||||
isPhone: false,
|
||||
email: undefined,
|
||||
authcode: undefined,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.refcode = this.$id().substring(0, 4);
|
||||
if (this.$refs.inputcode) this.$refs.inputcode.focus();
|
||||
window.addEventListener('keyup', (ev) => (ev.key === 'Enter' && !this.success ? this.getPassword() : false));
|
||||
},
|
||||
methods: {
|
||||
checkInfo() {
|
||||
this.errors = [];
|
||||
if (!this.$empty(this.username)) this.username = this.username.trim().toLowerCase();
|
||||
let result = this.$errEmail(this.username);
|
||||
if (result) this.errors.push({ name: 'username', text: 'Email không hợp lệ' });
|
||||
else this.isPhone = this.username.indexOf('@') >= 0 ? false : true;
|
||||
},
|
||||
checkEmail() {
|
||||
this.errors = [];
|
||||
let result = this.$errEmail(this.email);
|
||||
if (result) this.errors.push({ name: 'email', text: 'Email không hợp lệ' });
|
||||
},
|
||||
async getPassword() {
|
||||
this.success = undefined;
|
||||
this.message = undefined;
|
||||
this.action = undefined;
|
||||
this.errors = [];
|
||||
let result = this.$errEmail(this.username);
|
||||
if (result) this.errors.push({ name: 'username', text: 'Email không hợp lệ' });
|
||||
if (this.$empty(this.code)) this.errors.push({ name: 'code', text: 'Chưa nhập mã kiểm tra' });
|
||||
else if (this.refcode !== this.code) this.errors.push({ name: 'code', text: 'Mã kiểm tra không đúng' });
|
||||
if (this.errors.length > 0) return;
|
||||
let found = {
|
||||
name: 'user',
|
||||
url: 'data/User/',
|
||||
params: {
|
||||
filter: {
|
||||
email: this.username,
|
||||
},
|
||||
},
|
||||
};
|
||||
result = await this.$getapi([found]);
|
||||
console.log('===>', result);
|
||||
|
||||
let data = result[0].data.rows;
|
||||
if (data.length > 0) {
|
||||
this.user = data[0];
|
||||
this.authcode = this.$id();
|
||||
let ele = { user: this.user.id, code: this.authcode };
|
||||
result = await this.$insertapi('accountrecovery', ele);
|
||||
|
||||
console.log('=====>', result);
|
||||
|
||||
this.sendEmail(result);
|
||||
} else {
|
||||
this.errors.push({ name: 'username', text: 'Tài khoản không tồn tại' });
|
||||
}
|
||||
},
|
||||
async sendEmail(data) {
|
||||
let query = { id: this.user.id, code: this.authcode };
|
||||
if (this.$store.state.link) query.link = this.$store.state.link;
|
||||
let routeData = this.$router.resolve({ path: '/get-password', query: query });
|
||||
let path = window.location.origin + routeData.href;
|
||||
let conn = this.$findapi('notiform');
|
||||
console.log('conn =====>', conn);
|
||||
|
||||
conn.params.filter = { code: 'get-password' };
|
||||
let result = await this.$getapi([conn]);
|
||||
let msg = result[0].data.rows[0].detail;
|
||||
msg = msg.replace('[1]', this.user.fullname);
|
||||
msg = msg.replace('[3]', path);
|
||||
data = {
|
||||
subject: 'Phục hồi tài khoản BigDataTech.vn',
|
||||
content: msg,
|
||||
to: this.email ? this.email : this.username,
|
||||
sender: 2,
|
||||
};
|
||||
this.loading = true;
|
||||
result = await this.$insertapi('sendemailnow', data);
|
||||
this.message = `<b>Thành công</b>. Hãy mở email <b>${this.username}</b> để lấy lại mật khẩu.`;
|
||||
this.success = true;
|
||||
this.action = { name: 'signin', to: { path: '/signin' }, text: 'Đi tới trang đăng nhập' };
|
||||
this.loading = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
8
pages/index.vue
Normal file
8
pages/index.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template></template>
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.$router.push('/signin');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
22
pages/notice.vue
Normal file
22
pages/notice.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="columns is-centered mt-6 mx-3">
|
||||
<div class="column is-6">
|
||||
<div class="mb-5">
|
||||
<Logo></Logo>
|
||||
</div>
|
||||
<section class="hero">
|
||||
<div class="hero-body px-3 pt-3">
|
||||
<article class="message is-primary">
|
||||
<div class="message-body fs-16 has-background-white">
|
||||
<p><strong>Website của chúng tôi đang trong quá trình xây dựng. Vui lòng trở lại sau</strong></p>
|
||||
<p class="mt-4">Xin cảm ơn.</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {};
|
||||
</script>
|
||||
146
pages/policy.vue
Normal file
146
pages/policy.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div class="column user-policy is-4 p-0" style="margin: auto">
|
||||
<div class="user-part_head">
|
||||
<div class="user-part_head-btn" @click="getback()">
|
||||
<i class="mdi mdi-chevron-left"></i>
|
||||
</div>
|
||||
<div class="user-part_head-text">
|
||||
<p>Điều khoản và dịch vụ</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="policy_body px-3">
|
||||
<div class="policy_body-title1">
|
||||
<p>CÁC ĐIỀU KHOẢN DỊCH VỤ BÊN DƯỚI ĐÂY CÓ HIỆU LỰC CHO CẢ KHÁCH HÀNG CHÍNH THỨC VÀ KHÁCH HÀNG DÙNG THỬ</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Nội dung Điều khoản và Chính sách đang được cập nhật để đảm bảo tính đầy đủ và chính xác. Chúng tôi sẽ sớm hoàn
|
||||
thiện trong thời gian tới.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
getback() {
|
||||
this.$router.push('/signup');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.user-policy_body {
|
||||
padding: 0 20px;
|
||||
}
|
||||
.user-policy_body-title {
|
||||
margin-top: 40px;
|
||||
padding-bottom: 10px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #09b412;
|
||||
position: relative;
|
||||
}
|
||||
.user-policy_body-title::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #eee;
|
||||
bottom: 0px;
|
||||
}
|
||||
.user-pocily_body-content {
|
||||
padding-top: 40px;
|
||||
}
|
||||
.user-pocily_body-content-1 {
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.user-pocily_body-content-2 {
|
||||
font-weight: 600;
|
||||
}
|
||||
.user-pocily_body-content-3 {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.user-pocily_body-content-4 {
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.policy {
|
||||
margin: auto;
|
||||
}
|
||||
.policy_header {
|
||||
font-weight: 700;
|
||||
font-size: 40px;
|
||||
color: #09b412;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.policy_body {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.policy_body-title1 {
|
||||
text-align: center;
|
||||
margin: 10px 10px 0;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
}
|
||||
.policy_body-title2 {
|
||||
font-weight: 600;
|
||||
margin: 50px 0 20px;
|
||||
}
|
||||
.policy_body-sub span {
|
||||
font-weight: 600;
|
||||
}
|
||||
.policy_body-sub p {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
/* ____________________________user_______________________________ */
|
||||
.user-part_head {
|
||||
height: 70px;
|
||||
position: relative;
|
||||
background-color: rgba(9, 180, 18, 1);
|
||||
margin-bottom: 16px;
|
||||
border-bottom-right-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
}
|
||||
.user-part_head-btn {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 12px;
|
||||
left: 30px;
|
||||
color: #fdfdfd;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.user-part_head-btn:hover::after {
|
||||
transform: scale(1.2);
|
||||
transition-duration: 0.4s;
|
||||
}
|
||||
.user-part_head-btn::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0px;
|
||||
top: 0;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 50%;
|
||||
transform: scale(0);
|
||||
transition-duration: 0.4s;
|
||||
}
|
||||
.user-part_head-btn:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.user-part_head-text {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
color: #fdfdfd;
|
||||
}
|
||||
</style>
|
||||
625
pages/signin.vue
Normal file
625
pages/signin.vue
Normal file
@@ -0,0 +1,625 @@
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<div class="columns is-centered px-0 mx-0 my-0">
|
||||
<div :class="`column wrapper-login is-4-desktop is-6-tablet`">
|
||||
<div class="px-6 py-6 login-box">
|
||||
<div class="login-header">
|
||||
<Logo />
|
||||
<h2 class="title mt-4 has-text-centered">{{ isVietnamese ? 'Đăng nhập' : 'Login' }}</h2>
|
||||
</div>
|
||||
<div class="login-body">
|
||||
<div class="form-login">
|
||||
<div class="field mt-5">
|
||||
<label class="label">Email<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="email"
|
||||
placeholder="Nhập email"
|
||||
v-model="email"
|
||||
@blur="validateEmail(email)"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="help is-danger"
|
||||
v-if="errors.find((v) => v.name === 'email')"
|
||||
v-html="errors.find((v) => v.name === 'email').text"
|
||||
></p>
|
||||
</div>
|
||||
<div class="field mt-5">
|
||||
<label class="label">Mật khẩu<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="field-body">
|
||||
<div class="field has-addons">
|
||||
<p class="control is-expanded">
|
||||
<input
|
||||
class="input"
|
||||
:type="showpass ? 'text' : 'password'"
|
||||
placeholder="Nhập mật khẩu"
|
||||
v-model="password"
|
||||
@blur="validatePassword(password)"
|
||||
/>
|
||||
</p>
|
||||
<div class="control">
|
||||
<a class="button" @click="showpass = !showpass">
|
||||
<span class="icon">
|
||||
<i
|
||||
:class="
|
||||
showpass
|
||||
? 'mdi mdi-eye-outline has-text-dark fs22'
|
||||
: 'mdi mdi-eye-off-outline has-text-dark fs22'
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'password')">
|
||||
{{ errors.find((v) => v.name === 'password').text }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field mt-5 pt-1 group-action">
|
||||
<button class="button is-primary" @click="signin()">Đăng nhập</button>
|
||||
<a class="is-primary" @click="accountRecovery()">Quên mật khẩu?</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="social-login">
|
||||
<div class="social-header my-4">
|
||||
<span>Hoặc</span>
|
||||
</div>
|
||||
<div class="social-body">
|
||||
<div class="social-item">
|
||||
<GoogleLogin class="button" :onSuccess="onSuccess" :params="params">
|
||||
<img class="icon" src="/icons/google.svg" alt="Google" />
|
||||
<span>Đăng nhập với Google</span>
|
||||
</GoogleLogin>
|
||||
</div>
|
||||
<div class="social-item">
|
||||
<button class="button" @click="logInWithFacebook()">
|
||||
<img class="icon" src="/icons/facebook.svg" alt="Facebook" />
|
||||
<span> Đăng nhập với Facebook </span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-5 has-text-centered">
|
||||
Chưa có tài khoản?
|
||||
<nuxt-link class="ml-2" to="/signup">Tạo tài khoản</nuxt-link>
|
||||
</p>
|
||||
|
||||
<div class="info-support mt-4">
|
||||
<div class="support-header">
|
||||
<h5 class="title">Cần hỗ trợ</h5>
|
||||
</div>
|
||||
<div class="support-body">
|
||||
<p class="support-item">
|
||||
<a :href="`tel:${company.phone.support}`" target="_blank">
|
||||
<img src="/icons/phone-call.svg" alt="Support" width="24" height="24" />
|
||||
<span>{{ company.phone.support }}</span>
|
||||
</a>
|
||||
</p>
|
||||
<p class="support-item">
|
||||
<a :href="`mailto:${company.email.support}`">
|
||||
<img src="/icons/email.svg" alt="" width="24" height="24" />
|
||||
<span>{{ company.email.support }}</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="login-footer">
|
||||
<img src="/icons/shield.svg" alt="" width="24" />
|
||||
|
||||
<span> {{ new Date().getFullYear() }} </span>
|
||||
<span v-if="isVietnamese">
|
||||
Bản quyền thuộc về
|
||||
<a
|
||||
:href="company.parent.website"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:title="company.parent.slogan"
|
||||
>{{ company.parent.name }}</a
|
||||
>
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
Copyright by
|
||||
<a
|
||||
:href="company.parent.website"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:title="company.parent.sloganEn"
|
||||
>{{ company.parent.name }}</a
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Bowser from 'bowser';
|
||||
import GoogleLogin from 'vue-google-login';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GoogleLogin,
|
||||
},
|
||||
head() {
|
||||
return { title: `${this.isVietnamese ? 'Đăng nhập' : 'Login'} - ${this.company.name}` };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fullname: undefined,
|
||||
username: undefined,
|
||||
email: undefined,
|
||||
password: undefined,
|
||||
errors: [],
|
||||
showpass: false,
|
||||
registers: [],
|
||||
params: { client_id: '389310749372-mnfsd09ukgl0ahbdrm94dsepremlfgs9.apps.googleusercontent.com' },
|
||||
type: undefined,
|
||||
isLoaded: false,
|
||||
data: undefined,
|
||||
account: undefined,
|
||||
company: this.$companyInfo(),
|
||||
isVietnamese: true,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('keyup', (ev) =>
|
||||
ev.key === 'Enter' && this.$route.name === 'signup' ? this.signin() : false,
|
||||
);
|
||||
},
|
||||
watch: {
|
||||
isLoaded: function (newVal) {
|
||||
if (this.isLoaded) {
|
||||
let self = this;
|
||||
window.FB.login(
|
||||
function (response) {
|
||||
if (response.authResponse) {
|
||||
let userId = response.authResponse.userID;
|
||||
self.getUserInfo(userId);
|
||||
}
|
||||
},
|
||||
{
|
||||
scope: 'public_profile,email',
|
||||
return_scopes: true,
|
||||
auth_type: 'rerequest',
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
login: {
|
||||
get: function () {
|
||||
return this.$store.state.login;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateLogin', { login: val });
|
||||
},
|
||||
},
|
||||
registermethod: {
|
||||
get: function () {
|
||||
return this.$store.state.registermethod;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateRegisterMethod', { registermethod: val });
|
||||
},
|
||||
},
|
||||
authmethod: {
|
||||
get: function () {
|
||||
return this.$store.state.authmethod;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateAuthMethod', { authmethod: val });
|
||||
},
|
||||
},
|
||||
authstatus: {
|
||||
get: function () {
|
||||
return this.$store.state.authstatus;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateAuthStatus', { authstatus: val });
|
||||
},
|
||||
},
|
||||
usertype: {
|
||||
get: function () {
|
||||
return this.$store.state.usertype;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateUserType', { usertype: val });
|
||||
},
|
||||
},
|
||||
viewport: {
|
||||
get: function () {
|
||||
return this.$store.state.viewport;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateViewPort', { viewport: val });
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
checkError() {
|
||||
this.errors = [];
|
||||
if (!this.$empty(this.email)) {
|
||||
this.email = this.email.trim().toLowerCase();
|
||||
}
|
||||
|
||||
this.validateEmail(this.email);
|
||||
this.validatePassword(this.password);
|
||||
|
||||
return this.errors.length > 0 ? true : false;
|
||||
},
|
||||
|
||||
validateEmail(email) {
|
||||
// Xóa lỗi cũ
|
||||
this.errors = this.errors.filter((err) => err.name !== 'email');
|
||||
|
||||
if (this.$empty(email)) {
|
||||
this.errors.push({
|
||||
name: 'email',
|
||||
text: 'Email không được để trống.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.$regexEmail(String(email).toLowerCase())) {
|
||||
this.errors.push({
|
||||
name: 'email',
|
||||
text: 'Email không đúng định dạng.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
validatePassword(password) {
|
||||
// Xóa lỗi cũ
|
||||
this.errors = this.errors.filter((err) => err.name !== 'password');
|
||||
|
||||
if (this.$empty(password)) {
|
||||
this.errors.push({
|
||||
name: 'password',
|
||||
text: 'Mật khẩu không được để trống.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.$regexPassword(String(password).toLowerCase())) {
|
||||
this.errors.push({
|
||||
name: 'password',
|
||||
text: 'Mật khẩu không đúng định dạng.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
async signin() {
|
||||
if (this.checkError()) return;
|
||||
let conn = this.$findapi('login');
|
||||
conn.params.filter = { email: this.email, password: this.password };
|
||||
let result = await this.$getapi([conn]);
|
||||
let data = result?.find((v) => v.name === 'login').data.rows;
|
||||
this.account = data;
|
||||
this.fillData(data);
|
||||
},
|
||||
onSuccess(googleUser) {
|
||||
let info = googleUser.getBasicProfile();
|
||||
|
||||
let keys = Object.keys(info);
|
||||
this.email = info[keys[5]];
|
||||
this.fullname = info[keys[1]];
|
||||
this.username = info[keys[0]];
|
||||
this.type = 'google';
|
||||
if (!this.$empty(this.email)) this.checkAccount();
|
||||
},
|
||||
async checkAccount() {
|
||||
if (this.$empty(this.email)) return;
|
||||
let found = this.$findapi('user');
|
||||
found.params = { filter: { email: this.email } };
|
||||
let result = await this.$getapi([found]);
|
||||
let data = result.find((v) => v.name === 'user').data.rows;
|
||||
if (data.length > 0) this.fillData(data[0]);
|
||||
else {
|
||||
let rs = await this.$insertapi('gethash', { text: this.$id() });
|
||||
let hash = rs.rows[0];
|
||||
let registerMethod = this.registermethod.find((v) => v.code === 'create-account').id;
|
||||
if (this.type) registerMethod = this.registermethod.find((v) => v.code === this.type).id;
|
||||
data = {
|
||||
fullname: this.fullname,
|
||||
username: this.username ?? this.email,
|
||||
email: this.email,
|
||||
password: hash,
|
||||
type: this.usertype.find((v) => v.code === 'customer').id,
|
||||
register_method: registerMethod,
|
||||
auth_status: this.authstatus.find((v) => v.code === 'auth').id,
|
||||
auth_method:
|
||||
this.email.indexOf('@') >= 0
|
||||
? this.authmethod.find((v) => v.code === 'email').id
|
||||
: this.authmethod.find((v) => v.code === 'phone').id,
|
||||
display_name: this.fullname,
|
||||
};
|
||||
|
||||
let res = await this.$insertapi('user', data);
|
||||
if (res !== 'error') {
|
||||
this.login = res;
|
||||
this.redirectUrl();
|
||||
}
|
||||
}
|
||||
},
|
||||
invalidLogin(data) {
|
||||
console.log('login', data);
|
||||
|
||||
if (!data) {
|
||||
const text = 'Tài khoản hoặc mật khẩu không chính xác';
|
||||
this.errors.push({ name: 'email', text: text });
|
||||
this.errors.push({ name: 'password', text: text });
|
||||
} else if (data.blocked) {
|
||||
this.errors.push({ name: 'email', text: 'Tài khoản đang bị khóa. Đăng nhập không thành công' });
|
||||
} else if (data.auth_status === 1) {
|
||||
this.errors.push({
|
||||
name: 'email',
|
||||
text: `Tài khoản đang chờ xác thực. <a @click="$emit('resend')">Gửi lại mã</a>`,
|
||||
});
|
||||
}
|
||||
return this.errors.length > 0 ? true : false;
|
||||
},
|
||||
async fillData(data) {
|
||||
if (this.invalidLogin(data)) return;
|
||||
this.login = data; //store login
|
||||
if (data.type === 1 || data.type__code === 'customer') {
|
||||
this.redirectUrl();
|
||||
} else if (this.$store.state.link) {
|
||||
let ele = this.$copy(data);
|
||||
if (
|
||||
this.$store.state.link.indexOf('bigdatatech.vn') < 0 &&
|
||||
this.$store.state.link.indexOf('localhost:3000') < 0
|
||||
) {
|
||||
return this.$redirectWeb(ele);
|
||||
}
|
||||
//get token & redirect link
|
||||
const browser = Bowser.getParser(window.navigator.userAgent);
|
||||
let obj = {
|
||||
browser: browser.getBrowserName(),
|
||||
browser_version: browser.getBrowserVersion(),
|
||||
platform: browser.getPlatform().type,
|
||||
os: browser.getOSName(),
|
||||
user: data.id,
|
||||
token: this.$id(),
|
||||
};
|
||||
ele.token = obj.token;
|
||||
await this.$insertapi('authtoken', obj);
|
||||
//await this.sendNoti(obj)
|
||||
this.$redirectWeb(ele);
|
||||
} else this.redirectUrl();
|
||||
},
|
||||
async sendNoti(obj) {
|
||||
let found = this.$findapi('notiform');
|
||||
found.params.filter = { code: 'login-alert' };
|
||||
const result = await this.$getapi([found]);
|
||||
let data = result[0].data.rows[0];
|
||||
let content = data.detail;
|
||||
content = content.replace('[1]', this.email);
|
||||
content = content.replace('[2]', obj.browser);
|
||||
content = content.replace('[3]', obj.browser_version);
|
||||
content = content.replace('[4]', obj.platform);
|
||||
content = content.replace('[5]', obj.os);
|
||||
content = content.replace('[6]', this.$dayjs().format('DD/MM/YYYY HH:mm'));
|
||||
let ele = { title: data.name, content: content, user: [obj.user.toString()], type: 1 };
|
||||
await this.$insertapi('notification', ele);
|
||||
},
|
||||
async logInWithFacebook() {
|
||||
if (window.FB) {
|
||||
let self = this;
|
||||
window.FB.login(
|
||||
function (response) {
|
||||
if (response.authResponse) {
|
||||
let userId = response.authResponse.userID;
|
||||
self.getUserInfo(userId);
|
||||
}
|
||||
},
|
||||
{
|
||||
scope: 'public_profile,email',
|
||||
return_scopes: true,
|
||||
auth_type: 'rerequest',
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await this.loadFacebookSDK(document, 'script', 'facebook-jssdk');
|
||||
await this.initFacebook();
|
||||
}
|
||||
},
|
||||
async initFacebook() {
|
||||
let self = this;
|
||||
window.fbAsyncInit = function () {
|
||||
window.FB.init({
|
||||
appId: '314849628253331',
|
||||
cookie: true,
|
||||
xfbml: true,
|
||||
version: 'v8.0',
|
||||
});
|
||||
self.isLoaded = true;
|
||||
};
|
||||
},
|
||||
async loadFacebookSDK(d, s, id) {
|
||||
var js,
|
||||
fjs = d.getElementsByTagName(s)[0];
|
||||
if (d.getElementById(id)) {
|
||||
return;
|
||||
}
|
||||
js = d.createElement(s);
|
||||
js.id = id;
|
||||
js.src = 'https://connect.facebook.net/en_US/sdk.js';
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
},
|
||||
getUserInfo(userId) {
|
||||
let self = this;
|
||||
window.FB.api('/me?fields=id,name,email', function (response) {
|
||||
self.email = response.email;
|
||||
self.fullname = response.name;
|
||||
self.type = 'facebook';
|
||||
self.checkAccount();
|
||||
});
|
||||
},
|
||||
accountRecovery() {
|
||||
this.$router.push({ path: '/account/recovery' });
|
||||
},
|
||||
// async resendAuthcode() {
|
||||
// let code = this.$id();
|
||||
// let result = await this.$insertapi('userauth', { user: this.account.id, code: code });
|
||||
// let query = this.$store.state.link ? { link: this.$store.state.link } : {};
|
||||
// query.id = this.account.id;
|
||||
// query.email = this.email;
|
||||
// query.action = 'sendcode';
|
||||
// this.$router.push({ path: '/account/auth', query: query });
|
||||
// },
|
||||
async redirectUrl() {
|
||||
this.$router.push(this.$route.query.href || '/welcome');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.login-page {
|
||||
background-color: #f0fdf4;
|
||||
min-height: 100vh;
|
||||
align-content: center;
|
||||
|
||||
button.is-primary {
|
||||
background-color: #53b147;
|
||||
box-shadow: 0 4px 12px rgba(83, 177, 71, 0.25);
|
||||
&:hover {
|
||||
background: #45963a;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-login {
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #bbf7d0;
|
||||
background-color: #fff;
|
||||
padding: 0;
|
||||
// .login-header {
|
||||
// }
|
||||
.login-body {
|
||||
.form-login {
|
||||
.group-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.social-login {
|
||||
.social-header {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #ddd;
|
||||
}
|
||||
span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: #fff;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
.social-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.social-item {
|
||||
flex: 1;
|
||||
button {
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
&:hover {
|
||||
background-color: #bbf7d0;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-support {
|
||||
background-color: #fff;
|
||||
border: 1px solid #bbf7d0;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
.support-header {
|
||||
background-color: #f0fdf4;
|
||||
text-align: center;
|
||||
padding: 14px 0;
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.support-body {
|
||||
display: flex;
|
||||
padding: 16px 0;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
.support-item {
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
color: #374151;
|
||||
&:hover {
|
||||
color: #16a34a;
|
||||
img {
|
||||
filter: brightness(0) saturate(100%) invert(40%) sepia(80%) saturate(400%) hue-rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
696
pages/signup.vue
Normal file
696
pages/signup.vue
Normal file
@@ -0,0 +1,696 @@
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<div class="columns is-centered px-0 mx-0 my-0">
|
||||
<div :class="`column wrapper-login is-${viewport >= 4 ? 4 : 6}`">
|
||||
<div class="px-6 py-6 login-box">
|
||||
<div class="login-header">
|
||||
<Logo />
|
||||
<h2 class="title mt-4 has-text-centered">
|
||||
{{ isVietnamese ? 'Đăng ký tài khoản' : 'Register an account' }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="login-body">
|
||||
<div class="form-login">
|
||||
<div class="field mt-5 pt-1">
|
||||
<label class="label">Họ và tên<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="fullname"
|
||||
@blur="validateFullName(fullname)"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'fullname')">
|
||||
{{ errors.find((v) => v.name === 'fullname').text }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field mt-4">
|
||||
<label class="label">Điện thoại</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="phone" @blur="validatePhone(phone)" />
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'phone')">
|
||||
{{ errors.find((v) => v.name === 'phone').text }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field mt-4">
|
||||
<label class="label">Email<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="control">
|
||||
<input class="input" type="email" placeholder="" v-model="email" @blur="validateEmail(email)" />
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'email')">
|
||||
{{ errors.find((v) => v.name === 'email').text }}
|
||||
</p>
|
||||
<p class="help is-primary" v-else-if="info">{{ info }}</p>
|
||||
</div>
|
||||
<div class="field mt-4">
|
||||
<label class="label">Mật khẩu<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="field-body">
|
||||
<div class="field has-addons">
|
||||
<p class="control is-expanded">
|
||||
<input
|
||||
class="input"
|
||||
:type="showpass ? 'text' : 'password'"
|
||||
placeholder="Mật khẩu phải có ít nhất 8 ký tự, bao gồm chữ hoa, chữ thường, số và ký tự đặc biệt."
|
||||
v-model="password"
|
||||
@blur="validatePassword(password)"
|
||||
/>
|
||||
</p>
|
||||
<div class="control">
|
||||
<a class="button" @click="showpass = !showpass">
|
||||
<span class="icon">
|
||||
<i
|
||||
:class="
|
||||
showpass
|
||||
? 'mdi mdi-eye-outline has-text-dark fs22'
|
||||
: 'mdi mdi-eye-off-outline has-text-dark fs22'
|
||||
"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'password')">
|
||||
{{ errors.find((v) => v.name === 'password').text }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field mt-4">
|
||||
<label class="label">Nhập lại mật khẩu<b class="ml-1 has-text-danger">*</b></label>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<p class="control is-expanded">
|
||||
<input
|
||||
class="input"
|
||||
:type="showpass ? 'text' : 'password'"
|
||||
placeholder=""
|
||||
v-model="retypePassword"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'retypePassword')">
|
||||
{{ errors.find((v) => v.name === 'retypePassword').text }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field mt-4">
|
||||
<div class="control">
|
||||
<b-checkbox v-model="checkbox"
|
||||
>Tôi đồng ý với <a @click="$router.push('/policy')">các điều khoản và điều kiện</a></b-checkbox
|
||||
>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'checkbox')">
|
||||
{{ errors.find((v) => v.name === 'checkbox').text }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field mt-5 pt-1 group-action">
|
||||
<button class="button is-primary" @click="createAccount()">Tạo tài khoản</button>
|
||||
<!-- <a class="ml-2" @click="$router.push('/signin')">Đăng nhập</a> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="social-login">
|
||||
<div class="social-header my-4">
|
||||
<span>Hoặc</span>
|
||||
</div>
|
||||
<div class="social-body">
|
||||
<div class="social-item">
|
||||
<GoogleLogin class="button" :onSuccess="onSuccess" :params="params">
|
||||
<img class="icon" src="/icons/google.svg" alt="Google" />
|
||||
<span>Đăng ký với Google</span>
|
||||
</GoogleLogin>
|
||||
</div>
|
||||
<div class="social-item">
|
||||
<button class="button" @click="logInWithFacebook()">
|
||||
<img class="icon" src="/icons/facebook.svg" alt="Facebook" />
|
||||
<span> Đăng ký với Facebook </span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-5 has-text-centered">
|
||||
Bạn đã có tài khoản?
|
||||
<nuxt-link class="ml-2" to="/signin">Đăng nhập</nuxt-link>
|
||||
</p>
|
||||
|
||||
<div class="info-support mt-4">
|
||||
<div class="support-header">
|
||||
<h5 class="title">Cần hỗ trợ</h5>
|
||||
</div>
|
||||
<div class="support-body">
|
||||
<p class="support-item">
|
||||
<a :href="`tel:${company.phone.support}`" target="_blank">
|
||||
<img src="/icons/phone-call.svg" alt="Support" width="24" height="24" />
|
||||
<span>{{ company.phone.support }}</span>
|
||||
</a>
|
||||
</p>
|
||||
<p class="support-item">
|
||||
<a :href="`mailto:${company.email.support}`">
|
||||
<img src="/icons/email.svg" alt="" width="24" height="24" />
|
||||
<span>{{ company.email.support }}</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="login-footer">
|
||||
<img src="/icons/shield.svg" alt="" width="24" />
|
||||
|
||||
<span> {{ new Date().getFullYear() }} </span>
|
||||
<span v-if="isVietnamese">
|
||||
Bản quyền thuộc về
|
||||
<a
|
||||
:href="company.parent.website"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:title="company.parent.slogan"
|
||||
>{{ company.parent.name }}</a
|
||||
>
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
Copyright by
|
||||
<a
|
||||
:href="company.parent.website"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:title="company.parent.sloganEn"
|
||||
>{{ company.parent.name }}</a
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GoogleLogin from 'vue-google-login';
|
||||
export default {
|
||||
components: { GoogleLogin },
|
||||
head() {
|
||||
return { title: `${this.isVietnamese ? 'Đăng ký tài khoản' : 'Register an account'} - ${this.company.name}` };
|
||||
},
|
||||
//
|
||||
data() {
|
||||
return {
|
||||
fullname: undefined,
|
||||
email: undefined,
|
||||
password: undefined,
|
||||
retypePassword: undefined,
|
||||
errors: [],
|
||||
info: 'Hệ thống gửi mã xác thực qua email. Sau khi tạo vui lòng kiểm tra email trong Inbox hoặc Spam',
|
||||
showpass: false,
|
||||
hash: undefined,
|
||||
checkbox: false,
|
||||
params: { client_id: '389310749372-mnfsd09ukgl0ahbdrm94dsepremlfgs9.apps.googleusercontent.com' },
|
||||
type: undefined,
|
||||
status: undefined,
|
||||
user: undefined,
|
||||
code: undefined,
|
||||
isLoaded: false,
|
||||
show: true,
|
||||
discount: undefined,
|
||||
authEmail: undefined,
|
||||
phone: undefined,
|
||||
validTo: this.$dayjs().add(30, 'day').format('DD/MM/YYYY'),
|
||||
company: this.$companyInfo(),
|
||||
isVietnamese: true,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
window.addEventListener('keyup', (ev) =>
|
||||
ev.key === 'Enter' && this.$route.name === 'signup' ? this.createAccount() : false,
|
||||
);
|
||||
if (!this.$empty(this.$route.query.referer)) this.referer = this.$route.query.referer;
|
||||
if (this.referer) this.discount = await this.$getdata('commission', { code: 'customer' }, undefined, true);
|
||||
},
|
||||
watch: {
|
||||
isLoaded: function (newVal) {
|
||||
if (newVal) {
|
||||
let self = this;
|
||||
window.FB.login(function (response) {
|
||||
if (response.authResponse) {
|
||||
let userId = response.authResponse.userID;
|
||||
self.getUserInfo(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
registermethod: {
|
||||
get: function () {
|
||||
return this.$store.state.registermethod;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateRegisterMethod', { registermethod: val });
|
||||
},
|
||||
},
|
||||
authmethod: {
|
||||
get: function () {
|
||||
return this.$store.state.authmethod;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateAuthMethod', { authmethod: val });
|
||||
},
|
||||
},
|
||||
authstatus: {
|
||||
get: function () {
|
||||
return this.$store.state.authstatus;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateAuthStatus', { authstatus: val });
|
||||
},
|
||||
},
|
||||
usertype: {
|
||||
get: function () {
|
||||
return this.$store.state.usertype;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateUserType', { usertype: val });
|
||||
},
|
||||
},
|
||||
dialog: {
|
||||
get: function () {
|
||||
return this.$store.state['dialog'];
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateStore', { name: 'dialog', data: val });
|
||||
},
|
||||
},
|
||||
viewport: {
|
||||
get: function () {
|
||||
return this.$store.state.viewport;
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateViewPort', { viewport: val });
|
||||
},
|
||||
},
|
||||
referer: {
|
||||
get: function () {
|
||||
return this.$store.state['referer'];
|
||||
},
|
||||
set: function (val) {
|
||||
this.$store.commit('updateStore', { name: 'referer', data: val });
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
checkError() {
|
||||
this.errors = [];
|
||||
if (!this.$empty(this.fullname)) {
|
||||
this.fullname = this.fullname.trim();
|
||||
}
|
||||
if (!this.$empty(this.email)) {
|
||||
this.email = this.email.trim().toLowerCase();
|
||||
}
|
||||
// if (this.$empty(this.fullname)) {
|
||||
// this.errors.push({ name: 'fullname', text: 'Họ và tên không được bỏ trống.' });
|
||||
// } else if (this.fullname.length < 5) {
|
||||
// this.errors.push({ name: 'fullname', text: 'Họ và tên quá ngắn. Yêu cầu từ 5 kí tự trở nên.' });
|
||||
// }
|
||||
// if (!this.$empty(this.phone)) {
|
||||
// if (!this.$regexPhone(this.phone)) {
|
||||
// this.errors.push({ name: 'phone', text: 'Số điện thoại không hợp lệ.' });
|
||||
// }
|
||||
// }
|
||||
this.validateFullName(this.fullname);
|
||||
this.validatePhone(this.phone);
|
||||
this.validateEmail(this.email);
|
||||
this.validatePassword(this.password);
|
||||
|
||||
if (this.$empty(this.retypePassword)) {
|
||||
this.errors.push({ name: 'retypePassword', text: 'Nhắc lại mật khẩu không được bỏ trống.' });
|
||||
} else if (this.password !== this.retypePassword) {
|
||||
this.errors.push({ name: 'retypePassword', text: 'Nhắc lại mật khẩu phải giống với mật khẩu đã nhập.' });
|
||||
}
|
||||
if (!this.checkbox)
|
||||
this.errors.push({ name: 'checkbox', text: 'Bạn chưa chọn đồng ý với điều khoản và điều kiện của chúng tôi.' });
|
||||
return this.errors.length > 0 ? true : false;
|
||||
},
|
||||
|
||||
validateFullName(fullName) {
|
||||
// Xóa lỗi cũ
|
||||
this.errors = this.errors.filter((err) => err.name !== 'fullname');
|
||||
|
||||
if (this.$empty(fullName)) {
|
||||
this.errors.push({
|
||||
name: 'fullname',
|
||||
text: 'Họ và tên không được để trống.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fullName.length < 5) {
|
||||
this.errors.push({
|
||||
name: 'fullname',
|
||||
text: 'Họ và tên quá ngắn. Yêu cầu từ 5 kí tự trở nên.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
validateEmail(email) {
|
||||
// Xóa lỗi cũ
|
||||
this.errors = this.errors.filter((err) => err.name !== 'email');
|
||||
|
||||
if (this.$empty(email)) {
|
||||
this.errors.push({
|
||||
name: 'email',
|
||||
text: 'Email không được để trống.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.$regexEmail(String(email).toLowerCase())) {
|
||||
this.errors.push({
|
||||
name: 'email',
|
||||
text: 'Email không đúng định dạng.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
validatePassword(password) {
|
||||
// Xóa lỗi cũ
|
||||
this.errors = this.errors.filter((err) => err.name !== 'password');
|
||||
|
||||
if (this.$empty(password)) {
|
||||
this.errors.push({
|
||||
name: 'password',
|
||||
text: 'Mật khẩu không được để trống.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.$regexPassword(String(password).toLowerCase())) {
|
||||
this.errors.push({
|
||||
name: 'password',
|
||||
text: 'Mật khẩu không đúng định dạng.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
validatePhone(phone) {
|
||||
// Xóa lỗi cũ
|
||||
this.errors = this.errors.filter((err) => err.name !== 'phone');
|
||||
if (!this.$empty(phone)) {
|
||||
if (!this.$regexPhone(phone)) {
|
||||
this.errors.push({
|
||||
name: 'phone',
|
||||
text: 'Số điện thoại không hợp lệ.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
async createAccount() {
|
||||
if (this.checkError()) return;
|
||||
const userRes = await this.$getdata('user', { email: this.email });
|
||||
const customerRes = await this.$getdata('customer', { email: this.email });
|
||||
|
||||
if (userRes.length > 0 || customerRes.length > 0) {
|
||||
return this.errors.push({ name: 'email', text: 'Tài khoản đã tồn tại trong hệ thống.' });
|
||||
}
|
||||
|
||||
let rs = await this.$insertapi('gethash', { text: this.password });
|
||||
this.hash = rs.rows[0];
|
||||
let ele = this.$findapi('user');
|
||||
ele.params = { filter: { email: this.email } };
|
||||
let result = await this.$getapi([ele]);
|
||||
let data = result.find((v) => v.name === 'user').data.rows;
|
||||
if (data.length > 0) {
|
||||
return this.errors.push({ name: 'email', text: 'Tài khoản đã tồn tại trong hệ thống.' });
|
||||
}
|
||||
|
||||
let registerMethod = this.registermethod.find((v) => v.code === 'create-account').id;
|
||||
if (this.type) {
|
||||
if (this.authEmail !== this.email) this.type = undefined;
|
||||
else registerMethod = this.registermethod.find((v) => v.code === this.type).id;
|
||||
}
|
||||
this.status = this.authstatus.find((v) => v.code === 'auth');
|
||||
if (!this.type && this.email.indexOf('@') >= 0) this.status = this.authstatus.find((v) => v.code === 'wait');
|
||||
data = {
|
||||
fullname: this.fullname,
|
||||
email: this.email,
|
||||
password: this.hash,
|
||||
type: this.usertype.find((v) => v.code === 'customer').id,
|
||||
register_method: registerMethod,
|
||||
auth_status: this.status.id,
|
||||
phone: this.phone,
|
||||
auth_method:
|
||||
this.email.indexOf('@') >= 0
|
||||
? this.authmethod.find((v) => v.code === 'email').id
|
||||
: this.authmethod.find((v) => v.code === 'phone').id,
|
||||
display_name: this.fullname,
|
||||
};
|
||||
this.$route.query.type === 'employee' ? (data.type = this.usertype.find((v) => v.code === 'employee').id) : false;
|
||||
if (this.referer) data.referer = this.referer;
|
||||
|
||||
this.user = await this.$insertapi('user', data);
|
||||
const customer = await this.$insertapi('customer', { ...data, type: null });
|
||||
if (this.user !== 'error' && customer != 'error') {
|
||||
let text = 'Bạn đã đăng ký tài khoản thành công. Hệ thống tự động chuyển tới trang đăng nhập.';
|
||||
if (this.status.code === 'wait')
|
||||
text = 'Bạn đã đăng ký tài khoản thành công. Hệ thống tự động chuyển tới trang xác thực tài khoản';
|
||||
this.$dialog(text, 'Xác thực tài khoản', undefined, 5);
|
||||
}
|
||||
if (this.status.code === 'wait') {
|
||||
this.code = this.$id();
|
||||
let data = { user: this.user.id, code: this.code };
|
||||
result = await this.$insertapi('userauth', data);
|
||||
this.processAuth();
|
||||
} else this.$router.push({ path: '/signin' });
|
||||
},
|
||||
async processAuth() {
|
||||
let query = this.$store.state.link ? { code: this.code, link: this.$store.state.link } : { code: this.code };
|
||||
let routeData = this.$router.resolve({ path: '/account/auth', query: query });
|
||||
let path = window.location.origin + routeData.href;
|
||||
let conn = this.$findapi('userauth');
|
||||
conn.params.filter = { user: this.user.id };
|
||||
let result = await this.$getapi([conn]);
|
||||
|
||||
let msg = result[0].data.rows[0].code;
|
||||
msg = msg.replace('[1]', this.fullname);
|
||||
msg = msg.replace('[2]', this.code);
|
||||
msg = msg.replace('[3]', path);
|
||||
|
||||
let data = { subject: 'Xác thực tài khoản BigDataTechCloud', content: msg, to: this.user.email };
|
||||
result = await this.$insertapi('sendemail', data);
|
||||
this.$router.push({ path: '/account/auth', query: { id: this.user.id, email: this.email } });
|
||||
},
|
||||
onSuccess(googleUser) {
|
||||
let info = googleUser.getBasicProfile();
|
||||
console.log('Google info', info);
|
||||
|
||||
let keys = Object.keys(info);
|
||||
this.email = info[keys[5]];
|
||||
this.fullname = info[keys[1]];
|
||||
this.type = 'google';
|
||||
this.authEmail = this.$copy(this.email);
|
||||
},
|
||||
async logInWithFacebook() {
|
||||
if (window.FB) {
|
||||
let self = this;
|
||||
window.FB.login(
|
||||
function (response) {
|
||||
if (response.authResponse) {
|
||||
let userId = response.authResponse.userID;
|
||||
self.getUserInfo(userId);
|
||||
}
|
||||
},
|
||||
{
|
||||
scope: 'public_profile,email',
|
||||
return_scopes: true,
|
||||
auth_type: 'rerequest',
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await this.loadFacebookSDK(document, 'script', 'facebook-jssdk');
|
||||
await this.initFacebook();
|
||||
}
|
||||
},
|
||||
async initFacebook() {
|
||||
let self = this;
|
||||
window.fbAsyncInit = function () {
|
||||
window.FB.init({
|
||||
appId: '434259677606053',
|
||||
cookie: true,
|
||||
version: 'v8.0',
|
||||
});
|
||||
self.isLoaded = true;
|
||||
};
|
||||
},
|
||||
async loadFacebookSDK(d, s, id) {
|
||||
var js,
|
||||
fjs = d.getElementsByTagName(s)[0];
|
||||
if (d.getElementById(id)) {
|
||||
return;
|
||||
}
|
||||
js = d.createElement(s);
|
||||
js.id = id;
|
||||
js.src = 'https://connect.facebook.net/en_US/sdk.js';
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
},
|
||||
getUserInfo(userId) {
|
||||
let self = this;
|
||||
window.FB.api('/me?fields=id,name,email', function (response) {
|
||||
self.username = response.email;
|
||||
self.fullname = response.name;
|
||||
self.type = 'facebook';
|
||||
self.authEmail = self.$copy(self.username);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.login-page {
|
||||
background-color: #f0fdf4;
|
||||
min-height: 100vh;
|
||||
align-content: center;
|
||||
|
||||
button.is-primary {
|
||||
background-color: #53b147;
|
||||
box-shadow: 0 4px 12px rgba(83, 177, 71, 0.25);
|
||||
&:hover {
|
||||
background: #45963a;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-login {
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #bbf7d0;
|
||||
background-color: #fff;
|
||||
padding: 0;
|
||||
// .login-header {
|
||||
// }
|
||||
.login-body {
|
||||
.form-login {
|
||||
.group-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.social-login {
|
||||
.social-header {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #ddd;
|
||||
}
|
||||
span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: #fff;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
.social-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.social-item {
|
||||
flex: 1;
|
||||
button {
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
&:hover {
|
||||
background-color: #bbf7d0;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-support {
|
||||
background-color: #fff;
|
||||
border: 1px solid #bbf7d0;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
.support-header {
|
||||
background-color: #f0fdf4;
|
||||
text-align: center;
|
||||
padding: 14px 0;
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.support-body {
|
||||
display: flex;
|
||||
padding: 16px 0;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
.support-item {
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
color: #374151;
|
||||
&:hover {
|
||||
color: #16a34a;
|
||||
img {
|
||||
filter: brightness(0) saturate(100%) invert(40%) sepia(80%) saturate(400%) hue-rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
62
pages/welcome.vue
Normal file
62
pages/welcome.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="columns is-centered mx-0 mt-2">
|
||||
<div class="column is-10">
|
||||
<Logo class="mb-5 pb-1"></Logo>
|
||||
<p class="fs-16" v-if="login">Xin chào <b>{{ login.fullname }}</b><span v-if="packinfo? (packinfo.trialInfo==='active' || packinfo.buy) : false">. Sau đây là gói dịch vụ quý khách <b>đang sử dụng</b></span>
|
||||
</p>
|
||||
<template v-if="login? login.type!==1 : false">
|
||||
<Redirect class="mt-4"></Redirect>
|
||||
</template>
|
||||
<template v-else>
|
||||
<AccessExtend v-if="extend"></AccessExtend>
|
||||
<UserPack v-if="login" @info="getinfo"></UserPack>
|
||||
<template v-if="packinfo">
|
||||
<Redirect v-if="packinfo.trialInfo==='active' || packinfo.buy"></Redirect>
|
||||
<template v-else>
|
||||
<Redirect class="mb-3" v-if="packinfo.trialInfo==='no'"></Redirect>
|
||||
<p class="mt-5 fs-16" v-if="packinfo.trialInfo==='expiry'">Quý khách vui lòng chọn <b>gói dịch vụ</b> để tiếp tục. Cảm ơn quý khách đã tin tưởng và lựa chọn dịch vụ của chúng tôi</p>
|
||||
<p class="mt-2 fs-16" v-else="packinfo.trialInfo==='expiry'">BigDataTechCloud cung cấp các gói dịch vụ như dưới dây, quý khách có thể lựa chọn <b>dùng thử </b>để trải nghiệm sản phẩm hoặc <b>mua ngay</b>. Cảm ơn quý khách đã tin tưởng và lựa chọn dịch vụ của chúng tôi</p>
|
||||
<ServicePack v-bind="{packinfo: packinfo}"></ServicePack>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
packinfo: undefined,
|
||||
expiry: undefined,
|
||||
extend: false
|
||||
}
|
||||
},
|
||||
// async created() {
|
||||
// if(!this.login) return this.$router.push('/signin')
|
||||
// let found = await this.$getdata('order', {user: this.login.id, payment_status__code: 'unpaid'}, undefined, true)
|
||||
// if(found) return this.$router.push(`/service/order-info?id=${found.id}`)
|
||||
// },
|
||||
computed: {
|
||||
login: {
|
||||
get: function() {return this.$store.state.login},
|
||||
set: function(val) {this.$store.commit('updateLogin', {login: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getinfo(v) {
|
||||
if(this.login.type===1) {
|
||||
let found = this.$find(this.$store.state.common, {category: 'system', classify: 'mode', code: 'status'})
|
||||
if(found.detail==='dev') {
|
||||
if(!(v.trialInfo==='active' || v.buy)) this.$router.push('/notice')
|
||||
}
|
||||
}
|
||||
this.packinfo = v
|
||||
if(v.trialInfo==='expiry' && !v.buy) this.extend = true
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user