Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

323 рядки
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>