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

330 строки
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>