Skip to content

Commit eeb073d

Browse files
update profile
1 parent 608cafa commit eeb073d

File tree

11 files changed

+286
-6
lines changed

11 files changed

+286
-6
lines changed

src/api/account.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { endpoints, replacePk } from './endpoints'
33
import {
44
LoginReq, LoginRes,
55
MyInfoRes,
6-
RegisterTeacherReq, RegisterTeacherRes, TokenRefreshReq, TokenRefreshRes, UserDetailRes
6+
RegisterTeacherReq, RegisterTeacherRes, TokenRefreshReq, TokenRefreshRes, UpdateProfileReq, UpdateProfileRes, UserDetailRes
77
} from '@/interfaces/api/account'
88
import { User } from '@/interfaces/user'
99

@@ -32,5 +32,10 @@ export const account = {
3232
const endpoint = replacePk(endpoints.account.users.detail, pk)
3333
const res = await Vue.axios.get(endpoint)
3434
return res.data
35+
},
36+
37+
async updateProfile (payload: UpdateProfileReq): Promise<UpdateProfileRes> {
38+
const res = await Vue.axios.patch(endpoints.account.me.updateProfile, payload)
39+
return res.data
3540
}
3641
}

src/api/endpoints.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export const endpoints = {
77
detail: '/account/users/<pk>/'
88
},
99
me: {
10-
myInfo: '/account/me/'
10+
myInfo: '/account/me/',
11+
updateProfile: '/account/me/'
1112
}
1213
},
1314
classroom: {

src/interfaces/api/account.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,11 @@ export declare interface RegisterTeacherRes {
3535
export declare interface MyInfoRes extends User {}
3636

3737
export declare interface UserDetailRes extends User {}
38+
39+
export declare interface UpdateProfileReq {
40+
name?: User['name'];
41+
phone_number?: User['phone_number'];
42+
avatar?: User['avatar'];
43+
}
44+
45+
export declare interface UpdateProfileRes extends User {}

src/layouts/LayoutDefault.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@
2121
indeterminate
2222
color="primary"
2323
></v-progress-circular>
24-
<span v-else>
24+
<router-link
25+
v-else
26+
:to="{ name: 'MyInfo' }"
27+
class="white--text"
28+
>
2529
{{ user.name }}
26-
</span>
30+
</router-link>
2731
</div>
2832
</v-app-bar>
2933

src/router/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import Home from '../views/Home.vue'
99
import Auth from '../views/auth/Auth.vue'
1010
import Http404 from '../views/http/Http404.vue'
1111

12+
import MyInfo from '../views/account/MyInfo.vue'
13+
import ProfileUpdate from '../views/account/ProfileUpdate.vue'
14+
import ChangePassword from '../views/account/ChangePassword.vue'
15+
1216
import ClassroomList from '../views/classroom/ClassroomList.vue'
1317
import ClassroomCreate from '../views/classroom/ClassroomCreate.vue'
1418
import ClassroomUpdate from '../views/classroom/ClassroomUpdate.vue'
@@ -44,6 +48,23 @@ const routes: Array<RouteConfig> = [
4448
layout: LayoutNoAppbar
4549
}
4650
},
51+
...prefixWith('/account', [
52+
{
53+
path: '',
54+
name: 'MyInfo',
55+
component: MyInfo
56+
},
57+
{
58+
path: '/edit',
59+
name: 'ProfileUpdate',
60+
component: ProfileUpdate
61+
},
62+
{
63+
path: '/change-password',
64+
name: 'ChangePassword',
65+
component: ChangePassword
66+
}
67+
]),
4768
...prefixWith('/classrooms', [
4869
{
4970
path: '',

src/store/account.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Api } from '@/api'
2-
import { LoginReq, RegisterTeacherReq } from '@/interfaces/api/account'
2+
import { LoginReq, RegisterTeacherReq, UpdateProfileReq } from '@/interfaces/api/account'
33
import { User } from '@/interfaces/user'
44
import { loadAccessToken, loadRefreshToken, setAccessToken, setRefreshToken } from '@/utils/auth'
55
import { Module } from 'vuex'
@@ -75,6 +75,11 @@ export const account: Module<AccountState, RootState> = {
7575
async getInfo ({ commit }): Promise<void> {
7676
const data = await Api.account.getMyInfo()
7777
commit('SET_LOGGED_IN_USER', data)
78+
},
79+
80+
async updateProfile ({ commit }, payload: UpdateProfileReq): Promise<void> {
81+
const data = await Api.account.updateProfile(payload)
82+
commit('SET_LOGGED_IN_USER', data)
7883
}
7984
}
8085
}

src/views/Home.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export default class Home extends Vue {
7676
description: this.isTeacher ? classroomTeacherDescription : classroomStudentDescription
7777
}
7878
]
79+
7980
if (this.isTeacher) {
8081
links.push(
8182
{
@@ -85,6 +86,14 @@ export default class Home extends Vue {
8586
}
8687
)
8788
}
89+
90+
links.push(...[
91+
{
92+
text: 'Profile',
93+
to: { name: 'MyInfo' },
94+
description: 'Manage your profile.'
95+
}
96+
])
8897
return links
8998
}
9099
}

