mirror of
https://github.com/LukasKanka/api_test.git
synced 2026-11-11 06:34:36 +01:00
refactor: restructure to monorepo with backends and apiTest
- Move web/backend to backends/potter-api - Move web/frontend to backends/potter-api/frontend - Add root package.json with npm workspaces - Update README.md with monorepo navigation - Update .github/workflows/api-tests.yml for dynamic test discovery - Update bruno_test_scenario.md paths - Fix .gitignore to allow JSON data files - Add three new AI tests in apiTest/PotterAPI/AI/ Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
2e185b47ef
commit
d0cdabbf11
57 files changed
+292
-35
No files matched your search
@@ -0,0 +1,21 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,12 @@
|
||||
#build
|
||||
FROM node:lts-alpine as build-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
#run
|
||||
FROM nginx:stable-alpine as production-stage
|
||||
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,24 @@
|
||||
# frontend
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
+19164
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"start": "npm run serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.27.0",
|
||||
"core-js": "^3.6.4",
|
||||
"url": "^0.11.0",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.5.4",
|
||||
"@vue/cli-plugin-babel": "^5.0.4",
|
||||
"@vue/cli-plugin-eslint": "~5.0.4",
|
||||
"@vue/cli-service": "~5.0.4",
|
||||
"eslint": "^8.14.0",
|
||||
"eslint-plugin-vue": "^8.7.1",
|
||||
"sass": "^1.50.1",
|
||||
"sass-loader": "^12.6.0",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^5.72.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 169 KiB |
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>Spelleology</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Kanit:100i" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Lora:700" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
|
||||
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Allura&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@400&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
<template>
|
||||
<div id="app">
|
||||
<navigation />
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Navigation from "./components/Navigation";
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Navigation
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
button.main {
|
||||
padding: 10px 40px;
|
||||
background-color: #822724;
|
||||
color: white;
|
||||
font-size: 1.2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
}
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
button.main:hover {
|
||||
background-color: #72221f;
|
||||
}
|
||||
div.main {
|
||||
padding-top: 50px;
|
||||
min-height: 1000px;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<img src="../assets/gringottsbanklogo.png" alt="" />
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<h1>Gringotts Bank</h1>
|
||||
<h2>Investments for witches and wizards</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-5">
|
||||
<div class="col mx-5">
|
||||
<GringottsCalculator @calculation-done="handleCalculationDone" />
|
||||
|
||||
<GringottsForm
|
||||
v-if="newInvestment"
|
||||
:newInvestment="newInvestment"
|
||||
@create-investment="handleCreateInvestment"
|
||||
@reject-offer="handleRejectOffer"
|
||||
/>
|
||||
</div>
|
||||
<div class="col mx-5">
|
||||
<GringottsInvestmentList
|
||||
:investments="investments"
|
||||
@delete-investment="handleDeleteInvestment"
|
||||
/>
|
||||
<div v-if="investments.length === 0" class="no-investments-message">
|
||||
<p>No investments yet. Make your first one!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GringottsCalculator from "../components/gringotts-bank/GringottsCalculator.vue";
|
||||
import GringottsForm from "../components/gringotts-bank/GringottsForm.vue";
|
||||
import GringottsInvestmentList from "../components/gringotts-bank/GringottsInvestmentList.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GringottsCalculator,
|
||||
GringottsForm,
|
||||
GringottsInvestmentList,
|
||||
},
|
||||
created: function () {
|
||||
if (localStorage.getItem("investments")) {
|
||||
try {
|
||||
this.investments = JSON.parse(localStorage.getItem("investments"));
|
||||
} catch (e) {
|
||||
localStorage.removeItem("investments");
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newInvestment: null,
|
||||
investments: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleCalculationDone(newInvestmentData) {
|
||||
this.newInvestment = newInvestmentData;
|
||||
},
|
||||
handleCreateInvestment(customerName) {
|
||||
if (this.newInvestment) {
|
||||
// Create a new investment object with customer name
|
||||
const investment = {
|
||||
...this.newInvestment,
|
||||
customerName,
|
||||
};
|
||||
// Log the new investment to the console
|
||||
console.log("New Investment:", investment);
|
||||
this.investments.push(investment);
|
||||
|
||||
this.saveAppliedSavings();
|
||||
// Optionally, reset the newInvestment data
|
||||
this.newInvestment = null;
|
||||
//this.$refs.calculator.resetInputFields();
|
||||
}
|
||||
},
|
||||
handleRejectOffer() {
|
||||
this.newInvestment = null;
|
||||
|
||||
// Handle the rejected offer, e.g., log a message or perform other actions
|
||||
console.log("Offer Rejected");
|
||||
},
|
||||
handleDeleteInvestment(index) {
|
||||
// Handle the deletion of the investment in the parent component
|
||||
this.investments.splice(index, 1);
|
||||
this.saveAppliedSavings();
|
||||
},
|
||||
saveAppliedSavings() {
|
||||
const parsed = JSON.stringify(this.investments);
|
||||
localStorage.setItem("investments", parsed);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
div.main {
|
||||
img {
|
||||
width: 70%;
|
||||
}
|
||||
background-color: #eeedeb;
|
||||
h1,
|
||||
h2 {
|
||||
color: #747264;
|
||||
text-align: left;
|
||||
}
|
||||
p {
|
||||
color: #868475;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div class="modal-mask">
|
||||
<div class="modal-wrapper">
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h2 name="header">{{ spell.spell }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<h3 name="body">{{ spell.effect }}</h3>
|
||||
<h4 class="body">{{ spell.type }}</h4>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<slot name="footer">
|
||||
<button class="modal-default-button btn-block btn" @click="close">
|
||||
Close
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ModalComponent",
|
||||
props: ["spell"],
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit("close");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped >
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
z-index: 9998;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: table;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
text-align: center;
|
||||
width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 30px;
|
||||
background-color: #822724;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
|
||||
transition: all 0.2s ease-in-out;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.modal-header h2 {
|
||||
margin: 0 auto;
|
||||
color: white;
|
||||
font-family: "Harry", serif;
|
||||
font-size: 6em;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
margin: 20px 0;
|
||||
color: white;
|
||||
font-family: "Lora", serif;
|
||||
}
|
||||
.modal-body h3 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.modal-body h4 {
|
||||
font-family: "Kanit", serif;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.modal-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-enter .modal-container,
|
||||
.modal-leave-active .modal-container {
|
||||
-webkit-transform: scale(1.5);
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
.modal-default-button {
|
||||
font-family: "Harry", serif;
|
||||
font-size: 2em;
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
}
|
||||
.modal-default-button:active,
|
||||
.modal-default-button:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<ul class="navbar-nav mx-auto">
|
||||
<li class="nav-item">
|
||||
<router-link class="nav-link" to="/">Welcome</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link class="nav-link" to="/spelleology"
|
||||
>Spelleology</router-link
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link class="nav-link" to="/sortingHat">Sorting Hat</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link class="nav-link" to="/quotes">Quote Generator</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link class="nav-link" to="/gringottsBank"
|
||||
>Gringotts Bank</router-link
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "NavigationComponent",
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
a {
|
||||
color: #212529;
|
||||
&:hover {
|
||||
color: #822724;
|
||||
}
|
||||
&.router-link-exact-active {
|
||||
border-bottom: 1px solid #822724;
|
||||
color: #822724;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<h1 class="title">Potter Quotes</h1>
|
||||
<h2 class="subtitle">Level up by getting some wisdom</h2>
|
||||
|
||||
<div class="quote-controls">
|
||||
<button
|
||||
v-on:click="getQuote"
|
||||
class="main main mx-1"
|
||||
data-test="get-quote"
|
||||
>
|
||||
Get Quote
|
||||
</button>
|
||||
<button
|
||||
class="main mx-1"
|
||||
v-on:click="removeQuote"
|
||||
:disabled="quoteList.length === 0"
|
||||
id="remove-quote"
|
||||
>
|
||||
Remove Quote
|
||||
</button>
|
||||
|
||||
<div class="row wisdom-level col-md-10 mx-auto">
|
||||
<div class="col">
|
||||
<p data-test="wisdom-points">
|
||||
wisdom points +{{ this.quoteList.length }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p data-test="wisdom-level">current level {{ wizardLevel }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mx-auto">
|
||||
<div class="quote-container">
|
||||
<div v-if="this.qouteCounter == 0" class="empty-list-message">
|
||||
Click the button to get some wisdom
|
||||
</div>
|
||||
<transition-group v-else name="fade" tag="ul" class="quote-list">
|
||||
<li v-for="quoteitem in quoteList" :key="quoteitem.quote">
|
||||
<p>{{ quoteitem.quote }}</p>
|
||||
<p class="author">{{ quoteitem.author }}</p>
|
||||
</li>
|
||||
</transition-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "QuoteGenerator",
|
||||
data: function () {
|
||||
return {
|
||||
quote: "",
|
||||
quoteList: [],
|
||||
qouteCounter: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getQuote: function () {
|
||||
this.quote = "";
|
||||
this.$http.get("/quote").then((response) => {
|
||||
this.quote = response.data;
|
||||
this.quoteList.unshift(this.quote);
|
||||
this.qouteCounter++;
|
||||
});
|
||||
},
|
||||
removeQuote: function () {
|
||||
if (this.quoteList.length > 0) {
|
||||
this.quoteList.shift(); // Remove the newest quote
|
||||
this.qouteCounter--;
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
wizardLevel: function () {
|
||||
let level = "";
|
||||
if (this.qouteCounter <= 5) {
|
||||
level = "NOOB";
|
||||
}
|
||||
if (this.qouteCounter > 5) {
|
||||
level = "ROOKIE wand polisher";
|
||||
}
|
||||
if (this.qouteCounter > 10) {
|
||||
level = "Experienced broom SUMMONER";
|
||||
}
|
||||
if (this.qouteCounter > 15) {
|
||||
level = "NIGHTMARE Alchemist";
|
||||
}
|
||||
if (this.qouteCounter > 20) {
|
||||
level = "Spell OVERLORD";
|
||||
}
|
||||
if (this.qouteCounter > 25) {
|
||||
level = "Death potion EVANGELIST";
|
||||
}
|
||||
if (this.qouteCounter > 30) {
|
||||
level = "Dementor the HAPPINESS SPECIALIST";
|
||||
}
|
||||
if (this.qouteCounter > 35) {
|
||||
level = "Part-Time DEATHEATER";
|
||||
}
|
||||
if (this.qouteCounter > 40) {
|
||||
level = "First Impression SPOILER";
|
||||
}
|
||||
if (this.qouteCounter > 45) {
|
||||
level = "Azcaban INFLUENCER";
|
||||
}
|
||||
return level;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
$gray-color: #9b9a9a;
|
||||
$red-color: #822724;
|
||||
h1.title {
|
||||
font-family: "Roboto Slab", serif;
|
||||
}
|
||||
h2.subtitle {
|
||||
font-family: "Roboto Slab", serif;
|
||||
color: $gray-color;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 10px 30px;
|
||||
margin-top: 20px;
|
||||
background-color: $red-color;
|
||||
}
|
||||
|
||||
button#remove-quote {
|
||||
background-color: black;
|
||||
}
|
||||
button#remove-quote:disabled {
|
||||
background-color: #9b9a9a;
|
||||
|
||||
cursor: not-allowed;
|
||||
&:active {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
.quote-container {
|
||||
margin-top: 50px;
|
||||
ul {
|
||||
list-style: none;
|
||||
li {
|
||||
font-family: "Roboto Slab", serif;
|
||||
text-align: left;
|
||||
padding: 20px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0px 0px 7px 1px rgba(230, 230, 230, 1);
|
||||
&:nth-child(1) {
|
||||
transform: scale(1.05);
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0px 0px 15px 3px #c17f7d;
|
||||
}
|
||||
.author {
|
||||
color: $gray-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.fade-enter-to,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.quote-controls {
|
||||
p {
|
||||
display: inline-block;
|
||||
color: $red-color;
|
||||
font-weight: 700;
|
||||
font-size: 1.5em;
|
||||
text-align: justify;
|
||||
}
|
||||
button {
|
||||
transition: all 200ms ease-in;
|
||||
&:active {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.wisdom-level {
|
||||
margin-top: 20px;
|
||||
p {
|
||||
color: $gray-color;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
<template>
|
||||
<input
|
||||
type="text"
|
||||
class="search"
|
||||
placeholder="search for spell effect"
|
||||
id="search"
|
||||
data-test="search-input"
|
||||
v-bind="$attrs"
|
||||
v-bind:value="value"
|
||||
v-on="inputListeners"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SearchComponent",
|
||||
props: ["value"],
|
||||
computed: {
|
||||
inputListeners: function() {
|
||||
var vm = this;
|
||||
// `Object.assign` merges objects together to form a new object
|
||||
return Object.assign(
|
||||
{},
|
||||
// We add all the listeners from the parent
|
||||
this.$listeners,
|
||||
// Then we can add custom listeners or override the
|
||||
// behavior of some listeners.
|
||||
{
|
||||
// This ensures that the component works with v-model
|
||||
input: function(event) {
|
||||
vm.$emit("input", event.target.value);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
|
||||
<style lang="scss" scoped>
|
||||
input.search {
|
||||
outline: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-family: "Lora", serif;
|
||||
border-bottom: 2px solid #dca39e;
|
||||
background-color: #822724;
|
||||
border-radius: 0;
|
||||
font-size: 2em;
|
||||
margin-bottom: 50px;
|
||||
transition: all 200ms ease;
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
background-color: #822724;
|
||||
border-radius: 0;
|
||||
border-bottom: 2px solid #fff;
|
||||
color: white;
|
||||
}
|
||||
&::placeholder {
|
||||
color: #dca39e;
|
||||
font-family: "Kanit", serif;
|
||||
}
|
||||
&:focus::placeholder {
|
||||
color: #fff;
|
||||
font-family: "Kanit", serif;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<h1 class="title">Sorting Hat</h1>
|
||||
<h2 class="subtitle">Find yourself a new home in Hogwarts</h2>
|
||||
<button
|
||||
class="main"
|
||||
data-test="sort-button"
|
||||
:disabled="disabled"
|
||||
v-on:click="getQuote"
|
||||
>{{sortButtonLabel}}</button>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<img class="hat" src="../assets/sortinghat.jpg" alt />
|
||||
</div>
|
||||
<div class="col-md-4 my-auto message">
|
||||
<h2 v-if="quote" data-test="result-message">{{quote.sortingHatSays}}</h2>
|
||||
<h1 v-if="quote" data-test="house-result">{{quote.house}}</h1>
|
||||
<h3 class="message" v-else>{{loadingMessage}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SortingHat",
|
||||
data: function() {
|
||||
return {
|
||||
disabled: false,
|
||||
quote: "",
|
||||
isSorted: false,
|
||||
loadingMessage: "Click on sort to get yourself a new home"
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
sortButtonLabel: function() {
|
||||
if (this.isSorted) {
|
||||
return "Sort me again!";
|
||||
}
|
||||
return "Sort me";
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getQuote: function() {
|
||||
this.disabled = true;
|
||||
this.loadingMessage = "Let me think...";
|
||||
this.quote = "";
|
||||
this.$http
|
||||
.get("/sortingHat")
|
||||
.then(response => (this.quote = response.data))
|
||||
.then(() => {
|
||||
(this.isSorted = true), (this.disabled = false);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped >
|
||||
$gray-color: rgb(155, 154, 154);
|
||||
div.main {
|
||||
font-family: "Roboto Slab", serif;
|
||||
h2.subtitle {
|
||||
color: $gray-color;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
div.message {
|
||||
h1 {
|
||||
margin: 20px 0;
|
||||
text-align: left;
|
||||
color: #3f3f8c;
|
||||
font-size: 3em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.9em;
|
||||
text-align: left;
|
||||
color: $gray-color;
|
||||
}
|
||||
.message {
|
||||
text-align: center;
|
||||
color: $gray-color;
|
||||
}
|
||||
}
|
||||
.hat {
|
||||
width: 60%;
|
||||
}
|
||||
button {
|
||||
margin-top: 20px;
|
||||
background-color: #3f3f8c;
|
||||
&:hover {
|
||||
background-color: #333373;
|
||||
}
|
||||
&:disabled {
|
||||
background-color: $gray-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ul class="spells row text-center">
|
||||
<li
|
||||
v-bind:key="spell.id"
|
||||
v-for="spell in spells"
|
||||
v-on:click="$emit('clickOnspell', spell)"
|
||||
>
|
||||
{{ spell.effect }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SpellList",
|
||||
props: ["spells"],
|
||||
methods: {
|
||||
clickOnspell: function () {
|
||||
this.$emit("selectSpell");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss" scoped>
|
||||
.spells {
|
||||
list-style: none;
|
||||
padding: 20px;
|
||||
li {
|
||||
padding: 10px;
|
||||
color: #dca39e;
|
||||
font-family: "Lora", serif;
|
||||
vertical-align: middle;
|
||||
font-size: 2em;
|
||||
cursor: pointer;
|
||||
transition: all 200ms ease-in-out;
|
||||
&:nth-child(1n + 2) {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
&:nth-child(2n + 3) {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
&:nth-child(3n) {
|
||||
font-size: 2.2em;
|
||||
}
|
||||
&:hover {
|
||||
color: #fffcf9;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div class="main spells-main">
|
||||
<h1 class="text-center title">Spelleology</h1>
|
||||
<div class="col-md-12 mx-auto">
|
||||
<div class="col-md-6 row mx-auto">
|
||||
<search-component class="col" v-model="search" />
|
||||
<div class="col-md-4 unforgivable-checkbox">
|
||||
<input type="checkbox" id="unforgivableOnly" v-model="unforgivableOnly" />
|
||||
<label for="unforgivableOnly">Unforgivable only</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="type-filter">
|
||||
<input type="checkbox" id="spell" value="Spell" v-model="checkedSpells" />
|
||||
<label for="spell">Spell</label>
|
||||
<input type="checkbox" id="charm" value="Charm" v-model="checkedSpells" />
|
||||
<label for="charm">Charm</label>
|
||||
<input type="checkbox" id="curse" value="Curse" v-model="checkedSpells" />
|
||||
<label for="curse">Curse</label>
|
||||
<input type="checkbox" id="enchantment" value="Enchantment" v-model="checkedSpells" />
|
||||
<label for="enchantment">Enchantment</label>
|
||||
<input type="checkbox" id="hex" value="Hex" v-model="checkedSpells" />
|
||||
<label for="hex">Hex</label>
|
||||
<input type="checkbox" id="jinx" value="Jinx" v-model="checkedSpells" />
|
||||
<label for="jinx">Jinx</label>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
v-on:click="clearAll"
|
||||
id="deleteAll"
|
||||
data-test="delete-all"
|
||||
class="spell-button"
|
||||
>Delete all</button>
|
||||
<button v-on:click="reset" id="resetSpells" data-test="reset" class="spell-button">Reset</button>
|
||||
</div>
|
||||
<div class="row col-md-12 mx-auto">
|
||||
<spell-list
|
||||
v-if="spells.length >=1"
|
||||
:spells="filteredList"
|
||||
v-on:clickOnspell="selectSpell"
|
||||
/>
|
||||
</div>
|
||||
<h1
|
||||
class="subtitle text-center"
|
||||
v-if="spells.length ===0 || filteredList.length ===0"
|
||||
>Mischief managed</h1>
|
||||
<modal :spell="selectedSpell" v-show="showModal" @close="closeModal" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SpellList from "./SpellList.vue";
|
||||
import SearchComponent from "./SearchComponent.vue";
|
||||
import Modal from "./Modal.vue";
|
||||
|
||||
export default {
|
||||
name: "SpelleologyComponent",
|
||||
components: {
|
||||
SpellList,
|
||||
SearchComponent,
|
||||
Modal,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
spells: [],
|
||||
selectedSpell: "",
|
||||
showModal: false,
|
||||
search: "",
|
||||
unforgivableOnly: false,
|
||||
checkedSpells: ["Charm", "Enchantment", "Curse", "Spell", "Hex", "Jinx"],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredList: function () {
|
||||
let spells = this.spells;
|
||||
spells = spells.filter((spell) =>
|
||||
spell.effect.toLowerCase().includes(this.search.toLowerCase())
|
||||
);
|
||||
spells = spells.filter((spell) =>
|
||||
this.checkedSpells.includes(spell.type)
|
||||
);
|
||||
if (this.unforgivableOnly) {
|
||||
spells = spells.filter((spell) => spell.isUnforgivable === true);
|
||||
}
|
||||
return spells;
|
||||
},
|
||||
},
|
||||
|
||||
created: function () {
|
||||
this.$http.get("/spells").then((response) => (this.spells = response.data));
|
||||
},
|
||||
methods: {
|
||||
selectSpell: function (spell) {
|
||||
this.selectedSpell = spell;
|
||||
this.showModal = true;
|
||||
},
|
||||
closeModal() {
|
||||
this.showModal = false;
|
||||
},
|
||||
clearAll: function () {
|
||||
this.$http
|
||||
.delete("spells/actions/deleteAll")
|
||||
.then(() => (this.spells = []));
|
||||
},
|
||||
reset: function () {
|
||||
this.$http.get("spells/actions/reset").then((response) => {
|
||||
this.spells = response.data.spells;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
div.spells-main {
|
||||
background-color: #822724;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: "Harry", serif;
|
||||
|
||||
&.title {
|
||||
color: white;
|
||||
font-size: 8em;
|
||||
text-shadow: 7px 7px 2px #671e1c;
|
||||
}
|
||||
|
||||
&.subtitle {
|
||||
margin: 100px;
|
||||
font-size: 6em;
|
||||
color: #dca39e;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
color: white;
|
||||
padding: 0 10px;
|
||||
font-family: "Kanit", serif;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
div.unforgivable-checkbox {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.spell-button {
|
||||
padding: 1px 20px;
|
||||
background-color: #822724;
|
||||
font-family: "Kanit", serif;
|
||||
color: #fffcf9;
|
||||
font-size: 2em;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid #dca39e;
|
||||
margin: 0 20px;
|
||||
|
||||
&:hover {
|
||||
border-bottom: 2px solid white;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Harry";
|
||||
src: url("../assets/HARRYP.TTF");
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="welcome">
|
||||
<h1>Welcome Wizard</h1><img src=../assets/hogwartslogo.png alt="">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "WelcomeComponent"
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
div.welcome {
|
||||
margin-top: 50px;
|
||||
h1 {
|
||||
font-size: 8em;
|
||||
color: black;
|
||||
font-family: "Roboto Slab", serif;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Enter your data muggle!</h2>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<select class="custom-select" id="selectedFund" v-model="selectedFundId">
|
||||
<option disabled value="">Please select fund</option>
|
||||
<option
|
||||
v-for="(product, index) in products"
|
||||
:value="product.id"
|
||||
:key="index"
|
||||
>
|
||||
{{ product.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
class="form-control"
|
||||
aria-describedby="inputGroup-sizing-default"
|
||||
type="number"
|
||||
id="oneTimeInvestment"
|
||||
v-model="oneTimeInvestment"
|
||||
step="1000"
|
||||
placeholder="one time investment"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
class="form-control"
|
||||
aria-describedby="inputGroup-sizing-default"
|
||||
type="number"
|
||||
id="years"
|
||||
v-model="years"
|
||||
step="1"
|
||||
placeholder="years"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-block" @click="calculate" data-test="create-offer">
|
||||
Make me an offer
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectedFundId: "",
|
||||
oneTimeInvestment: null,
|
||||
years: null,
|
||||
products: [
|
||||
{ id: 1, name: "Galleon Guardian Fund", risk: "LOW", interestRate: 4 },
|
||||
{
|
||||
id: 2,
|
||||
name: "Phoenix Feather Portfolio",
|
||||
risk: "MEDIUM",
|
||||
interestRate: 6,
|
||||
},
|
||||
{ id: 3, name: "Basilisk Bonds", risk: "HIGH", interestRate: 9 },
|
||||
{
|
||||
id: 4,
|
||||
name: "Polyjuice Potion Fund",
|
||||
risk: "MEDIUM",
|
||||
interestRate: 5,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Marauder's Map Mutuals",
|
||||
risk: "HIGH",
|
||||
interestRate: 8,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Dementor Defense Dividends",
|
||||
risk: "INSANE",
|
||||
interestRate: 12,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "Quidditch Quest Equities",
|
||||
risk: "MEDIUM",
|
||||
interestRate: 6,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "Hogwarts Heritage Holdings",
|
||||
risk: "HIGH",
|
||||
interestRate: 9,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "Pensieve Prosperity Partners",
|
||||
risk: "LOW",
|
||||
interestRate: 3,
|
||||
},
|
||||
{ id: 10, name: "Owl Post Options", risk: "MEDIUM", interestRate: 5 },
|
||||
{
|
||||
id: 11,
|
||||
name: "Death Eater Dominance Fund",
|
||||
risk: "INSANE",
|
||||
interestRate: 15,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedFund() {
|
||||
return this.products.find((fund) => fund.id == this.selectedFundId);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
calculate() {
|
||||
if (this.selectedFund && this.oneTimeInvestment && this.years) {
|
||||
const oneTimeInvestment = parseFloat(this.oneTimeInvestment);
|
||||
const interestRate = this.selectedFund.interestRate / 100;
|
||||
const years = parseInt(this.years);
|
||||
|
||||
// Simple compound interest calculation
|
||||
const totalInvestment =
|
||||
oneTimeInvestment * Math.pow(1 + interestRate, years);
|
||||
const interestIncome = totalInvestment - oneTimeInvestment;
|
||||
const taxes = interestIncome * 0.19;
|
||||
const netIncome = totalInvestment - taxes;
|
||||
|
||||
const calculatedData = {
|
||||
oneTimeInvestment,
|
||||
fund: this.selectedFund,
|
||||
years,
|
||||
totalInvestment,
|
||||
interestIncome,
|
||||
taxes,
|
||||
netIncome,
|
||||
};
|
||||
|
||||
this.$emit("calculation-done", calculatedData);
|
||||
this.oneTimeInvestment = null;
|
||||
this.selectedFundId = "";
|
||||
this.years = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Add your component-specific styles here */
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
input,
|
||||
select,
|
||||
button {
|
||||
margin-top: 0px;
|
||||
border-radius: 0;
|
||||
}
|
||||
button {
|
||||
background-color: #747264;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
padding: 10px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
h2 {
|
||||
color: #747264;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="mt-5 offer-detail">
|
||||
<h2>The offer</h2>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col your-data">
|
||||
<h4>Your data</h4>
|
||||
<p>
|
||||
Investment: <span>{{ formatAsMoneyGBP(newInvestment.oneTimeInvestment) }}</span>
|
||||
</p>
|
||||
<p class="period">Period: <span>{{ newInvestment.years }} years</span></p>
|
||||
<p class="fund">Fund: <span>{{ newInvestment.fund.name }}</span></p>
|
||||
<p class="risk">Risk: <span>{{ newInvestment.fund.risk }}</span></p>
|
||||
</div>
|
||||
|
||||
<div class="col our-calculations">
|
||||
<h4>Our calculations</h4>
|
||||
<p>Net income: <span> {{ formatAsMoneyGBP(newInvestment.netIncome) }}</span></p>
|
||||
<p>
|
||||
Interest income: <span>{{ formatAsMoneyGBP(newInvestment.interestIncome) }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
Total income:
|
||||
{{
|
||||
formatAsMoneyGBP(
|
||||
newInvestment.netIncome + newInvestment.interestIncome
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<input class="form-control" aria-describedby="inputGroup-sizing-default" type="text" id="customerName"
|
||||
data-test="customer-name" v-model="customerName" placeholder="enter your name" />
|
||||
</div>
|
||||
|
||||
<button class="btn btn-success mx-3" @click="createInvestment" data-test="create-investment">
|
||||
Create Investment
|
||||
</button>
|
||||
<button class="btn btn-danger mx-3" @click="rejectOffer" data-test="decline-investment">
|
||||
Reject Offer
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
newInvestment: Object, // Pass the calculatedData as a prop
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
customerName: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
formatAsMoneyGBP(number) {
|
||||
// Use Intl.NumberFormat to format the number as GBP
|
||||
const formatter = new Intl.NumberFormat("en-GB", {
|
||||
style: "currency",
|
||||
currency: "GBP",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
// Return the formatted string
|
||||
return formatter.format(number);
|
||||
},
|
||||
createInvestment() {
|
||||
if (this.customerName) {
|
||||
console.log("emitting");
|
||||
this.$emit("create-investment", this.customerName);
|
||||
// Optionally, clear the input field after creating the investment
|
||||
this.customerName = "";
|
||||
}
|
||||
},
|
||||
rejectOffer() {
|
||||
// Handle the rejection, e.g., emit an event or perform other actions
|
||||
this.$emit("reject-offer");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
div.offer-detail {
|
||||
button {
|
||||
font-weight: 700;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
color: #fff;
|
||||
background-color: #747264;
|
||||
padding: 20px;
|
||||
|
||||
h4 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
|
||||
span {
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: left;
|
||||
font-weight: 900;
|
||||
}
|
||||
</style>
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Gringotts Investment List</h2>
|
||||
|
||||
<ul class="investment-list mt-3">
|
||||
<li class="my-2" v-for="(investment, index) in investments" :key="index">
|
||||
<h4>
|
||||
Investment issued for <span>{{ investment.customerName }}</span>
|
||||
<hr />
|
||||
</h4>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p>
|
||||
Investment:
|
||||
{{ formatAsMoneyGBP(investment.oneTimeInvestment) }}
|
||||
</p>
|
||||
<p>Period: {{ investment.years }} years</p>
|
||||
<p>Fund: {{ investment.fund.name }}</p>
|
||||
<p>Risk: {{ investment.fund.risk }}</p>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<p>Net income: {{ formatAsMoneyGBP(investment.netIncome) }}</p>
|
||||
<p>
|
||||
Interest income:
|
||||
{{ formatAsMoneyGBP(investment.interestIncome) }}
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
Total income:
|
||||
{{
|
||||
formatAsMoneyGBP(
|
||||
investment.netIncome + investment.interestIncome
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button
|
||||
class="btn btn-primary mr-2 float-left"
|
||||
id="showDetail"
|
||||
@click="openInvestmentModal(index)"
|
||||
>
|
||||
View Details
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger float-left"
|
||||
id="delete"
|
||||
@click="deleteInvestment(index)"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<GringottsInvestmentModal
|
||||
v-if="selectedInvestment !== null"
|
||||
:investment="selectedInvestment"
|
||||
:is-open="isModalOpen"
|
||||
@close-investment-modal="closeInvestmentModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GringottsInvestmentModal from "../gringotts-bank/GringottsInvestmentModal.vue";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
investments: Array, // Array of investments passed as a prop
|
||||
},
|
||||
components: {
|
||||
GringottsInvestmentModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedInvestment: null,
|
||||
isModalOpen: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
formatAsMoneyGBP(number) {
|
||||
// Use Intl.NumberFormat to format the number as GBP
|
||||
const formatter = new Intl.NumberFormat("en-GB", {
|
||||
style: "currency",
|
||||
currency: "GBP",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
// Return the formatted string
|
||||
return formatter.format(number);
|
||||
},
|
||||
deleteInvestment(index) {
|
||||
// Trigger the deletion of the investment at the specified index
|
||||
this.$emit("delete-investment", index);
|
||||
},
|
||||
openInvestmentModal(index) {
|
||||
this.selectedInvestment = this.investments[index];
|
||||
this.isModalOpen = true;
|
||||
},
|
||||
closeInvestmentModal() {
|
||||
this.selectedInvestment = null;
|
||||
this.isModalOpen = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
h2 {
|
||||
color: #747264;
|
||||
text-align: left;
|
||||
}
|
||||
ul.investment-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
li {
|
||||
hr {
|
||||
background-color: #fff;
|
||||
}
|
||||
h4 {
|
||||
text-align: left;
|
||||
span {
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
color: #fff;
|
||||
background-color: #747264;
|
||||
padding: 20px;
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
button#showDetail {
|
||||
background-color: #504e44;
|
||||
border: none;
|
||||
&:hover {
|
||||
background-color: #676458;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
p {
|
||||
text-align: left;
|
||||
span {
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
h5 {
|
||||
text-align: left;
|
||||
}
|
||||
transition: all 200ms ease-in;
|
||||
|
||||
&:hover {
|
||||
background-color: #817f71;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<!-- GringottsInvestmentModal.vue -->
|
||||
<template>
|
||||
<div v-if="isOpen" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Investment Details</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p>Fund: {{ investment.fund.name }}</p>
|
||||
<p>Risk: {{ investment.fund.risk }}</p>
|
||||
<p>Name: {{ investment.customerName }}</p>
|
||||
<p>
|
||||
Investment: {{ formatAsMoneyGBP(investment.oneTimeInvestment) }}
|
||||
</p>
|
||||
<p>Years: {{ investment.years }}</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p>Net income: {{ formatAsMoneyGBP(investment.netIncome) }}</p>
|
||||
<p>
|
||||
Interest income:
|
||||
{{ formatAsMoneyGBP(investment.interestIncome) }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Total income:
|
||||
{{
|
||||
formatAsMoneyGBP(
|
||||
investment.netIncome + investment.interestIncome
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-dismiss="modal"
|
||||
@click="closeInvestmentModal"
|
||||
id="close"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
investment: Object, // The specific investment object to display details
|
||||
isOpen: Boolean, // Whether the modal is open or not
|
||||
},
|
||||
methods: {
|
||||
closeInvestmentModal() {
|
||||
this.$emit("close-investment-modal"); // Emit an event to close the modal
|
||||
},
|
||||
formatAsMoneyGBP(number) {
|
||||
// Use Intl.NumberFormat to format the number as GBP
|
||||
const formatter = new Intl.NumberFormat("en-GB", {
|
||||
style: "currency",
|
||||
currency: "GBP",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
// Return the formatted string
|
||||
return formatter.format(number);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* Add your modal styles here */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.modal-dialog {
|
||||
margin: 0 auto;
|
||||
width: 1200px;
|
||||
.modal-content {
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 0;
|
||||
background-color: #747264;
|
||||
border: none;
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button#close {
|
||||
background-color: #504e44;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
&:hover {
|
||||
background-color: #676458;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
export default {
|
||||
name: "Modal",
|
||||
props: ["spell"],
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit("close");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
import axios from "axios";
|
||||
import VueRouter from "vue-router";
|
||||
import Spelleology from "../src/components/Spelleology";
|
||||
import SortingHat from "../src/components/SortingHat";
|
||||
import Welcome from "../src/components/Welcome";
|
||||
import QuoteGenerator from "../src/components/QuoteGenerator";
|
||||
import GringottsBank from "../src/components/GringottsBank";
|
||||
|
||||
const axiosconfig = {
|
||||
baseURL: process.env.VUE_APP_API_URL || "http://localhost:3000",
|
||||
timeout: 30000,
|
||||
};
|
||||
Vue.prototype.$http = axios.create(axiosconfig);
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const router = new VueRouter({
|
||||
routes: [
|
||||
{ path: "/spelleology", component: Spelleology },
|
||||
{ path: "/sortingHat", component: SortingHat },
|
||||
{ path: "/quotes", component: QuoteGenerator },
|
||||
{ path: "/gringottsBank", component: GringottsBank },
|
||||
{ path: "/", component: Welcome },
|
||||
],
|
||||
});
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
//...
|
||||
resolve: {
|
||||
fallback: {
|
||||
url: require.resolve("url"),
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user