Refactor user profile page
to be static
This commit is contained in:
parent
4af30bdbac
commit
56bd71c063
|
|
@ -193,10 +193,8 @@ class UserController extends Controller
|
|||
$user = Auth::user();
|
||||
|
||||
return view('user.profile')
|
||||
->with('extra', [
|
||||
'unverified' => option('require_verification') && ! $user->verified,
|
||||
'admin' => $user->isAdmin(),
|
||||
]);
|
||||
->with('user', $user)
|
||||
->with('site_name', option_localized('site_name'));
|
||||
}
|
||||
|
||||
public function handleProfile(Request $request, Filter $filter, Dispatcher $dispatcher)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ import { queryStringify } from './utils'
|
|||
import { showModal } from './notify'
|
||||
import { trans } from './i18n'
|
||||
|
||||
export interface ResponseBody {
|
||||
code: number
|
||||
message: string
|
||||
}
|
||||
|
||||
class HTTPError extends Error {
|
||||
response: Response
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@ export default [
|
|||
},
|
||||
{
|
||||
path: 'user/profile',
|
||||
component: () => import('../views/user/Profile.vue'),
|
||||
el: '.content > .container-fluid',
|
||||
module: [
|
||||
() => import('../views/user/profile/index'),
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'user/oauth/manage',
|
||||
|
|
|
|||
|
|
@ -1,300 +0,0 @@
|
|||
<template>
|
||||
<div class="container-fluid">
|
||||
<email-verification />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div v-once class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 v-t="'user.profile.avatar.title'" class="card-title" />
|
||||
</div>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="card-body" v-html="$t('user.profile.avatar.notice')" />
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" data-test="resetAvatar" @click="resetAvatar">
|
||||
{{ $t('user.resetAvatar') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="card card-warning"
|
||||
data-test="changePassword"
|
||||
@submit.prevent="changePassword"
|
||||
>
|
||||
<div class="card-header">
|
||||
<h3 v-t="'user.profile.password.title'" class="card-title" />
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label v-t="'user.profile.password.old'" />
|
||||
<input
|
||||
v-model="oldPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label v-t="'user.profile.password.new'" />
|
||||
<input
|
||||
v-model="newPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
required
|
||||
minlength="8"
|
||||
maxlength="32"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label v-t="'user.profile.password.confirm'" />
|
||||
<input
|
||||
ref="confirmPassword"
|
||||
v-model="confirmPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
required
|
||||
minlength="8"
|
||||
maxlength="32"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ $t('user.profile.password.button') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<form
|
||||
class="card card-primary"
|
||||
data-test="changeNickName"
|
||||
@submit.prevent="changeNickName"
|
||||
>
|
||||
<div class="card-header">
|
||||
<h3 v-t="'user.profile.nickname.title'" class="card-title" />
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="nickname"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:placeholder="$t('user.profile.nickname.rule')"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ $t('general.submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form
|
||||
class="card card-warning"
|
||||
data-test="changeEmail"
|
||||
@submit.prevent="changeEmail"
|
||||
>
|
||||
<div class="card-header">
|
||||
<h3 v-t="'user.profile.email.title'" class="card-title" />
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<input
|
||||
ref="email"
|
||||
v-model="email"
|
||||
type="email"
|
||||
class="form-control"
|
||||
:placeholder="$t('user.profile.email.new')"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
ref="currentPassword"
|
||||
v-model="currentPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
:placeholder="$t('user.profile.email.password')"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ $t('user.profile.email.button') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card card-danger">
|
||||
<div class="card-header">
|
||||
<h3 v-t="'user.profile.delete.title'" class="card-title" />
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<template v-if="isAdmin">
|
||||
<p v-t="'user.profile.delete.admin'" />
|
||||
<button class="btn btn-danger" disabled>
|
||||
{{ $t('user.profile.delete.button') }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p v-t="{ path: 'user.profile.delete.notice', args: { site: siteName } }" />
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
data-toggle="modal"
|
||||
data-target="#modal-delete-account"
|
||||
>
|
||||
{{ $t('user.profile.delete.button') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form data-test="deleteAccount" @submit.prevent="deleteAccount">
|
||||
<modal
|
||||
id="modal-delete-account"
|
||||
type="danger"
|
||||
:title="$t('user.profile.delete.modal-title')"
|
||||
flex-footer
|
||||
>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-once v-html="nl2br($t('user.profile.delete.modal-notice'))" />
|
||||
<br>
|
||||
<input
|
||||
v-model="deleteConfirm"
|
||||
type="password"
|
||||
class="form-control"
|
||||
:placeholder="$t('user.profile.delete.password')"
|
||||
required
|
||||
>
|
||||
<template #footer>
|
||||
<button
|
||||
v-t="'general.close'"
|
||||
type="button"
|
||||
class="btn btn-outline-light"
|
||||
data-dismiss="modal"
|
||||
/>
|
||||
<button
|
||||
v-t="'general.submit'"
|
||||
type="submit"
|
||||
class="btn btn-outline-light"
|
||||
/>
|
||||
</template>
|
||||
</modal>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmailVerification from '../../components/EmailVerification.vue'
|
||||
import Modal from '../../components/Modal.vue'
|
||||
import emitMounted from '../../components/mixins/emitMounted'
|
||||
import { showModal, toast } from '../../scripts/notify'
|
||||
|
||||
export default {
|
||||
name: 'Profile',
|
||||
components: {
|
||||
EmailVerification,
|
||||
Modal,
|
||||
},
|
||||
mixins: [
|
||||
emitMounted,
|
||||
],
|
||||
data: () => ({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
nickname: '',
|
||||
email: '',
|
||||
currentPassword: '',
|
||||
deleteConfirm: '',
|
||||
siteName: blessing.site_name,
|
||||
isAdmin: blessing.extra.admin,
|
||||
}),
|
||||
methods: {
|
||||
nl2br: str => str.replace(/\n/g, '<br>'),
|
||||
async resetAvatar() {
|
||||
try {
|
||||
await showModal({ text: this.$t('user.resetAvatarConfirm') })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
const { message } = await this.$http.post(
|
||||
'/user/profile/avatar',
|
||||
{ tid: 0 },
|
||||
)
|
||||
toast.success(message)
|
||||
Array.from(document.querySelectorAll('[alt="User Image"]'))
|
||||
.forEach(el => (el.src += `?${new Date().getTime()}`))
|
||||
},
|
||||
async changePassword() {
|
||||
const {
|
||||
oldPassword, newPassword, confirmPassword,
|
||||
} = this
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
toast.error(this.$t('auth.invalidConfirmPwd'))
|
||||
this.$refs.confirmPassword.focus()
|
||||
return
|
||||
}
|
||||
|
||||
const { code, message } = await this.$http.post(
|
||||
'/user/profile?action=password',
|
||||
{ current_password: oldPassword, new_password: newPassword },
|
||||
)
|
||||
await showModal({ mode: 'alert', text: message })
|
||||
if (code === 0) {
|
||||
window.location = `${blessing.base_url}/auth/login`
|
||||
}
|
||||
},
|
||||
async changeNickName() {
|
||||
const { nickname } = this
|
||||
|
||||
const { code, message } = await this.$http.post(
|
||||
'/user/profile?action=nickname',
|
||||
{ new_nickname: nickname },
|
||||
)
|
||||
if (code === 0) {
|
||||
Array
|
||||
.from(document.querySelectorAll('[data-mark="nickname"]'))
|
||||
.forEach(el => (el.textContent = nickname))
|
||||
toast.success(message)
|
||||
return
|
||||
}
|
||||
showModal({ mode: 'alert', text: message })
|
||||
},
|
||||
async changeEmail() {
|
||||
const { email } = this
|
||||
|
||||
const { code, message } = await this.$http.post(
|
||||
'/user/profile?action=email',
|
||||
{ new_email: email, password: this.currentPassword },
|
||||
)
|
||||
await showModal({ mode: 'alert', text: message })
|
||||
if (code === 0) {
|
||||
window.location = `${blessing.base_url}/auth/login`
|
||||
}
|
||||
},
|
||||
async deleteAccount() {
|
||||
const { deleteConfirm: password } = this
|
||||
|
||||
const { code, message } = await this.$http.post(
|
||||
'/user/profile?action=delete',
|
||||
{ password },
|
||||
)
|
||||
await showModal({ mode: 'alert', text: message })
|
||||
if (code === 0) {
|
||||
window.location = `${blessing.base_url}/auth/login`
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
19
resources/assets/src/views/user/profile/deleteAccount.ts
Normal file
19
resources/assets/src/views/user/profile/deleteAccount.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { post, ResponseBody } from '../../../scripts/net'
|
||||
import { showModal } from '../../../scripts/notify'
|
||||
|
||||
export default async function handler(event: Event) {
|
||||
event.preventDefault()
|
||||
|
||||
const form = event.target as HTMLFormElement
|
||||
const password: string = form.password.value
|
||||
|
||||
const { code, message }: ResponseBody = await post(
|
||||
'/user/profile?action=delete',
|
||||
{ password },
|
||||
)
|
||||
|
||||
await showModal({ mode: 'alert', text: message })
|
||||
if (code === 0) {
|
||||
window.location.href = blessing.base_url
|
||||
}
|
||||
}
|
||||
19
resources/assets/src/views/user/profile/email.ts
Normal file
19
resources/assets/src/views/user/profile/email.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { post, ResponseBody } from '../../../scripts/net'
|
||||
import { showModal } from '../../../scripts/notify'
|
||||
|
||||
export default async function handler(event: Event) {
|
||||
event.preventDefault()
|
||||
|
||||
const form = event.target as HTMLFormElement
|
||||
const email: string = form.email.value
|
||||
const password: string = form.password.value
|
||||
|
||||
const { code, message }: ResponseBody = await post(
|
||||
'/user/profile?action=email',
|
||||
{ new_email: email, password },
|
||||
)
|
||||
await showModal({ mode: 'alert', text: message })
|
||||
if (code === 0) {
|
||||
window.location.href = `${blessing.base_url}/auth/login`
|
||||
}
|
||||
}
|
||||
31
resources/assets/src/views/user/profile/index.ts
Normal file
31
resources/assets/src/views/user/profile/index.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import resetAvatar from './resetAvatar'
|
||||
import passwordFormHandler from './password'
|
||||
import nicknameFormHandler from './nickname'
|
||||
import emailFormHandler from './email'
|
||||
import deleteAccountFormHandler from './deleteAccount'
|
||||
|
||||
const btnResetAvatar = document.querySelector('#reset-avatar')
|
||||
if (btnResetAvatar) {
|
||||
btnResetAvatar.addEventListener('click', resetAvatar)
|
||||
}
|
||||
|
||||
const passwordForm = document.querySelector<HTMLFormElement>('#change-password')
|
||||
if (passwordForm) {
|
||||
passwordForm.addEventListener('submit', passwordFormHandler)
|
||||
}
|
||||
|
||||
const nicknameForm = document.querySelector<HTMLFormElement>('#change-nickname')
|
||||
if (nicknameForm) {
|
||||
nicknameForm.addEventListener('submit', nicknameFormHandler)
|
||||
}
|
||||
|
||||
const emailForm = document.querySelector<HTMLFormElement>('#change-email')
|
||||
if (emailForm) {
|
||||
emailForm.addEventListener('submit', emailFormHandler)
|
||||
}
|
||||
|
||||
const deleteAccountForm = document
|
||||
.querySelector<HTMLFormElement>('#modal-delete-account')
|
||||
if (deleteAccountForm) {
|
||||
deleteAccountForm.addEventListener('submit', deleteAccountFormHandler)
|
||||
}
|
||||
21
resources/assets/src/views/user/profile/nickname.ts
Normal file
21
resources/assets/src/views/user/profile/nickname.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { post, ResponseBody } from '../../../scripts/net'
|
||||
import { showModal } from '../../../scripts/notify'
|
||||
|
||||
export default async function handler(event: Event) {
|
||||
event.preventDefault()
|
||||
|
||||
const form = event.target as HTMLFormElement
|
||||
const nickname: string = form.nickname.value
|
||||
|
||||
const { code, message }: ResponseBody = await post(
|
||||
'/user/profile?action=nickname', { new_nickname: nickname },
|
||||
)
|
||||
showModal({ mode: 'alert', text: message })
|
||||
if (code === 0) {
|
||||
document
|
||||
.querySelectorAll('[data-mark="nickname"]')
|
||||
.forEach(el => {
|
||||
el.textContent = nickname
|
||||
})
|
||||
}
|
||||
}
|
||||
27
resources/assets/src/views/user/profile/password.ts
Normal file
27
resources/assets/src/views/user/profile/password.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { post, ResponseBody } from '../../../scripts/net'
|
||||
import { trans } from '../../../scripts/i18n'
|
||||
import { showModal, toast } from '../../../scripts/notify'
|
||||
|
||||
export default async function handler(event: Event) {
|
||||
event.preventDefault()
|
||||
|
||||
const form = event.target as HTMLFormElement
|
||||
const oldPassword = form.oldPassword.value
|
||||
const newPassword = form.newPassword.value
|
||||
const confirmPassword = form.confirm.value
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
toast.error(trans('auth.invalidConfirmPwd'))
|
||||
;(form.confirm as HTMLInputElement).focus()
|
||||
return
|
||||
}
|
||||
|
||||
const { code, message }: ResponseBody = await post(
|
||||
'/user/profile?action=password',
|
||||
{ current_password: oldPassword, new_password: newPassword },
|
||||
)
|
||||
await showModal({ mode: 'alert', text: message })
|
||||
if (code === 0) {
|
||||
window.location.href = `${blessing.base_url}/auth/login`
|
||||
}
|
||||
}
|
||||
21
resources/assets/src/views/user/profile/resetAvatar.ts
Normal file
21
resources/assets/src/views/user/profile/resetAvatar.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { showModal, toast } from '../../../scripts/notify'
|
||||
import { trans } from '../../../scripts/i18n'
|
||||
import { post, ResponseBody } from '../../../scripts/net'
|
||||
|
||||
export default async function resetAvatar() {
|
||||
try {
|
||||
await showModal({ text: trans('user.resetAvatarConfirm') })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
const { message }: ResponseBody = await post(
|
||||
'/user/profile/avatar',
|
||||
{ tid: 0 },
|
||||
)
|
||||
toast.success(message)
|
||||
document.querySelectorAll<HTMLImageElement>('[alt="User Image"]')
|
||||
.forEach(el => {
|
||||
el.src += `?${new Date().getTime()}`
|
||||
})
|
||||
}
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { flushPromises } from '../../utils'
|
||||
import { showModal, toast } from '@/scripts/notify'
|
||||
import Profile from '@/views/user/Profile.vue'
|
||||
|
||||
jest.mock('@/scripts/notify')
|
||||
|
||||
window.blessing.extra = { unverified: false }
|
||||
|
||||
test('computed values', () => {
|
||||
window.blessing.extra = { admin: true }
|
||||
const wrapper = mount<Vue & { siteName: string, isAdmin: boolean }>(Profile)
|
||||
expect(wrapper.vm.siteName).toBe('Blessing Skin')
|
||||
expect(wrapper.vm.isAdmin).toBeTrue()
|
||||
window.blessing.extra = { admin: false }
|
||||
expect(mount<Vue & { isAdmin: boolean }>(Profile).vm.isAdmin).toBeFalse()
|
||||
})
|
||||
|
||||
test('convert linebreak', () => {
|
||||
const wrapper = mount<Vue & { nl2br(input: string): string }>(Profile)
|
||||
expect(wrapper.vm.nl2br('a\nb\nc')).toBe('a<br>b<br>c')
|
||||
})
|
||||
|
||||
test('reset avatar', async () => {
|
||||
showModal
|
||||
.mockRejectedValueOnce(null)
|
||||
.mockResolvedValue({ value: '' })
|
||||
Vue.prototype.$http.post.mockResolvedValue({ message: 'ok' })
|
||||
const wrapper = mount(Profile)
|
||||
const button = wrapper.find('[data-test=resetAvatar]')
|
||||
document.body.innerHTML += '<img alt="User Image" src="a">'
|
||||
|
||||
button.trigger('click')
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled()
|
||||
|
||||
button.trigger('click')
|
||||
await flushPromises()
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/profile/avatar',
|
||||
{ tid: 0 },
|
||||
)
|
||||
await flushPromises()
|
||||
expect(toast.success).toBeCalledWith('ok')
|
||||
expect(document.querySelector('img')!.src).toMatch(/\d+$/)
|
||||
})
|
||||
|
||||
test('change password', async () => {
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ code: 1, message: 'w' })
|
||||
.mockResolvedValueOnce({ code: 0, message: 'o' })
|
||||
const wrapper = mount(Profile)
|
||||
const form = wrapper.find('[data-test=changePassword]')
|
||||
|
||||
wrapper.setData({ oldPassword: '1' })
|
||||
wrapper.setData({ newPassword: '1' })
|
||||
wrapper.setData({ confirmPassword: '2' })
|
||||
form.trigger('submit')
|
||||
expect(toast.error).toBeCalledWith('auth.invalidConfirmPwd')
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled()
|
||||
|
||||
wrapper.setData({ confirmPassword: '1' })
|
||||
form.trigger('submit')
|
||||
await flushPromises()
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/profile?action=password',
|
||||
{ current_password: '1', new_password: '1' },
|
||||
)
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
|
||||
|
||||
form.trigger('submit')
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'o' })
|
||||
})
|
||||
|
||||
test('change nickname', async () => {
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ code: 1, message: 'w' })
|
||||
.mockResolvedValue({ code: 0, message: 'o' })
|
||||
const wrapper = mount(Profile)
|
||||
const form = wrapper.find('[data-test=changeNickName]')
|
||||
document.body.innerHTML += '<span data-mark="nickname"></span>'
|
||||
|
||||
wrapper.setData({ nickname: 'nickname' })
|
||||
form.trigger('submit')
|
||||
await flushPromises()
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/profile?action=nickname',
|
||||
{ new_nickname: 'nickname' },
|
||||
)
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
|
||||
|
||||
form.trigger('submit')
|
||||
await flushPromises()
|
||||
expect(toast.success).toBeCalledWith('o')
|
||||
expect(document.querySelector('span')!.textContent).toBe('nickname')
|
||||
})
|
||||
|
||||
test('change email', async () => {
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ code: 1, message: 'w' })
|
||||
.mockResolvedValue({ code: 0, message: 'o' })
|
||||
const wrapper = mount(Profile)
|
||||
const form = wrapper.find('[data-test=changeEmail]')
|
||||
|
||||
wrapper.setData({ email: 'a@b.c', currentPassword: 'abc' })
|
||||
form.trigger('submit')
|
||||
await flushPromises()
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/profile?action=email',
|
||||
{ new_email: 'a@b.c', password: 'abc' },
|
||||
)
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
|
||||
|
||||
form.trigger('submit')
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'o' })
|
||||
})
|
||||
|
||||
test('delete account', async () => {
|
||||
window.blessing.extra = { admin: true }
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ code: 1, message: 'w' })
|
||||
.mockResolvedValue({ code: 0, message: 'o' })
|
||||
const wrapper = mount(Profile)
|
||||
const form = wrapper.find('[data-test=deleteAccount]')
|
||||
|
||||
wrapper.setData({ deleteConfirm: 'abc' })
|
||||
form.trigger('submit')
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/profile?action=delete',
|
||||
{ password: 'abc' },
|
||||
)
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
|
||||
|
||||
form.trigger('submit')
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
|
||||
})
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { flushPromises } from '../../../utils'
|
||||
import { showModal } from '@/scripts/notify'
|
||||
import { post } from '@/scripts/net'
|
||||
import handler from '@/views/user/profile/deleteAccount'
|
||||
|
||||
jest.mock('@/scripts/notify')
|
||||
jest.mock('@/scripts/net')
|
||||
|
||||
test('delete account', async () => {
|
||||
post
|
||||
.mockResolvedValueOnce({ code: 1, message: 'w' })
|
||||
.mockResolvedValue({ code: 0, message: 'o' })
|
||||
|
||||
const form = document.createElement('form')
|
||||
form.addEventListener('submit', handler)
|
||||
|
||||
const password = document.createElement('input')
|
||||
password.name = 'password'
|
||||
password.value = 'abc'
|
||||
form.appendChild(password)
|
||||
form.password = password
|
||||
|
||||
const event = new Event('submit')
|
||||
form.dispatchEvent(event)
|
||||
await flushPromises()
|
||||
expect(post).toBeCalledWith(
|
||||
'/user/profile?action=delete',
|
||||
{ password: 'abc' },
|
||||
)
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
|
||||
|
||||
form.dispatchEvent(event)
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'o' })
|
||||
})
|
||||
41
resources/assets/tests/views/user/profile/email.test.ts
Normal file
41
resources/assets/tests/views/user/profile/email.test.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { flushPromises } from '../../../utils'
|
||||
import { showModal } from '@/scripts/notify'
|
||||
import { post } from '@/scripts/net'
|
||||
import handler from '@/views/user/profile/email'
|
||||
|
||||
jest.mock('@/scripts/notify')
|
||||
jest.mock('@/scripts/net')
|
||||
|
||||
test('change email', async () => {
|
||||
post
|
||||
.mockResolvedValueOnce({ code: 1, message: 'w' })
|
||||
.mockResolvedValue({ code: 0, message: 'o' })
|
||||
|
||||
const form = document.createElement('form')
|
||||
form.addEventListener('submit', handler)
|
||||
|
||||
const email = document.createElement('input')
|
||||
email.name = 'email'
|
||||
email.value = 'a@b.c'
|
||||
form.appendChild(email)
|
||||
form.email = email
|
||||
|
||||
const password = document.createElement('input')
|
||||
password.name = 'password'
|
||||
password.value = 'abc'
|
||||
form.appendChild(password)
|
||||
form.password = password
|
||||
|
||||
const event = new Event('submit')
|
||||
form.dispatchEvent(event)
|
||||
await flushPromises()
|
||||
expect(post).toBeCalledWith(
|
||||
'/user/profile?action=email',
|
||||
{ new_email: 'a@b.c', password: 'abc' },
|
||||
)
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
|
||||
|
||||
form.dispatchEvent(event)
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'o' })
|
||||
})
|
||||
37
resources/assets/tests/views/user/profile/nickname.test.ts
Normal file
37
resources/assets/tests/views/user/profile/nickname.test.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { flushPromises } from '../../../utils'
|
||||
import { showModal } from '@/scripts/notify'
|
||||
import { post } from '@/scripts/net'
|
||||
import handler from '@/views/user/profile/nickname'
|
||||
|
||||
jest.mock('@/scripts/notify')
|
||||
jest.mock('@/scripts/net')
|
||||
|
||||
test('change nickname', async () => {
|
||||
document.body.innerHTML = '<span data-mark="nickname"></span>'
|
||||
post
|
||||
.mockResolvedValueOnce({ code: 1, message: 'w' })
|
||||
.mockResolvedValue({ code: 0, message: 'o' })
|
||||
|
||||
const form = document.createElement('form')
|
||||
form.addEventListener('submit', handler)
|
||||
|
||||
const nickname = document.createElement('input')
|
||||
nickname.name = 'nickname'
|
||||
nickname.value = 'nickname'
|
||||
form.appendChild(nickname)
|
||||
form.nickname = nickname
|
||||
|
||||
const event = new Event('submit')
|
||||
form.dispatchEvent(event)
|
||||
await flushPromises()
|
||||
expect(post).toBeCalledWith(
|
||||
'/user/profile?action=nickname',
|
||||
{ new_nickname: 'nickname' },
|
||||
)
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
|
||||
|
||||
form.dispatchEvent(event)
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'o' })
|
||||
expect(document.querySelector('span')!.textContent).toBe('nickname')
|
||||
})
|
||||
53
resources/assets/tests/views/user/profile/password.test.ts
Normal file
53
resources/assets/tests/views/user/profile/password.test.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { flushPromises } from '../../../utils'
|
||||
import { showModal, toast } from '@/scripts/notify'
|
||||
import { post } from '@/scripts/net'
|
||||
import handler from '@/views/user/profile/password'
|
||||
|
||||
jest.mock('@/scripts/notify')
|
||||
jest.mock('@/scripts/net')
|
||||
|
||||
test('change password', async () => {
|
||||
post
|
||||
.mockResolvedValueOnce({ code: 1, message: 'w' })
|
||||
.mockResolvedValue({ code: 0, message: 'o' })
|
||||
|
||||
const form = document.createElement('form')
|
||||
form.addEventListener('submit', handler)
|
||||
|
||||
const oldPassword = document.createElement('input')
|
||||
oldPassword.name = 'oldPassword'
|
||||
oldPassword.value = '1'
|
||||
form.appendChild(oldPassword)
|
||||
form.oldPassword = oldPassword
|
||||
|
||||
const newPassword = document.createElement('input')
|
||||
newPassword.name = 'newPassword'
|
||||
newPassword.value = '1'
|
||||
form.appendChild(newPassword)
|
||||
form.newPassword = newPassword
|
||||
|
||||
const confirm = document.createElement('input')
|
||||
confirm.name = 'confirm'
|
||||
confirm.value = '2'
|
||||
form.appendChild(confirm)
|
||||
form.confirm = confirm
|
||||
|
||||
const event = new Event('submit')
|
||||
form.dispatchEvent(event)
|
||||
await flushPromises()
|
||||
expect(post).not.toBeCalled()
|
||||
expect(toast.error).toBeCalledWith('auth.invalidConfirmPwd')
|
||||
|
||||
confirm.value = '1'
|
||||
form.dispatchEvent(event)
|
||||
await flushPromises()
|
||||
expect(post).toBeCalledWith(
|
||||
'/user/profile?action=password',
|
||||
{ current_password: '1', new_password: '1' },
|
||||
)
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'w' })
|
||||
|
||||
form.dispatchEvent(event)
|
||||
await flushPromises()
|
||||
expect(showModal).toBeCalledWith({ mode: 'alert', text: 'o' })
|
||||
})
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { showModal, toast } from '@/scripts/notify'
|
||||
import { post } from '@/scripts/net'
|
||||
import resetAvatar from '@/views/user/profile/resetAvatar'
|
||||
|
||||
jest.mock('@/scripts/notify')
|
||||
jest.mock('@/scripts/net')
|
||||
|
||||
test('reset avatar', async () => {
|
||||
showModal
|
||||
.mockRejectedValueOnce(null)
|
||||
.mockResolvedValue({ value: '' })
|
||||
post.mockResolvedValue({ message: 'ok' })
|
||||
document.body.innerHTML = '<img alt="User Image" src="a">'
|
||||
|
||||
await resetAvatar()
|
||||
expect(post).not.toBeCalled()
|
||||
|
||||
await resetAvatar()
|
||||
expect(post).toBeCalledWith('/user/profile/avatar', { tid: 0 })
|
||||
expect(toast.success).toBeCalledWith('ok')
|
||||
expect(document.querySelector('img')!.src).toMatch(/\d+$/)
|
||||
})
|
||||
|
|
@ -119,6 +119,7 @@ profile:
|
|||
notice: Click the gear icon "<i class="fa fa-cog"></i>" of any skin in your closet, then click "Set as avatar". We will cut the head segment of that skin for you. If there is no icon like this, please try to unable your ADs blocking extension.
|
||||
wrong-type: You can't set a cape as avatar.
|
||||
success: New avatar was set successfully.
|
||||
reset: Reset Avatar
|
||||
|
||||
password:
|
||||
title: Change Password
|
||||
|
|
|
|||
|
|
@ -2,6 +2,156 @@
|
|||
|
||||
{% block title %}{{ trans('general.profile') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
{{ trans('user.profile.avatar.title') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ trans('user.profile.avatar.notice')|raw }}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" id="reset-avatar">
|
||||
{{ trans('user.profile.avatar.reset') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="card card-warning" method="post" id="change-password">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
{{ trans('user.profile.password.title') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label>{{ trans('user.profile.password.old') }}</label>
|
||||
<input type="password" class="form-control" name="oldPassword" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ trans('user.profile.password.new') }}</label>
|
||||
<input type="password" class="form-control" name="newPassword" required
|
||||
minlength="8" maxlength="32">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ trans('user.profile.password.confirm') }}</label>
|
||||
<input type="password" class="form-control" name="confirm" required
|
||||
minlength="8" maxlength="32">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ trans('user.profile.password.button') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<form class="card card-primary" method="post" id="change-nickname">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
{{ trans('user.profile.nickname.title') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="nickname" required
|
||||
placeholder="{{ trans('user.profile.nickname.rule') }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ trans('general.submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="card card-warning" method="post" id="change-email">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
{{ trans('user.profile.email.title') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<input type="email" class="form-control" name="email" required
|
||||
placeholder="{{ trans('user.profile.email.new') }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control" name="password" required
|
||||
placeholder="{{ trans('user.profile.email.password') }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ trans('user.profile.email.button') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card card-danger">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
{{ trans('user.profile.delete.title') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if user.admin %}
|
||||
<p>{{ trans('user.profile.delete.admin') }}</p>
|
||||
<button class="btn btn-danger" disabled="disabled">
|
||||
{{ trans('user.profile.delete.button') }}
|
||||
</button>
|
||||
{% else %}
|
||||
<p>{{ trans('user.profile.delete.notice', { site: site_name }) }}</p>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
data-toggle="modal"
|
||||
data-target="#modal-delete-account"
|
||||
>
|
||||
{{ trans('user.profile.delete.button') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="modal-delete-account" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content bg-danger">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ trans('user.profile.delete.modal-title') }}
|
||||
</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>{{ trans('user.profile.delete.modal-notice')|nl2br|raw }}</div>
|
||||
<br>
|
||||
<input type="password" class="form-control" required name="password"
|
||||
placeholder="{{ trans('user.profile.delete.password') }}">
|
||||
</div>
|
||||
<div class="modal-footer d-flex justify-content-between">
|
||||
<button type="button" class="btn btn-outline-light" data-dismiss="modal">
|
||||
{{ trans('general.close') }}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-outline-light">
|
||||
{{ trans('general.submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block before_foot %}
|
||||
<script>
|
||||
Object.defineProperty(blessing, 'extra', {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user