src/views/account/ChangePassword.vue

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<template>
2+
<v-container class="container-sm">
3+
ChangePassword
4+
</v-container>
5+
</template>
6+
7+
<script lang="ts">
8+
import { Vue, Component } from 'vue-property-decorator'
9+
10+
@Component
11+
export default class ChangePassword extends Vue {
12+
13+
}
14+
</script>
15+
16+
<style scoped lang="scss">
17+
18+
</style>

src/views/account/MyInfo.vue

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<template>
2+
<v-container class="container-sm">
3+
<v-breadcrumbs :items="breadcrumbs"></v-breadcrumbs>
4+
5+
<h1>Profile</h1>
6+
<v-divider></v-divider>
7+
8+
<div class="mt-5">
9+
<v-row>
10+
<v-col cols="auto">
11+
<v-avatar
12+
:color="user.avatar !== null ? 'white' : 'primary'"
13+
size="256"
14+
tile
15+
>
16+
<v-img
17+
v-if="user.avatar !== null"
18+
:src="user.avatar"
19+
:alt="user.name"
20+
></v-img>
21+
<span
22+
v-else
23+
class="white--text"
24+
>
25+
{{ user.name.charAt(0) }}
26+
</span>
27+
</v-avatar>
28+
</v-col>
29+
<v-col cols="auto">
30+
<div>
31+
<h2>{{ user.name }}</h2>
32+
<p class="caption">{{ user.email }}</p>
33+
</div>
34+
<div>
35+
<v-row>
36+
<v-col cols="auto">
37+
<p>Account</p>
38+
<p>Phone number</p>
39+
</v-col>
40+
<v-col cols="auto" class="font-weight-bold">
41+
<p>{{ account }}</p>
42+
<p>{{ user.phone_number }}</p>
43+
</v-col>
44+
</v-row>
45+
</div>
46+
<div>
47+
<p>
48+
<router-link :to="{ name: 'ProfileUpdate' }">
49+
Edit profile
50+
</router-link>
51+
</p>
52+
<p>
53+
<router-link :to="{ name: 'ChangePassword' }">
54+
Change password
55+
</router-link>
56+
</p>
57+
</div>
58+
</v-col>
59+
</v-row>
60+
</div>
61+
</v-container>
62+
</template>
63+
64+
<script lang="ts">
65+
import { User } from '@/interfaces/user'
66+
import { Vue, Component } from 'vue-property-decorator'
67+
import { mapGetters, mapState } from 'vuex'
68+
69+
@Component({
70+
computed: {
71+
...mapState('account', {
72+
user: 'loggedInUser'
73+
}),
74+
...mapGetters('account', [
75+
'isTeacher'
76+
])
77+
}
78+
})
79+
export default class MyInfo extends Vue {
80+
breadcrumbs = [
81+
{ text: 'Home', to: { name: 'Home' }, exact: true },
82+
{ text: 'Profile', to: { name: 'MyInfo' }, exact: true }
83+
]
84+
85+
user!: User
86+
isTeacher!: boolean
87+
88+
get account (): string {
89+
return this.isTeacher ? 'Teacher' : 'Student'
90+
}
91+
}
92+
</script>
93+
94+
<style scoped lang="scss">
95+
96+
</style>

