<template> <div class="page settings"> <h2>Settings</h2> <section class="form inputs"> <h3>Avatar</h3> <canvas class="displayer" width="200" height="200" ref="avatar"></canvas> <input type="file" @change="e => changeAvatar(e.target.files[0])" /> <button @click="uploadAvatar">Upload</button> <label class="error">{{avatarError}}</label> </section> <section class="form inputs letterhead"> <h3>Letterhead</h3> <canvas class="displayer" height="200" ref="letterhead"></canvas> <input type="file" @change="e => {setLetterhead(e.target.files[0])}" /> <button @click="uploadLetterhead">Upload</button> <label class="error">{{letterheadError}}</label> </section> <section class="form inputs special"> <h3>Profile</h3> <label for="">First Name</label> <input type="text" v-model="user.firstName"> <label for="">Last Name</label> <input type="text" :value="user.lastName"> <label for="">NMLS ID</label> <input type="text"> <label for="">Branch ID</label> <input type="text" :value="user.branchId" disabled> <select id="" name="" :value="user.country"> <option value="USA">USA</option> <option value="Canada">Canada</option> </select> <div class="address-entry"> <label for="">Address</label> <input type="text" @input="searchLocation" :value="address.full"> <dropdown v-if="addresses && addresses.length" :entries="addresses.map(a => ({text: a.full_address, value: a}))" @select="setAddress" > </dropdown> </div> <button @click="saveProfile">Save</button> </section> <section class="form inputs special"> <h3>Reset Password</h3> <label for="">Old Password</label> <input type="password" v-model="oldPass"> <label for="">New Password</label> <input type="password" v-model="newPass"> <label for="">Confirm Password</label> <input type="password" v-model="confirmPass"> <button :disabled="!(oldPass && newPass && confirmPass)" @click="changePassword">Save</button> <label class="error">{{passError}}</label> </section> <Dialog v-if="ready" @close="() => ready = false"> <h3>Confirm your current password to save changes.</h3> <input type="text"> <button>Confirm</button> </Dialog> </div> </template> <script setup> import { ref, watch, onMounted } from "vue" import Dialog from "./dialog.vue" import Dropdown from "./dropdown.vue" let avatar = ref(null) // the canvas element let letterhead = ref(null) // the canvas element let ready = ref(false) let avatarChanged = ref(false) let avatarError = ref('') let letterheadError = ref('') let passError = ref('') let oldPass = ref('') let newPass = ref('') let confirmPass = ref('') const locationsId = ref(null) const addresses = ref([]) const address = ref({}) const props = defineProps(['user', 'token']) const emit = defineEmits(['updateAvatar', 'updateLetterhead']) const user = Object.assign({}, props.user) function save() { } function check() { ready.value = true } function uploadAvatar() { avatar.value.toBlob(b => { fetch(`/api/user/avatar`, {method: 'POST', body: b, headers: { "Accept": "application/json", "Authorization": `Bearer ${props.token}`, }, }).then(resp => { if (resp.ok) {emit('updateAvatar')} }) }) } function uploadLetterhead() { letterhead.value.toBlob(b => { fetch(`/api/user/letterhead`, {method: 'POST', body: b, headers: { "Accept": "application/json", "Authorization": `Bearer ${props.token}`, }, }).then(resp => { if (resp.ok) {emit('updateLetterhead')} }) }) } function setLetterhead(f) { letterheadError.value = "" const validTypes = ['image/jpeg', 'image/png'] if (!validTypes.includes(f.type)) { letterheadError.value = 'Image must be JPEG of PNG format' return } fetch(`/api/letterhead`, {method: 'POST', body: f, headers: { "Accept": "application/json", "Authorization": `Bearer ${props.token}`, }, }).then(resp => { if (resp.ok) { resp.blob().then(b => changeLetterhead(b)) } else { resp.text().then(e => letterheadError.value = e) } }) } function changeAvatar(blob) { const validTypes = ['image/jpeg', 'image/png'] if (!validTypes.includes(blob?.type)) { avatarError.value = 'Image must be JPEG of PNG format' return } avatarError.value = '' createImageBitmap(blob, {resizeWidth: 200, resizeHeight: 200, resizeQuality: 'medium'}). then((img) => { avatar.value.getContext("2d").drawImage(img, 0, 0, 200, 200) }) } function changeLetterhead(blob) { const validTypes = ['image/jpeg', 'image/png'] if (!validTypes.includes(blob.type)) { letterheadError.value = 'Image must be JPEG of PNG format' return } createImageBitmap(blob). then((img) => { let ctx = letterhead.value.getContext("2d") ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) ctx.drawImage(img, 0, 0) }) } function setAddress(a) { address.value = { id: address.value.id, full: a.full_address, street: a.address, city: a.context?.place.name ?? '', region: a.context?.region?.name ?? '', country: a.context?.country?.name ?? '', zip: a.context?.postcode?.name ?? '', } addresses.value = null } function saveProfile() { user.value.address = address.value fetch(`/api/user`, {method: 'PATCH', body: JSON.stringify(user.value), headers: { "Accept": "application/json", "Authorization": `Bearer ${props.token}`, }, }).then(resp => { if (resp.ok) {} }) } function changePassword(f) { fetch(`/api/user/password`, {method: 'POST', body: JSON.stringify({ old: oldPass.value, new: newPass.value, confirm: confirmPass.value, }), headers: { "Accept": "application/json", "Authorization": `Bearer ${props.token}`, }, }).then(resp => { if (resp.ok) { resp.blob().then(b => { oldPass.value = "" newPass.value = "" confirmPass.value = "" passError.value = "" }) } else { resp.text().then(e => passError.value = e) } }) } function searchLocation(e) { address.value.full = e.target.value clearTimeout(locationsId.value) locationsId.value = setTimeout(getLocations, 1000, e) } function getLocations(e) { let prefix = "https://api.mapbox.com/search/searchbox/v1/suggest?" let search = e.target.value let key = encodeURIComponent(process.env.MAPBOX_API_KEY) if (!search) return fetch(`${prefix}q=${search}&proximity=ip&access_token=${key}&session_token=1` ).then(resp => resp.json()).then(entries => { if (!entries?.suggestions) { addresses.value = null return } addresses.value = entries.suggestions.filter(e => e.full_address) }) } watch(props.user, (u) => { if (props.user.avatar) { changeAvatar(props.user.avatar) } if (props.user.letterhead) { changeLetterhead(props.user.letterhead) } address.value = Object.assign({}, props.user.address) user.value = Object.assign({}, props.user) user.value.address = address.value }, {immediate: true}) </script> <style scoped> div.address-entry { display: flex; flex-flow: column; position: relative; } </style>