Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 
 

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