main
vue 188 lines 4.33 KB
Raw
1 <template>
2 <div class="page">
3 <n-card class="header flex flex-col" content-class="p-0!">
4 <div class="user-info flex flex-wrap">
5 <div class="propic">
6 <n-avatar :size="100" :src="userPic" round :img-props="{ alt: 'avatar' }" />
7 <ImageCropper
8 v-if="propicEnabled"
9 v-slot="{ openCropper }"
10 shape="circle"
11 placeholder="Select your profile picture"
12 @crop="setCroppedImage"
13 >
14 <Icon :name="EditIcon" :size="16" class="edit" @click="openCropper()" />
15 </ImageCropper>
16 </div>
17 <div class="info flex grow flex-col justify-center">
18 <div class="name">
19 <h1>{{ userName }}</h1>
20 </div>
21 <div class="details flex flex-wrap">
22 <div class="item">
23 <n-tooltip placement="top">
24 <template #trigger>
25 <div class="tooltip-wrap">
26 <Icon :name="RoleIcon" />
27 <span>{{ userRole }}</span>
28 </div>
29 </template>
30 <span>Role</span>
31 </n-tooltip>
32 </div>
33 <div v-if="userEmail" class="item">
34 <div class="tooltip-wrap">
35 <Icon :name="EmailIcon" />
36 <span>{{ userEmail }}</span>
37 </div>
38 </div>
39 </div>
40 </div>
41 <div class="actions">
42 <ImageCropper
43 v-if="propicEnabled"
44 v-slot="{ openCropper }"
45 shape="circle"
46 placeholder="Select your profile picture"
47 @crop="setCroppedImage"
48 >
49 <n-button size="large" type="primary" @click="openCropper()">Edit profile image</n-button>
50 </ImageCropper>
51 </div>
52 </div>
53 <div class="section-selector">
54 <n-tabs v-model:value="tabActive">
55 <n-tab name="settings">Settings</n-tab>
56 <n-tab name="security">Security</n-tab>
57 </n-tabs>
58 </div>
59 </n-card>
60
61 <div class="main">
62 <n-tabs v-model:value="tabActive" tab-class="hidden!" animated>
63 <n-tab-pane name="settings">
64 <ProfileSettings />
65 </n-tab-pane>
66 <n-tab-pane name="security">
67 <div class="flex flex-col gap-4">
68 <ChangePasswordCard />
69 <TotpToggle />
70 </div>
71 </n-tab-pane>
72 </n-tabs>
73 </div>
74 </div>
75 </template>
76
77 <script lang="ts" setup>
78 import type { ImageCropperResult } from "@/components/common/ImageCropper.vue"
79 import { NAvatar, NButton, NCard, NTab, NTabPane, NTabs, NTooltip } from "naive-ui"
80 import { ref } from "vue"
81 import ChangePasswordCard from "@/components/auth/ChangePassword.vue"
82 import TotpToggle from "@/components/auth/TotpToggle.vue"
83 import Icon from "@/components/common/Icon.vue"
84 import ImageCropper from "@/components/common/ImageCropper.vue"
85 import ProfileSettings from "@/components/profile/ProfileSettings.vue"
86
87 import { useAuthStore } from "@/stores/auth"
88
89 const propicEnabled = false
90
91 const RoleIcon = "tabler:user"
92 const EditIcon = "uil:image-edit"
93 const EmailIcon = "carbon:email"
94
95 const tabActive = ref("settings")
96 const authStore = useAuthStore()
97
98 const userRole = authStore.userRoleName
99 const userName = authStore.userName
100 const userEmail = authStore.userEmail
101 const userPic = ref(authStore.userPic)
102
103 function setCroppedImage(result: ImageCropperResult) {
104 const canvas = result.canvas as HTMLCanvasElement
105 userPic.value = canvas.toDataURL()
106 }
107 </script>
108
109 <style lang="scss" scoped>
110 .page {
111 .header {
112 .user-info {
113 gap: 30px;
114 padding: 30px;
115 padding-bottom: 20px;
116 border-block-end: 1px solid var(--border-color);
117 container-type: inline-size;
118
119 .propic {
120 position: relative;
121 height: 100px;
122
123 .edit {
124 display: none;
125 align-items: center;
126 justify-content: center;
127 background-color: var(--primary-color);
128 color: var(--bg-default-color);
129 position: absolute;
130 width: 26px;
131 height: 26px;
132 border-radius: 50%;
133 top: -1px;
134 right: -1px;
135 border: 1px solid var(--bg-default-color);
136 cursor: pointer;
137 }
138 }
139 .info {
140 .name {
141 margin-bottom: 12px;
142
143 @media (max-width: 450px) {
144 h1 {
145 font-size: 28px;
146 }
147 }
148 }
149
150 .details {
151 gap: 24px;
152
153 .item {
154 .tooltip-wrap {
155 display: flex;
156 align-items: center;
157 gap: 8px;
158 line-height: 1;
159 }
160 }
161 }
162 }
163
164 @container (max-width: 900px) {
165 .propic {
166 .edit {
167 display: flex;
168 }
169 }
170 }
171 }
172 .section-selector {
173 padding: 0px 30px;
174 padding-top: 15px;
175
176 :deep() {
177 .n-tabs .n-tabs-tab {
178 padding-bottom: 20px;
179 }
180 }
181 }
182 }
183
184 .main {
185 margin-top: 18px;
186 }
187 }
188 </style>