Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

323 lines
7.0 KiB

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