Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
 
 
 
 
 
 

292 line
7.4 KiB

  1. <template>
  2. <div>
  3. <section class="form inputs">
  4. <h3>Loan</h3>
  5. <label>Name</label>
  6. <input :value="loan.title" required
  7. @input="(e) => $emit('update:name', stripLetters(e))">
  8. <button @click="() => $emit('del')">Delete</button>
  9. </section>
  10. <section class="form inputs">
  11. <div class="hint">
  12. <img class="icon" src="/assets/image/icon/question-circle.svg" alt="">
  13. <div class="tooltip">
  14. <p>Assumes borrower is not self employed, not bankrupt in the past 7
  15. years, a citizen, and intends to occupy the property.</p>
  16. </div>
  17. </div>
  18. <h3>Borrower</h3>
  19. <label>Number of Borrowers</label>
  20. <input :value="estimate.borrower.num"
  21. @input="(e) => $emit('update:borrowerNum', stripInt(e))">
  22. <label>Credit Score</label>
  23. <input :value="estimate.borrower.credit"
  24. @input="(e) => $emit('update:borrowerCredit', stripInt(e))">
  25. <label>Monthly Income ($)</label>
  26. <input :value="estimate.borrower.income"
  27. @input="(e) => $emit('update:borrowerIncome', strip(e))">
  28. </section>
  29. <section class="radios form">
  30. <h3>Transaction Type</h3>
  31. <input selected type="radio" name="transaction_type" value="0"
  32. :value="estimate.transaction"
  33. @input="() => $emit('update:transaction', 0)"
  34. >
  35. <label>Purchase</label>
  36. <input type="radio" name="transaction_type" value="1"
  37. :value="estimate.transaction"
  38. @input="() => $emit('update:transaction', 1)"
  39. >
  40. <label>Refinance</label>
  41. </section>
  42. <section class="form inputs">
  43. <h3>Property Details</h3>
  44. <label>Price ($)</label>
  45. <input :value="estimate.price/100" @input="(e) => $emit('update:price', strip(e))">
  46. <label>Type</label>
  47. <select id="" name=""
  48. :value="estimate.property"
  49. @change="(e) => $emit('update:property', e.target.value)">
  50. <option value="attched">Single Family Attached</option>
  51. <option value="detached">Single Family Detached</option>
  52. <option value="lorise">Lo-rise (4 stories or less)</option>
  53. <option value="hirise">Hi-rise (over 4 stories)</option>
  54. </select>
  55. </section>
  56. <section class="radios form">
  57. <h3>Loan Type</h3>
  58. <input type="radio"
  59. name="loan_type"
  60. :checked="loan.type.id == 1"
  61. value="1"
  62. @change="(e) => $emit('update:loanType', 1)"
  63. >
  64. <label>Conventional</label>
  65. <input type="radio"
  66. name="loan_type"
  67. value="2"
  68. :checked="loan.type.id == 2"
  69. @change="(e) => $emit('update:loanType', 2)">
  70. <label>FHA</label>
  71. <input type="radio"
  72. name="loan_type"
  73. value="3"
  74. :checked="loan.type.id == 3"
  75. @change="(e) => $emit('update:loanType', 3)">
  76. <label>VA</label>
  77. <input type="radio"
  78. name="loan_type"
  79. value="4"
  80. :checked="loan.type.id == 4"
  81. @change="(e) => $emit('update:loanType', 4)">
  82. <label>USDA</label>
  83. </section>
  84. <section class="form inputs">
  85. <h3>Loan Details</h3>
  86. <label>Loan Term (years)</label>
  87. <input :value="loan.term"
  88. @input="(e) => $emit('update:term', stripInt(e))">
  89. <label>Loan Program</label>
  90. <select id="" name=""
  91. :value="loan.program"
  92. @change="(e) => $emit('update:program', e.target.value)">
  93. <option value="none">None</option>
  94. </select>
  95. <label>Loan to Value (%)</label>
  96. <input :value="loan.ltv" @input="(e) => $emit('update:ltv', e)">
  97. <label>Loan Amount ($)</label>
  98. <input :value="loan.amount / 100"
  99. @input="(e) => $emit('update:amount', e)">
  100. <label>Housing Expense DTI (%) - Optional</label>
  101. <input :value="loan.housingDti"
  102. @input="(e) => $emit('update:housingDti', e)">
  103. <label>Total DTI (%) - Optional</label>
  104. <input :value="loan.dti" @input="(e) => $emit('update:dti', e)">
  105. <label>Home Owner's Association ($/month)</label>
  106. <input :value="loan.hoa / 100"
  107. @input="(e) => { $emit('update:hoa', strip(e)) }">
  108. <label>Interest Rate (%)</label>
  109. <input :value="loan.interest"
  110. @input="(e) => { $emit('update:interest', stripPerc(e)) }">
  111. <label>Days of Interest</label>
  112. <input :value="loan.interestDays"
  113. @input="(e) => $emit('update:interestDays', stripInt(e))">
  114. <label>Hazard Insurance Escrow (months)</label>
  115. <input :value="loan.hazardEscrow"
  116. @input="(e) => { $emit('update:hazardEscrow', stripInt(e)) }">
  117. <label>Hazard Insurance ($/month)</label>
  118. <input :value="loan.hazard / 100"
  119. @input="(e) => $emit('update:hazard', strip(e))">
  120. <label>Real Estate Tax Escrow (months)</label>
  121. <input :value="loan.taxEscrow"
  122. @input="e => $emit('update:taxEscrow', stripInt(e))">
  123. <label>Real Estate Tax ($/month)</label>
  124. <input :value="loan.tax/100"
  125. @input="(e) => $emit('update:tax', strip(e))">
  126. </section>
  127. <section class="form inputs">
  128. <h3>Fees</h3>
  129. <div v-for="(fee, indx) in loan.fees"
  130. :key="fee.name + indx" class="fee"
  131. >
  132. <label>
  133. ${{(fee.amount / 100).toFixed(2)}}
  134. {{ fee.perc ? ` ${fee.perc}%` : ''}} - {{fee.name}}<br>
  135. {{fee.type}}
  136. </label>
  137. <img width="21" height="21" src="/assets/image/icon/x-red.svg"
  138. @click="() => loan.fees.splice(indx, 1)">
  139. </div>
  140. <button @click="resetFees">Reset</button>
  141. <button @click="createFee">New</button>
  142. </section>
  143. <fee-dialog v-if="newFee"
  144. :heading="'New Fee'"
  145. :initial="{}"
  146. :price="estimate.price"
  147. @close="() => newFee = null"
  148. @save="addFee"
  149. />
  150. <section class="form inputs mi">
  151. <h3>Mortgage Insurance</h3>
  152. <div class="row">
  153. <input checked type="radio" name="mi"/><label>Manual %</label>
  154. <input type="checkbox" :checked="loan.mi.monthly"
  155. @change="e => $emit('toggle:manualMIMonthly')" />
  156. <label>monthly</label>
  157. </div>
  158. <div class="row">
  159. <input :value="loan.mi.rate" @input="e => $emit('update:manualMI', stripPerc(e))" />
  160. </div>
  161. </section>
  162. <section class="form inputs">
  163. <button @click="validate">Continue</button>
  164. <ul class="errors">
  165. <li v-for="e in errors">{{e}}</li>
  166. </ul>
  167. </section>
  168. </div>
  169. </template>
  170. <script>
  171. import FeeDialog from "../fee-dialog.vue"
  172. import { stripLetters, strip, stripInt, stripPerc } from "../../helpers.js"
  173. const newFee = {
  174. name: '', type: '', amount: 0, perc: 0
  175. }
  176. // Setup this.newFee for the creation dialog
  177. function createFee() {
  178. this.newFee = Object.assign({}, newFee)
  179. }
  180. // If valid, add the current this.newFee to the array of fees and reset
  181. // this.newFee to null
  182. function addFee(fee, isDebit) {
  183. if (!isDebit) fee.amount = fee.amount * -1
  184. this.loan.fees.push(fee)
  185. this.newFee = null
  186. }
  187. function validate() {
  188. fetch(`/api/estimate/validate`,
  189. {method: 'POST',
  190. body: JSON.stringify(this.estimate),
  191. headers: {
  192. "Accept": "application/json",
  193. "Authorization": `Bearer ${this.token}`,
  194. },
  195. }).then(resp => {
  196. if (resp.ok && resp.status == 200) {
  197. this.errors = []
  198. this.$emit('continue')
  199. return
  200. } else {
  201. resp.text().then(t => this.errors = [t])
  202. }
  203. })
  204. }
  205. function generate() {
  206. const errors = this.validate()
  207. if (errors.length) {
  208. this.errors = errors
  209. return
  210. }
  211. }
  212. export default {
  213. components: { FeeDialog },
  214. methods: {
  215. strip, stripInt, stripLetters, stripPerc, createFee, addFee, validate,
  216. generate
  217. },
  218. props: ['estimate', 'loan', 'token'],
  219. // Loan updates assume the currently selected loan is being modified, and
  220. // $emit has no need to clarify.
  221. emits: [
  222. 'del',
  223. 'update:name',
  224. 'update:borrowerNum',
  225. 'update:borrowerCredit',
  226. 'update:borrowerIncome',
  227. 'update:transaction',
  228. 'update:price',
  229. 'update:property',
  230. // Loan specific emits
  231. 'update:loanType',
  232. 'update:term',
  233. 'update:program',
  234. 'update:ltv',
  235. 'update:amount',
  236. 'update:housingDti',
  237. 'update:dti',
  238. 'update:hoa',
  239. 'update:interest',
  240. 'update:interestDays',
  241. 'update:hazardEscrow',
  242. 'update:hazard',
  243. 'update:taxEscrow',
  244. 'update:tax',
  245. 'update:manualMI',
  246. 'toggle:manualMIMonthly',
  247. 'continue'
  248. ],
  249. data() {
  250. return {
  251. newFee: null,
  252. errors: [],
  253. hash: window.location.hash
  254. }
  255. },
  256. created() {
  257. }
  258. }
  259. </script>