Skip to content
Snippets Groups Projects
Commit c1d03cef authored by Viktor Gunnar Grevskott's avatar Viktor Gunnar Grevskott
Browse files

Merge branch 'feat/profile-and-settings-improvements' into 'main'

Feat/profile and settings improvements

See merge request !104
parents 4ce33258 043399a3
Branches
No related tags found
1 merge request!104Feat/profile and settings improvements
Pipeline #285347 passed with warnings
Showing
with 556 additions and 296 deletions
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m438-240 226-226-58-58-169 169-84-84-57 57 142 142ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z"/></svg>
\ No newline at end of file
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { type FeedbackResponseDTO, UserService } from '@/api'
import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'
const feedbacks = ref<FeedbackResponseDTO[]>([]);
onMounted(async () => {
try {
feedbacks.value = await UserService.getFeedback();
console.log(feedbacks.value)
} catch (error) {
handleUnknownError(error);
}
})
const formattedDate = (dateStr?: string): string => {
if (!dateStr) return '';
return new Date(dateStr).toLocaleString();
};
</script>
<template>
<div class="feedback-container">
<h1>Feedback List</h1>
<div class="feedback-list">
<!-- Loop through feedback items -->
<div class="feedback-item" v-for="feedback in feedbacks" :key="feedback.id">
<div class="email">{{ feedback.email }}</div>
<div class="message">{{ feedback.message }}</div>
<div class="created-at">{{ formattedDate(feedback.createdAt) }}</div>
</div>
</div>
</div>
</template>
<style scoped>
.feedback-container {
max-width: 800px;
margin: auto;
padding: 20px;
}
.feedback-list {
margin-top: 20px;
border-top: 1px solid #ccc;
}
.feedback-item {
padding: 10px;
border-bottom: 1px solid #ccc;
}
.email, .message, .created-at {
padding: 5px 0;
}
.email {
font-weight: bold;
color: #333;
}
.message {
margin: 5px 0;
line-height: 1.5;
color: #666;
}
.created-at {
font-size: 0.8rem;
color: #999;
}
</style>
\ No newline at end of file
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
<img src="@/assets/icons/storefront.svg">Butikk <img src="@/assets/icons/storefront.svg">Butikk
</router-link> </router-link>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown d-flex flex-column">
<a data-mdb-dropdown-init class=" nav-link dropdown-toggle hidden-arrow notification" href="#" id="navbarDropdownMenuLink" <a data-mdb-dropdown-init class=" nav-link dropdown-toggle hidden-arrow notification" href="#" id="navbarDropdownMenuLink"
role="button" data-bs-toggle="dropdown" aria-expanded="false"> role="button" data-bs-toggle="dropdown" aria-expanded="false">
<img src="/src/assets/icons/bell-white.svg"> <img src="/src/assets/icons/bell-white.svg">
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
<img :src="notificationImageMapper[String(item.notificationType)]" alt="Varslingsikon" class="notification-icon"> <img :src="notificationImageMapper[String(item.notificationType)]" alt="Varslingsikon" class="notification-icon">
</div> </div>
<div class="flex-grow-1 ms-3"> <div class="flex-grow-1 ms-3">
<div class="not-item dropdown-item">{{item.message}}</div> <div class="not-item dropdown-item" id="notificationText">{{item.message}}</div>
</div> </div>
</router-link> </router-link>
</li> </li>
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
<li>Ingen varslinger</li> <li>Ingen varslinger</li>
</ul> </ul>
</li> </li>
<li v-if="userStore.isLoggedIn" class="nav-item dropdown"> <li v-if="userStore.isLoggedIn" class="nav-item dropdown d-flex flex-column">
<a <a
data-cy="user" data-cy="user"
:class="['nav-link', 'dropdown-toggle', 'username-text', 'text-white', { 'underline-active': !isAnyActivePage() }]" :class="['nav-link', 'dropdown-toggle', 'username-text', 'text-white', { 'underline-active': !isAnyActivePage() }]"
...@@ -122,7 +122,7 @@ ...@@ -122,7 +122,7 @@
<li> <li>
<router-link data-cy="admin" <router-link data-cy="admin"
class="dropdown-item dropdown-username-link" class="dropdown-item dropdown-username-link"
:to="toSetting()" :to="toAdmin()"
exact-active-class="active-link" exact-active-class="active-link"
@click="toggleDropdown"> @click="toggleDropdown">
<img src="@/assets/icons/admin.svg">Admin <img src="@/assets/icons/admin.svg">Admin
...@@ -313,6 +313,15 @@ function toFeedback(): string { ...@@ -313,6 +313,15 @@ function toFeedback(): string {
return '/feedback'; return '/feedback';
} }
/**
* Redirects to the admin page.
*
* @returns {string} The URL for the admin page.
*/
function toAdmin(): string {
return '/admin';
}
/** /**
* Redirects to the friends page. * Redirects to the friends page.
* *
...@@ -463,6 +472,12 @@ onMounted(() => { ...@@ -463,6 +472,12 @@ onMounted(() => {
margin: 0 140px; margin: 0 140px;
} }
@media (max-width: 768px) {
.container-fluid {
margin: 0 20px;
}
}
#logo { #logo {
font-size: 2.5rem; font-size: 2.5rem;
height: 100%; height: 100%;
...@@ -485,5 +500,14 @@ onMounted(() => { ...@@ -485,5 +500,14 @@ onMounted(() => {
display: none; display: none;
} }
#notificationText {
text-wrap: nowrap;
}
@media (max-width: 768px) {
#notificationText {
text-wrap: wrap;
}
}
</style> </style>
\ No newline at end of file
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<div v-if="showFriends"> <div v-if="showFriends">
<div v-if="elementsInFriends"> <div v-if="elementsInFriends">
<div class="row"> <div class="row">
<div class="col-lg-3" v-for="friend in friends" :key="friend.id"> <div class="friendBox d-flex flex-wrap" v-for="friend in friends" :key="friend.id">
<div class="card card-one"> <div class="card card-one">
<div class="header"> <div class="header">
<div v-if="friend.profileImage" class="avatar"> <div v-if="friend.profileImage" class="avatar">
...@@ -71,7 +71,8 @@ ...@@ -71,7 +71,8 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Legg til venn</h5> <h5 class="modal-title">Legg til venn</h5>
<button type="button" class="close btn-close" @click="showAddFriend = false" aria-label="Close"></button> <button type="button" class="close btn-close" @click="showAddFriend = false"
aria-label="Close"></button>
</div> </div>
<div class="modal-body d-flex justify-content-center align-items-center flex-column"> <div class="modal-body d-flex justify-content-center align-items-center flex-column">
<form class="col-md-10 d-flex justify-content-center align-items-center flex-row my-4" <form class="col-md-10 d-flex justify-content-center align-items-center flex-row my-4"
...@@ -86,8 +87,8 @@ ...@@ -86,8 +87,8 @@
<div class="row d-flex align-items-center"> <div class="row d-flex align-items-center">
<div class="col-md-2 col-sm-2"> <div class="col-md-2 col-sm-2">
<div v-if="user.profileImage !== null"><img id="profilePicture" <div v-if="user.profileImage !== null"><img id="profilePicture"
:src="apiUrl + '/api/images/' + user.profileImage" :src="apiUrl + '/api/images/' + user.profileImage" alt="bruker"
alt="bruker" class="profile-photo-lg"></div> class="profile-photo-lg"></div>
<div v-else><img id="profilePicture" :src="'../src/assets/userprofile.png'" <div v-else><img id="profilePicture" :src="'../src/assets/userprofile.png'"
alt="bruker" class="profile-photo-lg"></div> alt="bruker" class="profile-photo-lg"></div>
</div> </div>
...@@ -319,7 +320,7 @@ body { ...@@ -319,7 +320,7 @@ body {
/*social */ /*social */
.card-one { .card-one {
position: relative; position: relative;
width: 300px; width: 200px;
background: #fff; background: #fff;
box-shadow: 0 10px 7px -5px rgba(0, 0, 0, 0.4); box-shadow: 0 10px 7px -5px rgba(0, 0, 0, 0.4);
} }
...@@ -660,4 +661,8 @@ ul.friend-list .right p { ...@@ -660,4 +661,8 @@ ul.friend-list .right p {
#addFriend:hover { #addFriend:hover {
background-color: #003b58f5; background-color: #003b58f5;
} }
.friendBox {
width: 250px;
}
</style> </style>
\ No newline at end of file
...@@ -31,6 +31,12 @@ import LoginForm from '@/components/Login/LoginForm.vue' ...@@ -31,6 +31,12 @@ import LoginForm from '@/components/Login/LoginForm.vue'
box-shadow: rgba(57, 57, 63, 0.5) 0px 1px 20px 0px; box-shadow: rgba(57, 57, 63, 0.5) 0px 1px 20px 0px;
} }
@media (max-width: 600px){
.box {
padding: 0;
}
}
.title { .title {
font-size: 60px; font-size: 60px;
color: white; color: white;
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue'; import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue';
import type { UserUpdateDTO } from '@/api' import { AccountControllerService, type BalanceDTO, BankProfileControllerService, type UserUpdateDTO } from '@/api'
import { UserService } from '@/api'; import { UserService } from '@/api';
import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'
...@@ -128,13 +128,16 @@ async function getAccountInfo() { ...@@ -128,13 +128,16 @@ async function getAccountInfo() {
try { try {
let response = await UserService.getUser(); let response = await UserService.getUser();
savingsAccount.value = response.savingsAccountBBAN; savingsAccount.value = response.savingsAccountBBAN;
/*if (response.savingsAccount?.balance) { let bban1: any = response.savingsAccountBBAN;
savingsAccountBalance.value = response.savingsAccount?.balance AccountControllerService.getAccountsByBban({bban: bban1}).then((balance: BalanceDTO) => {
}*/ savingsAccountBalance.value = balance.balance;
})
spendingAccount.value = response.checkingAccountBBAN; spendingAccount.value = response.checkingAccountBBAN;
/*if (response.checkingAccount?.balance) { let bban2: any = response.checkingAccountBBAN;
spendingAccountBalance.value = response.checkingAccountBBAN?.balance AccountControllerService.getAccountsByBban({bban: bban2}).then((balance: BalanceDTO) => {
}*/ spendingAccountBalance.value = balance.balance;
})
} catch (err) { } catch (err) {
handleUnknownError(err) handleUnknownError(err)
} }
......
...@@ -19,6 +19,8 @@ let banners = ref([] as any) ...@@ -19,6 +19,8 @@ let banners = ref([] as any)
let hasBanners = ref(false); let hasBanners = ref(false);
let selectedBannerId = ref(0); let selectedBannerId = ref(0);
const selectedBanner = ref() const selectedBanner = ref()
const errorMsg = ref('');
const successMsg = ref('');
const iconSrc = ref('../src/assets/userprofile.png'); const iconSrc = ref('../src/assets/userprofile.png');
const fileInputRef = ref(); const fileInputRef = ref();
...@@ -152,13 +154,16 @@ const handleSubmit = async () => { ...@@ -152,13 +154,16 @@ const handleSubmit = async () => {
lastName: surnameRef.value, lastName: surnameRef.value,
}; };
try { try {
UserService.update({ requestBody: updateUserPayload }) await UserService.update({ requestBody: updateUserPayload })
useUserInfoStore().setUserInfo({ useUserInfoStore().setUserInfo({
firstname: firstNameRef.value, firstname: firstNameRef.value,
lastname: surnameRef.value, lastname: surnameRef.value,
}) })
errorMsg.value = '';
successMsg.value = 'Profilen ble oppdatert!';
} catch (err) { } catch (err) {
handleUnknownError(err); errorMsg.value = handleUnknownError(err);
successMsg.value = '';
console.error(err) console.error(err)
} }
} }
...@@ -174,7 +179,8 @@ onMounted(() => { ...@@ -174,7 +179,8 @@ onMounted(() => {
<div class="tab-pane active" id="profile"> <div class="tab-pane active" id="profile">
<h6>DIN PROFILINFORMASJON</h6> <h6>DIN PROFILINFORMASJON</h6>
<hr> <hr>
<form @submit.prevent="handleSubmit" novalidate> <form @submit.prevent="handleSubmit" novalidate class="d-flex infoHolder">
<div>
<div class="user-avatar"> <div class="user-avatar">
<input type="file" ref="fileInputRef" @change="handleFileChange" accept=".jpg, .jpeg, .png" <input type="file" ref="fileInputRef" @change="handleFileChange" accept=".jpg, .jpeg, .png"
style="display: none" /> style="display: none" />
...@@ -184,30 +190,40 @@ onMounted(() => { ...@@ -184,30 +190,40 @@ onMounted(() => {
src="../../assets/icons/download.svg"> Last opp bilde</button> src="../../assets/icons/download.svg"> Last opp bilde</button>
</div> </div>
</div> </div>
</div>
<div class="mx-5">
<div class="form-group"> <div class="form-group">
<BaseInput data-cy="first-name" :model-value="firstNameRef" @input-change-event="handleFirstNameInputEvent" <BaseInput data-cy="first-name" :model-value="firstNameRef" @input-change-event="handleFirstNameInputEvent"
id="firstNameInputChange" input-id="first-name-new" type="text" label="Fornavn" id="firstNameInputChange" input-id="first-name-new" type="text" label="Fornavn"
placeholder="Skriv inn ditt fornavn" invalid-message="Vennligst skriv inn ditt fornavn" placeholder="Skriv inn ditt fornavn" invalid-message="Vennligst skriv inn ditt fornavn" class="inputDynamic" />
style="max-width: 300px" />
</div> </div>
<br> <br>
<div class="form-group"> <div class="form-group">
<BaseInput data-cy="last-name" :model-value="surnameRef" @input-change-event="handleSurnameInputEvent" <BaseInput data-cy="last-name" :model-value="surnameRef" @input-change-event="handleSurnameInputEvent"
id="surnameInput-change" input-id="surname-new" type="text" label="Etternavn" id="surnameInput-change" input-id="surname-new" type="text" label="Etternavn"
placeholder="Skriv inn ditt etternavn" invalid-message="Vennligst skriv inn ditt etternavn" placeholder="Skriv inn ditt etternavn" invalid-message="Vennligst skriv inn ditt etternavn"
style="max-width: 300px" /> class="inputDynamic"/>
</div> </div>
<br> <br>
<div class="d-flex">
<p class="text-danger"> {{ errorMsg }}</p>
<p class="text-success"> {{ successMsg }}</p>
</div>
<button data-cy="profile-submit-btn" type="submit" class="btn btn-primary classyButton">Oppdater profil</button> <button data-cy="profile-submit-btn" type="submit" class="btn btn-primary classyButton">Oppdater profil</button>
</div>
</form> </form>
<hr> <hr>
<div> <div>
<h6>Banners</h6> <h6>Banners</h6>
<div v-if="hasBanners" class="scrolling-wrapper-badges row flex-row flex-wrap mt-2 pb-2 pt-2"> <div v-if="hasBanners" class="scrolling-wrapper-badges row flex-row flex-wrap mt-2 pb-2 pt-2">
<div v-for="banner in banners" :key="banner.id" class="card text-center banner justify-content-center d-flex align-items-center" @click="selectItem(banner.id)" <div v-for="banner in banners" :key="banner.id"
:class="{ 'selected-banner': banner.id === selectedBannerId }" data-bs-toggle="tooltip" class="card text-center banner justify-content-center d-flex align-items-center"
data-bs-placement="top" data-bs-custom-class="custom-tooltip" :data-bs-title="banner.criteria"> @click="selectItem(banner.id)" :class="{ 'selected-banner': banner.id === selectedBannerId }"
<img :src="apiUrl + `/api/images/${banner.imageId}`" class="card-img-top" :class="{ 'selected-banner': banner.id === selectedBanner }" alt="Banner" style="width: 200px; height: 100px" @click="selectItem(banner.imageId)" /> data-bs-toggle="tooltip" data-bs-placement="top" data-bs-custom-class="custom-tooltip"
:data-bs-title="banner.criteria">
<img :src="apiUrl + `/api/images/${banner.imageId}`" class="card-img-top"
:class="{ 'selected-banner': banner.id === selectedBanner }" alt="Banner"
style="width: 200px; height: 100px" @click="selectItem(banner.imageId)" />
</div> </div>
</div> </div>
<div v-else> <div v-else>
...@@ -259,4 +275,26 @@ onMounted(() => { ...@@ -259,4 +275,26 @@ onMounted(() => {
cursor: pointer; cursor: pointer;
width: 200px; width: 200px;
} }
.infoHolder {
display: flex;
flex-direction: row;
justify-content: space-between;
}
@media (max-width: 1252px) {
.infoHolder {
flex-direction: column;
}
}
.inputDynamic {
width: 340px;
}
@media (max-width: 960px) {
.inputDynamic {
width: 200px;
}
}
</style> </style>
\ No newline at end of file
...@@ -13,19 +13,23 @@ ...@@ -13,19 +13,23 @@
<h1>Stash</h1> <h1>Stash</h1>
<div class="category row mb-2 m-2"> <div class="category row mb-2 m-2">
<div class="card text-center justify-content-center align-items-center" style="width: 8rem; border: none"> <div class="card text-center justify-content-center align-items-center" style="width: 8rem; border: none">
<img src="../../assets/items/adfree.png" class="card-img-top" alt="..." style="width: 100px; height: 100px;" /> <img src="../../assets/items/adfree.png" class="card-img-top" alt="..."
style="width: 100px; height: 100px;" />
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Adfree</h5> <h5 class="card-title">Adfree</h5>
<button type="button" class="btn btn-primary" id="buttonStyle" @click="buyNoAds"> <button type="button" class="btn btn-primary" id="buttonStyle" data-toggle="modal"
data-target="#adfreeModal">
+35kr +35kr
</button> </button>
</div> </div>
</div> </div>
<div class="card text-center justify-content-center align-items-center" style="width: 8rem; border: none"> <div class="card text-center justify-content-center align-items-center" style="width: 8rem; border: none">
<img src="../../assets/items/piggybank.webp" class="card-img-top" alt="..." style="width: 100px; height: 100px;" /> <img src="../../assets/items/piggybank.webp" class="card-img-top" alt="..."
style="width: 100px; height: 100px;" />
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Premium</h5> <h5 class="card-title">Premium</h5>
<button type="button" class="btn btn-primary" id="buttonStyle" @click="buyPremium"> <button type="button" class="btn btn-primary" id="buttonStyle" data-toggle="modal"
data-target="#premiumModal">
+50kr +50kr
</button> </button>
</div> </div>
...@@ -35,18 +39,16 @@ ...@@ -35,18 +39,16 @@
<div class="col-md-12"> <div class="col-md-12">
<h1>Items</h1> <h1>Items</h1>
<div class="category row mb-2 m-2"> <div class="category row mb-2 m-2">
<div v-for="product in products" :key="product.id" class="card text-center d-flex justify-content-center align-items-center" <div v-for="product in products" :key="product.id"
class="card text-center d-flex justify-content-center align-items-center"
style="width: 16rem; border: none"> style="width: 16rem; border: none">
<img :src="apiUrl + `/api/images/${product.imageId}`" style="width: 200px; height: 100px;" class="card-img-top" alt="..." /> <img :src="apiUrl + `/api/images/${product.imageId}`" style="width: 200px; height: 100px;"
class="card-img-top" alt="..." />
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{{ product.itemName }}</h5> <h5 class="card-title">{{ product.itemName }}</h5>
<h6>{{ product.price }}<img src="../../assets/items/pigcoin.png" style="width: 2rem" /></h6> <h6>{{ product.price }}<img src="../../assets/items/pigcoin.png" style="width: 2rem" /></h6>
<ShopButton <ShopButton v-if="!product.alreadyBought" button-text="Buy item" :disabled="product.price > points"
v-if="!product.alreadyBought" @click="buyItem(product.id)" />
button-text="Buy item"
:disabled="product.price > points"
@click="buyItem(product.id)"
/>
<p v-else>Owned</p> <p v-else>Owned</p>
</div> </div>
</div> </div>
...@@ -55,46 +57,93 @@ ...@@ -55,46 +57,93 @@
<div class="col-md-12"> <div class="col-md-12">
<h1>Cool items</h1> <h1>Cool items</h1>
<div class="category row mb-2 m-2"> <div class="category row mb-2 m-2">
<div class="card text-center d-flex justify-content-center align-items-center" style="width: 8rem; border: none"> <div class="card text-center d-flex justify-content-center align-items-center"
<img src="../../assets/items/coffee.jpg" class="card-img-top" alt="..." style="width: 100px; height: 100px;"> style="width: 8rem; border: none">
<img src="../../assets/items/coffee.jpg" class="card-img-top" alt="..."
style="width: 100px; height: 100px;">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Free Coffee</h5> <h5 class="card-title">Free Coffee</h5>
<h6>500<img src="../../assets/items/pigcoin.png" style="width: 2rem"></h6> <h6>500<img src="../../assets/items/pigcoin.png" style="width: 2rem"></h6>
<ShopButton <ShopButton button-text="Buy item" :disabled="500 > points" @click="buySomething()" />
button-text="Buy item"
:disabled="500 > points"
@click="buySomething()"
/>
</div> </div>
</div> </div>
<div class="card text-center d-flex justify-content-center align-items-center" style="width: 8rem; border: none"> <div class="card text-center d-flex justify-content-center align-items-center"
<img src="../../assets/items/viaplay.jpg" class="card-img-top" alt="..." style="width: 100px; height: 100px;"> style="width: 8rem; border: none">
<img src="../../assets/items/viaplay.jpg" class="card-img-top" alt="..."
style="width: 100px; height: 100px;">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">1 Month</h5> <h5 class="card-title">1 Month</h5>
<h6>10 000<img src="../../assets/items/pigcoin.png" style="width: 2rem"></h6> <h6>10 000<img src="../../assets/items/pigcoin.png" style="width: 2rem"></h6>
<ShopButton <ShopButton button-text="Buy item" :disabled="10000 > points" @click="buySomething()" />
button-text="Buy item"
:disabled="10000 > points"
@click="buySomething()"
/>
</div> </div>
</div> </div>
<div class="card text-center d-flex justify-content-center align-items-center" style="width: 8rem; border: none"> <div class="card text-center d-flex justify-content-center align-items-center"
<img src="../../assets/items/pirbad.png" class="card-img-top" alt="..." style="width: 100px; height: 100px;"> style="width: 8rem; border: none">
<img src="../../assets/items/pirbad.png" class="card-img-top" alt="..."
style="width: 100px; height: 100px;">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">-10% rabatt</h5> <h5 class="card-title">-10% rabatt</h5>
<h6>1000<img src="../../assets/items/pigcoin.png" style="width: 2rem"></h6> <h6>1000<img src="../../assets/items/pigcoin.png" style="width: 2rem"></h6>
<ShopButton <ShopButton button-text="Buy item" :disabled="1000 > points" @click="buySomething()" />
button-text="Buy item"
:disabled="1000 > points"
@click="buySomething()"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="premiumModal" tabindex="-1" role="dialog" aria-labelledby="premiumModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="premiumModalLabel">Premium Package</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<!-- Add premium package information here -->
<p>Unlock exclusive features with our Premium Package!</p>
<ul>
<li>Feature 1</li>
<li>Feature 2</li>
<li>Feature 3</li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Modal for Ad-free Package -->
<div class="modal fade" id="adfreeModal" tabindex="-1" role="dialog" aria-labelledby="adfreeModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="adfreeModalLabel">Ad-free Package</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<!-- Add ad-free package information here -->
<p>Enjoy uninterrupted browsing with our Ad-free Package!</p>
<ul>
<li>No more annoying ads</li>
<li>Fast loading times</li>
<li>Exclusive content</li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
...@@ -261,6 +310,5 @@ onMounted(() => { ...@@ -261,6 +310,5 @@ onMounted(() => {
flex-direction: column; flex-direction: column;
} }
#background { #background {}
}
</style> </style>
\ No newline at end of file
...@@ -5,6 +5,7 @@ import { useUserInfoStore } from "@/stores/UserStore"; ...@@ -5,6 +5,7 @@ import { useUserInfoStore } from "@/stores/UserStore";
import {UserService, BadgeService, GoalService, type GoalDTO, type BadgeDTO, FriendService} from "@/api"; import {UserService, BadgeService, GoalService, type GoalDTO, type BadgeDTO, FriendService} from "@/api";
import { ItemService } from "@/api"; import { ItemService } from "@/api";
import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'
import bannerImage from '@/assets/banners/stacked.svg'
let apiUrl = import.meta.env.VITE_APP_API_URL; let apiUrl = import.meta.env.VITE_APP_API_URL;
...@@ -12,6 +13,8 @@ let firstname = ref(); ...@@ -12,6 +13,8 @@ let firstname = ref();
let lastname = ref(); let lastname = ref();
const imageUrl = ref(`../src/assets/userprofile.png`); const imageUrl = ref(`../src/assets/userprofile.png`);
const bannerImageUrl = ref(bannerImage);
let hasBadges = ref(false) let hasBadges = ref(false)
let hasInventory = ref(false) let hasInventory = ref(false)
...@@ -26,6 +29,8 @@ const streak = ref(0 as any); ...@@ -26,6 +29,8 @@ const streak = ref(0 as any);
const isFriend = ref(false); const isFriend = ref(false);
const isRequestSent = ref(false); const isRequestSent = ref(false);
const isMe = ref(false);
/** /**
* Sets up the form for displaying user profile information. * Sets up the form for displaying user profile information.
* Retrieves user profile data including first name, last name, points, streak, profile image, inventory, and badges. * Retrieves user profile data including first name, last name, points, streak, profile image, inventory, and badges.
...@@ -50,7 +55,12 @@ async function setupForm() { ...@@ -50,7 +55,12 @@ async function setupForm() {
if (response.profileImage) { if (response.profileImage) {
imageUrl.value = apiUrl + "/api/images/" + response.profileImage; imageUrl.value = apiUrl + "/api/images/" + response.profileImage;
} }
getInventory(); if (response.bannerImage != 0 && response.bannerImage !== null) {
console.log(response.bannerImage)
bannerImageUrl.value = apiUrl + "/api/images/" + response.bannerImage;
}
let userId = route.params.id;
isMe.value = String(userId) !== String(useUserInfoStore().id);
getBadges(); getBadges();
} catch (err) { } catch (err) {
handleUnknownError(err) handleUnknownError(err)
...@@ -143,14 +153,34 @@ const removeFriend = () => { ...@@ -143,14 +153,34 @@ const removeFriend = () => {
<div class="row d-flex justify-content-center align-items-center h-100"> <div class="row d-flex justify-content-center align-items-center h-100">
<div class="col 12"> <div class="col 12">
<div class="card"> <div class="card">
<div class="rounded-top text-white d-flex flex-row bg-primary" style="height:200px;" id="banner"> <div class="rounded-top text-white d-flex flex-row bg-primary justify-content-between" :style="{
height: '200px',
backgroundImage: `url(${bannerImageUrl})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat'
}">
<div class=" text-white d-flex flex-row">
<div class=" d-flex flex-column align-items-center justify-content-center"> <div class=" d-flex flex-column align-items-center justify-content-center">
<img :src="imageUrl" alt="Generisk plassholderbilde" class="img-fluid img-thumbnail" <img :src="imageUrl" alt="Generisk plassholderbilde" class="img-fluid img-thumbnail"
style="width: 150px; height:150px; margin-left: 25px; margin-right: 15px;"> style="width: 150px; height:150px; margin-left: 25px; margin-right: 15px;">
</div> </div>
<h1 data-cy="firstname" style="display: flex; align-items: end; margin-bottom: 20px;">{{ firstname }} {{ lastname }}</h1> <h1 data-cy="firstname" style="display: flex; align-items: end; margin-bottom: 20px;">{{ firstname }} {{
lastname }}</h1>
</div>
<div class="d-flex align-items-end text-white my-3 mx-5">
<div class="d-flex align-items-center flex-column">
<p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="points"><img
src="@/assets/items/pigcoin.png" style="width: 80px; height: 80px" data-toggle="tooltip"
title="Points"> {{ points }}</p>
</div>
<div class="d-flex align-items-center flex-column px-3">
<p class="mb-1 h2 d-flex flex-column align-items-center" data-cy="streak"><img
src="@/assets/icons/fire.png" style="width: 80px; height: 80px" data-toggle="tooltip"
title="Points"> {{ streak }}</p>
</div>
</div> </div>
<div class="p-3 text-black" style="background-color: #f8f9fa;"> </div>
<div v-if="isMe" class="p-3 text-black" style="background-color: #f8f9fa;">
<div class="d-flex justify-content-end text-center py-1"> <div class="d-flex justify-content-end text-center py-1">
<div style="width: 100%; display: flex; justify-content: start"> <div style="width: 100%; display: flex; justify-content: start">
<button <button
...@@ -178,35 +208,6 @@ const removeFriend = () => { ...@@ -178,35 +208,6 @@ const removeFriend = () => {
Fjern venn Fjern venn
</button> </button>
</div> </div>
<div>
<p class="mb-1 h2" data-cy="points">{{ points }} <img src="@/assets/items/pigcoin.png" style="width: 4rem"></p>
<p class="small text-muted mb-0">Poeng</p>
</div>
<div class="px-3">
<p class="mb-1 h2" data-cy="streak">{{ streak }} <img src="@/assets/icons/fire.png" style="width: 4rem"></p>
<p class="small text-muted mb-0">Streak</p>
</div>
</div>
</div>
<hr>
<div class="card-body p-1 text-black">
<div class="row">
<div class="col">
<div class="container-fluid">
<h1 class="mt-1 text-start badges-text">Lageret ditt</h1>
<div v-if="hasInventory" class="scrolling-wrapper-badges row flex-row flex-nowrap mt-2 pb-2 pt-2">
<div v-for="product in inventory" :key="product.id" class="card text-center"
style="width: 12rem; border: none; cursor: pointer; margin: 1rem; border: 2px solid black">
<img :src="apiUrl + `/api/images/${product.imageId}`" class="card-img-top"
alt="..." />
<div class="card-body">
<h5 class="card-title">{{ product.itemName }}</h5>
</div>
</div>
</div>
<div v-else>Ingen gjenstander</div>
</div>
</div>
</div> </div>
</div> </div>
<hr> <hr>
......
...@@ -168,8 +168,8 @@ const toUpdateUserSettings = () => { ...@@ -168,8 +168,8 @@ const toUpdateUserSettings = () => {
<div class="row d-flex justify-content-center align-items-center h-100"> <div class="row d-flex justify-content-center align-items-center h-100">
<div class="col 12"> <div class="col 12">
<div class="card"> <div class="card">
<div class="rounded-top text-white d-flex flex-row bg-primary justify-content-between" :style="{ <div class="rounded-top text-white d-flex flex-row bg-primary justify-content-between flex-wrap" id="banner" :style="{
height: '200px',
backgroundImage: `url(${bannerImageUrl})`, backgroundImage: `url(${bannerImageUrl})`,
backgroundSize: 'cover', backgroundSize: 'cover',
backgroundRepeat: 'no-repeat' backgroundRepeat: 'no-repeat'
...@@ -250,7 +250,8 @@ const toUpdateUserSettings = () => { ...@@ -250,7 +250,8 @@ const toUpdateUserSettings = () => {
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{{ goals[index]['name'] }}</h5> <h5 class="card-title">{{ goals[index]['name'] }}</h5>
<p class="card-text">{{ goals[index]['description'] }}</p> <p class="card-text">{{ goals[index]['description'] }}</p>
<p class="card-text"><small class="text-muted">{{ goals[index]['targetAmount'] }}</small> <p class="card-text"><small class="text-muted">{{ goals[index]['targetAmount']
}}</small>
</p> </p>
<a href="#" class="btn stretched-link" @click="toRoadmap"></a> <a href="#" class="btn stretched-link" @click="toRoadmap"></a>
</div> </div>
...@@ -330,7 +331,13 @@ const toUpdateUserSettings = () => { ...@@ -330,7 +331,13 @@ const toUpdateUserSettings = () => {
} }
#banner { #banner {
background-image: url('/src/assets/banners/stacked.svg'); height: 200px;
}
@media (max-width: 940px) {
#banner {
height: 320px;
}
} }
/*-------*/ /*-------*/
...@@ -338,6 +345,12 @@ const toUpdateUserSettings = () => { ...@@ -338,6 +345,12 @@ const toUpdateUserSettings = () => {
background-color: #00DBDE; background-color: #00DBDE;
} }
.classyButton {
background-color: #003A58;
border: #003A58;
color: white;
}
.classyButton:hover { .classyButton:hover {
background-color: #003b58ec; background-color: #003b58ec;
border: #003A58; border: #003A58;
......
...@@ -28,6 +28,12 @@ const routes = [ ...@@ -28,6 +28,12 @@ const routes = [
name: 'profile', name: 'profile',
component: () => import('@/views/User/MyProfileView.vue'), component: () => import('@/views/User/MyProfileView.vue'),
}, },
{
path: 'admin',
name: 'admin',
component: () => import('@/views/Admin/AdminDashboardView.vue'),
meta: { requiresAdmin: true }
},
{ {
path: '/settings', path: '/settings',
name: 'settings', name: 'settings',
...@@ -186,7 +192,7 @@ router.beforeEach((to, from, next) => { ...@@ -186,7 +192,7 @@ router.beforeEach((to, from, next) => {
if (requiresAuth && !isAuthenticated) { if (requiresAuth && !isAuthenticated) {
next({ name: 'login', query: { redirect: to.fullPath } }); next({ name: 'login', query: { redirect: to.fullPath } });
} else if (requiresAdmin && userRole !== 'admin') { } else if (requiresAdmin && userRole !== 'ADMIN') {
next({ name: 'unauthorized' }); next({ name: 'unauthorized' });
} else if (requiresPremium && userSubscription !== 'PREMIUM') { } else if (requiresPremium && userSubscription !== 'PREMIUM') {
next({ name: 'home' }); next({ name: 'home' });
......
<script setup lang="ts">
import AddminFeedback from "@/components/Admin/AdminFeedback.vue";
</script>
<template>
<AddminFeedback></AddminFeedback>
</template>
<style scoped>
</style>
\ No newline at end of file
...@@ -18,4 +18,10 @@ import { useUserInfoStore } from '@/stores/UserStore'; ...@@ -18,4 +18,10 @@ import { useUserInfoStore } from '@/stores/UserStore';
min-height: 700px; min-height: 700px;
margin: 0 140px; margin: 0 140px;
} }
@media (max-width: 768px) {
#minHeight {
margin: 0 20px;
}
}
</style> </style>
\ No newline at end of file
...@@ -2,15 +2,25 @@ ...@@ -2,15 +2,25 @@
<main> <main>
<div class="wrapper"> <div class="wrapper">
<div id="formFrame"> <div id="formFrame">
<h1>TIlbakemelding</h1> <h1>Tilbakemelding</h1>
<form @submit.prevent="submitForm"> <form ref="formRef" id="loginForm" @submit.prevent="submitForm" novalidate>
<BaseInput v-model="email" label="Email" type="email" placeholder="Enter your email" inputId="email" required /> <BaseInput :model-value="emailRef"
@input-change-event="handleEmailInputEvent"
id="emailInput"
input-id="email"
type="email"
label="E-post"
placeholder="Skriv inn din e-post"
invalid-message="Ugyldig e-post"
/>
<br> <br>
<label for="feedback">Din tilbakemelding:</label> <label for="feedback">Din tilbakemelding:</label>
<textarea v-model="message" placeholder="Write here" rows="5" name="comment[text]" id="comment_text" cols="33" <textarea v-model="messageRef" placeholder="Skriv meldingen din her" rows="5" name="comment[text]" id="comment_text" cols="33"
required></textarea> required></textarea>
<BaseButton button-text="Send" @click="submitForm">Send inn</BaseButton> <p data-cy="change-email-msg-error" class="text-danger">{{ errorMsg }}</p>
<p v-if="submissionStatus">{{ submissionStatus }}</p> <BaseButton button-text="Send" @click="submitForm" style="padding: 10px 30px; font-size: 18px; font-weight: normal;">Send inn</BaseButton>
<p data-cy="change-email-msg-confirm" class="text-success">{{ confirmationMsg }}</p>
</form> </form>
</div> </div>
</div> </div>
...@@ -21,13 +31,33 @@ ...@@ -21,13 +31,33 @@
import { ref } from 'vue'; import { ref } from 'vue';
import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue'; import BaseInput from '@/components/BaseComponents/Input/BaseInput.vue';
import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue'; import BaseButton from '@/components/BaseComponents/Buttons/BaseButton.vue';
import { type FeedbackRequestDTO, UserService } from '@/api'
import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'
const email = ref(""); const emailRef = ref("");
const message = ref(""); const messageRef = ref("");
const submissionStatus = ref(""); const errorMsg = ref('')
const confirmationMsg = ref('')
const submitForm = async () => { const handleEmailInputEvent = (newValue: any) => {
emailRef.value = newValue
}
const submitForm = async () => {
try {
const feedbackRequest: FeedbackRequestDTO = {
email: emailRef.value,
message: messageRef.value
};
console.log("feedbackRequest", feedbackRequest);
UserService.sendFeedback({ requestBody: feedbackRequest });
messageRef.value = ''
errorMsg.value = ''
confirmationMsg.value = 'Tilbakemeldingen ble sendt!'
} catch (err) {
errorMsg.value = handleUnknownError(err);
confirmationMsg.value = ''
}
}; };
</script> </script>
......
...@@ -107,7 +107,8 @@ function toBilling() { ...@@ -107,7 +107,8 @@ function toBilling() {
<div class="card-header border-bottom mb-3 d-flex d-md-none"> <div class="card-header border-bottom mb-3 d-flex d-md-none">
<ul class="nav nav-tabs card-header-tabs nav-gap-x-1" role="tablist"> <ul class="nav nav-tabs card-header-tabs nav-gap-x-1" role="tablist">
<li class="nav-item"> <li class="nav-item">
<a href="#" data-toggle="tab" class="nav-link has-icon active"><svg <a @click.prevent="setActive('/settings/profile')" @click="toProfile"
:class="['nav-item nav-link has-icon', { 'nav-link-faded': useRoute().path !== '/settings/profile', 'active': useRoute().path === '/settings/profile' }]"><svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-user"> stroke-linejoin="round" class="feather feather-user">
...@@ -116,7 +117,8 @@ function toBilling() { ...@@ -116,7 +117,8 @@ function toBilling() {
</svg></a> </svg></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#" data-toggle="tab" class="nav-link has-icon"><svg <a @click.prevent="setActive('/settings/account')" @click="toAccount"
:class="['nav-item nav-link has-icon', { 'nav-link-faded': useRoute().path !== '/settings/account', 'active': useRoute().path === '/settings/account' }]"><svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-settings"> stroke-linejoin="round" class="feather feather-settings">
...@@ -127,7 +129,8 @@ function toBilling() { ...@@ -127,7 +129,8 @@ function toBilling() {
</svg></a> </svg></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#" data-toggle="tab" class="nav-link has-icon"><svg <a @click.prevent="setActive('/settings/security')" @click="toSecurity"
:class="['nav-item nav-link has-icon', { 'nav-link-faded': useRoute().path !== '/settings/security', 'active': useRoute().path === '/settings/security' }]"><svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-shield"> stroke-linejoin="round" class="feather feather-shield">
...@@ -135,16 +138,8 @@ function toBilling() { ...@@ -135,16 +138,8 @@ function toBilling() {
</svg></a> </svg></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#" data-toggle="tab" class="nav-link has-icon"><svg <a @click.prevent="setActive('/settings/bank')" @click="toBilling"
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" :class="['nav-item nav-link has-icon', { 'nav-link-faded': useRoute().path !== '/settings/bank', 'active': useRoute().path === '/settings/bank' }]"><svg
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-bell">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
</svg></a>
</li>
<li class="nav-item">
<a href="#" data-toggle="tab" class="nav-link has-icon"><svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="feather feather-credit-card"> stroke-linejoin="round" class="feather feather-credit-card">
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment