Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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