src/views/account/ProfileUpdate.vue

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<template>
2+
<v-container class="container-sm">
3+
<v-breadcrumbs :items="breadcrumbs"></v-breadcrumbs>
4+
5+
<h1>Edit profile</h1>
6+
<v-divider></v-divider>
7+
8+
<v-card
9+
v-if="localUser !== null"
10+
class="mt-5"
11+
>
12+
<v-card-text>
13+
<v-form>
14+
<v-text-field
15+
v-model="name"
16+
label="Name"
17+
:error-messages="nameErrs"
18+
:error-count="nameErrs.length"
19+
></v-text-field>
20+
<v-text-field
21+
v-model="phoneNumber"
22+
label="Phone number"
23+
:error-messages="phoneNumberErrs"
24+
:error-count="phoneNumberErrs.length"
25+
></v-text-field>
26+
</v-form>
27+
</v-card-text>
28+
29+
<v-card-actions>
30+
<v-btn
31+
color="primary"
32+
min-width="110"
33+
@click="saveProfile"
34+
:loading="loading"
35+
>
36+
Save
37+
</v-btn>
38+
</v-card-actions>
39+
</v-card>
40+
</v-container>
41+
</template>
42+
43+
<script lang="ts">
44+
import { UpdateProfileReq } from '@/interfaces/api/account'
45+
import { User } from '@/interfaces/user'
46+
import { snakeCaseToCamelCase, unexpectedExc } from '@/utils'
47+
import { assertErrCode, status } from '@/utils/status-codes'
48+
import { Vue, Component } from 'vue-property-decorator'
49+
import { mapState } from 'vuex'
50+
51+
@Component({
52+
computed: {
53+
...mapState('account', {
54+
user: 'loggedInUser'
55+
})
56+
}
57+
})
58+
export default class ProfileUpdate extends Vue {
59+
// eslint-disable-next-line no-undef
60+
[key: string]: unknown
61+
62+
breadcrumbs = [
63+
{ text: 'Home', to: { name: 'Home' }, exact: true },
64+
{ text: 'Profile', to: { name: 'MyInfo' }, exact: true },
65+
{ text: 'Edit', to: { name: 'ProfileUpdate' }, exact: true }
66+
]
67+
68+
user!: User
69+
name: User['name'] = ''
70+
phoneNumber: User['phone_number'] = ''
71+
nameErrs: string[] = []
72+
phoneNumberErrs: string[] = []
73+
loading = false
74+
75+
created (): void {
76+
this.name = this.user.name
77+
this.phoneNumber = this.user.phone_number
78+
}
79+
80+
saveProfile (): void {
81+
if (this.loading) return
82+
this.loading = true
83+
84+
const payload: UpdateProfileReq = {
85+
name: this.name,
86+
phone_number: this.phoneNumber
87+
}
88+
89+
this.$store.dispatch('account/updateProfile', payload)
90+
.then(() => {
91+
this.$router.push({ name: 'MyInfo' })
92+
})
93+
.catch(err => {
94+
if (assertErrCode(err, status.HTTP_400_BAD_REQUEST)) {
95+
const data = err.response.data
96+
Object.entries(data).forEach(([field, errMsgs]) => {
97+
const attr = `${snakeCaseToCamelCase(field)}Errs`
98+
this[attr] = errMsgs
99+
})
100+
} else {
101+
unexpectedExc(err)
102+
}
103+
})
104+
.finally(() => {
105+
this.loading = false
106+
})
107+
}
108+
}
109+
</script>
110+
111+
<style scoped lang="scss">
112+
113+
</style>

0 commit comments

Comments
 (0)