@@ -0,0 +1,32 @@ | |||
<template> | |||
<div class="entries"> | |||
<span class="entry" v-for="entry in entries" | |||
@click="$emit('select', entry.value)" | |||
>{{entry.text}}</span> | |||
</div> | |||
</template> | |||
<style scoped> | |||
.entries { | |||
display: flex; | |||
flex-flow: column; | |||
position: absolute; | |||
left: 0; | |||
background: var(--secondary-bg); | |||
width: 100%; | |||
margin-top: 5px; | |||
z-index: 3; | |||
border: 1px solid black; | |||
} | |||
.entry { | |||
border-bottom: 1px solid var(--text); | |||
padding: 5px; | |||
cursor: pointer; | |||
} | |||
</style> | |||
<script setup> | |||
const props = defineProps(['entries']) | |||
const emits = defineEmits(['select']) | |||
</script> |
@@ -38,6 +38,16 @@ | |||
<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> | |||
@@ -67,6 +77,7 @@ | |||
<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 | |||
@@ -78,6 +89,9 @@ 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']) | |||
let user = Object.assign({}, props.user) | |||
@@ -176,8 +190,19 @@ function changeLetterhead(blob) { | |||
}) | |||
} | |||
function setAddress(a) { | |||
addresses.value = null | |||
address.value = { | |||
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, | |||
} | |||
} | |||
function saveProfile() { | |||
console.log(user.firstName) | |||
fetch(`/api/user`, | |||
{method: 'PATCH', | |||
body: JSON.stringify(user), | |||
@@ -216,6 +241,31 @@ function changePassword(f) { | |||
}) | |||
} | |||
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) | |||
@@ -226,4 +276,13 @@ watch(props.user, (u) => { | |||
} | |||
}, {immediate: true}) | |||
onMounted(() => { | |||
}) | |||
</script> | |||
<style scoped> | |||
div.address-entry input { | |||
width: 100%; | |||
} | |||
</style> |
@@ -1,9 +0,0 @@ | |||
/* | |||
package main | |||
var config = map[string]string { | |||
"dbUsername": "", | |||
"dbPassword": "", | |||
} | |||
*/ | |||
package main |
@@ -2,6 +2,7 @@ | |||
CREATE TABLE address ( | |||
id INT AUTO_INCREMENT, | |||
full VARCHAR(200) UNIQUE NOT NULL, | |||
street VARCHAR(40) UNIQUE NOT NULL, | |||
city VARCHAR(40) UNIQUE NOT NULL, | |||
region VARCHAR(40) UNIQUE NOT NULL, | |||
@@ -11,6 +11,7 @@ | |||
}, | |||
"devDependencies": { | |||
"css-loader": "^6.7.1", | |||
"dotenv-webpack": "^8.0.1", | |||
"vue-loader": "^17.0.1", | |||
"vue-template-compiler": "^2.7.13", | |||
"webpack": "^5.74.0", | |||
@@ -1277,6 +1278,39 @@ | |||
"integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==", | |||
"optional": true | |||
}, | |||
"node_modules/dotenv": { | |||
"version": "8.6.0", | |||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", | |||
"integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", | |||
"dev": true, | |||
"engines": { | |||
"node": ">=10" | |||
} | |||
}, | |||
"node_modules/dotenv-defaults": { | |||
"version": "2.0.2", | |||
"resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", | |||
"integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", | |||
"dev": true, | |||
"dependencies": { | |||
"dotenv": "^8.2.0" | |||
} | |||
}, | |||
"node_modules/dotenv-webpack": { | |||
"version": "8.0.1", | |||
"resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-8.0.1.tgz", | |||
"integrity": "sha512-CdrgfhZOnx4uB18SgaoP9XHRN2v48BbjuXQsZY5ixs5A8579NxQkmMxRtI7aTwSiSQcM2ao12Fdu+L3ZS3bG4w==", | |||
"dev": true, | |||
"dependencies": { | |||
"dotenv-defaults": "^2.0.2" | |||
}, | |||
"engines": { | |||
"node": ">=10" | |||
}, | |||
"peerDependencies": { | |||
"webpack": "^4 || ^5" | |||
} | |||
}, | |||
"node_modules/ee-first": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", | |||
@@ -9,6 +9,7 @@ | |||
}, | |||
"devDependencies": { | |||
"css-loader": "^6.7.1", | |||
"dotenv-webpack": "^8.0.1", | |||
"vue-loader": "^17.0.1", | |||
"vue-template-compiler": "^2.7.13", | |||
"webpack": "^5.74.0", | |||
@@ -29,6 +29,7 @@ import ( | |||
type Address struct { | |||
Id int `json:"id"` | |||
Full string `json:"full"` | |||
Street string `json:"street"` | |||
City string `json:"city"` | |||
Region string `json:"region"` | |||
@@ -1017,7 +1018,6 @@ func changePassword(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||
claim, err := getClaims(r) | |||
err = json.NewDecoder(r.Body).Decode(&pass) | |||
if err != nil { http.Error(w, "Bad fields.", 422); return } | |||
fmt.Println(pass) | |||
if checkPassword(db, claim.Id, pass.Old) { | |||
err = setPassword(db, claim.Id, pass.New) | |||
@@ -1,5 +1,6 @@ | |||
const { VueLoaderPlugin } = require('vue-loader') | |||
const path = require('path'); | |||
const path = require('path') | |||
const Dotenv = require('dotenv-webpack') | |||
module.exports = { | |||
entry: './main.js', | |||
@@ -23,5 +24,5 @@ module.exports = { | |||
} | |||
}, | |||
// Required for also applying rules to sections of SFC | |||
plugins: [new VueLoaderPlugin()], | |||
plugins: [new VueLoaderPlugin(), new Dotenv({systemVars: true})], | |||
}; |