Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

291 line
6.3 KiB

  1. <template>
  2. <div class="panel">
  3. <template v-if="user">
  4. <side-bar v-if="user"
  5. :role="user && user.status"
  6. :avatar="user.avatar"
  7. :active="active">
  8. </side-bar>
  9. <div v-if="loading" class="page loading">
  10. <span class="error" >{{loadingError}}</span>
  11. <spinner></spinner>
  12. </div>
  13. <home :user="user" v-else-if="active == 1" />
  14. <new-estimate
  15. :user="user"
  16. :fees="fees"
  17. :token="token"
  18. v-else-if="active == 2" />
  19. <estimates
  20. :user="user"
  21. :fees="fees"
  22. v-else-if="active == 3"
  23. :token="token"
  24. @addFeeTemp="(f) => fees.push(f)"
  25. @removeFeeTemp="(fee) => fees = fees.filter(f => f.id != fee.id)"
  26. @download="downloadEstimate"
  27. />
  28. <settings
  29. :user="user"
  30. :token="token"
  31. @updateAvatar="updateAvatar"
  32. @updateLetterhead="updateLetterhead"
  33. v-else-if="active == 4" />
  34. <sign-out :user="user" v-else-if="active == 5" />
  35. </template>
  36. <template v-if="!user && active == 6">
  37. <login @login="start" />
  38. </template>
  39. </div>
  40. </template>
  41. <script>
  42. import SideBar from "./sidebar.vue"
  43. import Spinner from "./spinner.vue"
  44. import Home from "./home.vue"
  45. import NewEstimate from "./new/new.vue"
  46. import Estimates from "./estimates.vue"
  47. import Settings from "./settings.vue"
  48. import SignOut from "./sign-out.vue"
  49. import Login from "./login.vue"
  50. function getCookie(name) {
  51. var re = new RegExp(name + "=([^;]+)")
  52. var value = re.exec(document.cookie)
  53. return (value != null) ? unescape(value[1]) : null
  54. }
  55. function refreshToken() {
  56. const token = getCookie("skouter")
  57. const timeout = setTimeout(this.refreshToken, 1000*60*25)
  58. fetch(`/api/token`,
  59. {method: 'GET',
  60. headers: {
  61. "Accept": "application/json",
  62. "Authorization": `Bearer ${token}`,
  63. },
  64. }).then(response => {
  65. if (!response.ok) {
  66. console.log("Error refreshing token.")
  67. clearTimeout(timeout)
  68. window.location.hash = '#login'
  69. } else {
  70. this.token = getCookie("skouter")
  71. }
  72. })
  73. }
  74. function getUser() {
  75. const token = getCookie("skouter")
  76. this.token = token
  77. return fetch(`/api/user`,
  78. {method: 'GET',
  79. headers: {
  80. "Accept": "application/json",
  81. "Authorization": `Bearer ${token}`,
  82. },
  83. }).then(response => {
  84. if (response.ok) {
  85. return response.json()
  86. } else {
  87. // Redirect to login if starting token is invalid
  88. window.location.hash = '#login'
  89. }
  90. }).then (result => {
  91. if (!result) return // Exit if token is invalid
  92. this.user = result
  93. if (this.user.avatar) return
  94. return getAvatar(token)
  95. }).then(b => {
  96. const validTypes = ['image/jpeg', 'image/png']
  97. if (!b || !validTypes.includes(b.type) || b.size <= 1) {
  98. fetch("/assets/image/default-avatar.jpg").
  99. then(r => r.blob()).then( a => this.user.avatar = a )
  100. } else {
  101. this.user.avatar = b
  102. }
  103. return getLetterhead(token)
  104. }).then(b => {
  105. const validTypes = ['image/jpeg', 'image/png']
  106. if (!b || !validTypes.includes(b.type) || b.size <= 1) {
  107. fetch("/assets/image/default-avatar.jpg").
  108. then(r => r.blob()).then( a => this.user.letterhead = a )
  109. } else {
  110. this.user.letterhead = b
  111. }
  112. })
  113. }
  114. function getAvatar(t) {
  115. return fetch("/api/user/avatar",
  116. {method: 'GET',
  117. headers: {
  118. "Accept": "application/json",
  119. "Authorization": `Bearer ${t || this.token}`,
  120. }
  121. }).then(r => r.blob())
  122. }
  123. function getLetterhead(t) {
  124. return fetch("/api/user/letterhead",
  125. {method: 'GET',
  126. headers: {
  127. "Accept": "application/json",
  128. "Authorization": `Bearer ${t || this.token}`,
  129. }
  130. }).then(r => r.blob())
  131. }
  132. function updateAvatar() {
  133. const token = getCookie("skouter")
  134. getAvatar(token).then(b => this.user.avatar = b)
  135. }
  136. function updateLetterhead() {
  137. const token = getCookie("skouter")
  138. getLetterhead(token).then(b => this.user.letterhead = b)
  139. }
  140. function getFees() {
  141. const token = getCookie("skouter")
  142. return fetch(`/api/fees`,
  143. {method: 'GET',
  144. headers: {
  145. "Accept": "application/json",
  146. "Authorization": `Bearer ${token}`,
  147. },
  148. }).then(response => {
  149. if (response.ok) { return response.json() }
  150. }).then (result => {
  151. if (!result || !result.length) return // Exit if token is invalid or no fees are saved
  152. this.fees = result
  153. })
  154. }
  155. // Used to check the current section of the app generally without a regex match
  156. // each time.
  157. function active() {
  158. if (this.hash == '' || this.hash == '#') {
  159. return 1
  160. } else if (/^#new\/?/.exec(this.hash)) {
  161. return 2
  162. } else if (/^#estimates\/?/.exec(this.hash)) {
  163. return 3
  164. } else if (/^#settings\/?/.exec(this.hash)) {
  165. return 4
  166. } else if (/^#sign-out\/?/.exec(this.hash)) {
  167. return 5
  168. } else if (/^#login\/?/.exec(this.hash)) {
  169. return 6
  170. } else if (/^#estimate\/?/.exec(this.hash)) {
  171. return 7
  172. } else {
  173. return 0
  174. }
  175. }
  176. // Fetch data before showing UI. If requests fail, assume token is expired.
  177. function start() {
  178. this.loading = true
  179. let loaders = []
  180. loaders.push(this.getUser())
  181. loaders.push(this.getFees())
  182. Promise.all(loaders).then((a, b) => {
  183. this.loading = false
  184. if (!b) {
  185. // Time untill token expiration may have elapsed before the page
  186. // reloaded
  187. this.refreshToken()
  188. }
  189. }).catch(error => {
  190. console.log("An error occured %O", error)
  191. this.loadingError = "Could not initialize app."
  192. window.location.hash = 'login'
  193. })
  194. }
  195. function downloadEstimate(estimate) {
  196. fetch(`/api/pdf`,
  197. {method: 'POST',
  198. body: JSON.stringify(estimate),
  199. headers: {
  200. "Accept": "application/json",
  201. "Authorization": `Bearer ${this.token}`,
  202. },
  203. }).then(response => {
  204. if (response.ok) { return response.blob() }
  205. }).then (result => {
  206. console.log(result)
  207. if (!result) return // Exit if token is invalid or blank file returned
  208. })
  209. }
  210. export default {
  211. components: {
  212. SideBar,
  213. Spinner,
  214. Home,
  215. NewEstimate,
  216. Estimates,
  217. Settings,
  218. SignOut,
  219. Login
  220. },
  221. computed: { active },
  222. methods: {
  223. getCookie,
  224. start,
  225. getUser,
  226. getFees,
  227. refreshToken,
  228. updateAvatar,
  229. getAvatar,
  230. updateLetterhead,
  231. getLetterhead,
  232. downloadEstimate,
  233. },
  234. data() {
  235. return {
  236. loading: true,
  237. user: null,
  238. hash: window.location.hash,
  239. fees: [],
  240. loadingError: "",
  241. token: '',
  242. }
  243. },
  244. created() {
  245. window.onhashchange = () => this.hash = window.location.hash
  246. this.token = this.getCookie("skouter")
  247. if (!this.token) {
  248. window.location.hash = 'login'
  249. this.loading = false
  250. return
  251. }
  252. this.start()
  253. }
  254. }
  255. </script>