diff --git a/spec.json b/spec.json new file mode 100644 index 0000000000000000000000000000000000000000..26570a49b0bae494c990a7ffb525e6bedf4c6a7f --- /dev/null +++ b/spec.json @@ -0,0 +1,535 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Sparesti API", + "description": "The Sparesti API", + "version": "3.0" + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "Generated server url" + } + ], + "security": [ + { + "Bearer Authentication": [] + } + ], + "tags": [ + { + "name": "Authentication", + "description": "User authentication" + }, + { + "name": "Leaderboard", + "description": "Retrieving leaderboard data" + } + ], + "paths": { + "/api/auth/valid-email/{email}": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "Validate email", + "description": "Check that the given email is valid", + "operationId": "validateEmail", + "parameters": [ + { + "name": "email", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "409": { + "description": "Email already exists", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ExceptionResponse" + } + } + } + }, + "200": { + "description": "Email is valid", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [] + } + }, + "/api/auth/signup": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "User Signup", + "description": "Sign up a new user", + "operationId": "signup", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignUpRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successfully signed up", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResponse" + } + } + } + }, + "409": { + "description": "Email already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExceptionResponse" + } + } + } + } + }, + "security": [] + } + }, + "/api/auth/login": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "User Login", + "description": "Log in with an existing user", + "operationId": "login", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successfully logged in", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResponse" + } + } + } + }, + "401": { + "description": "Invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExceptionResponse" + } + } + } + }, + "404": { + "description": "User not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExceptionResponse" + } + } + } + } + }, + "security": [] + } + }, + "/api/users": { + "patch": { + "tags": [ + "user-controller" + ], + "summary": "Update profile", + "description": "Update the profile of the authenticated user", + "operationId": "update", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserUpdateDTO" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successfully updated profile", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDTO" + } + } + } + } + } + } + }, + "/api/users/{userId}/profile": { + "get": { + "tags": [ + "user-controller" + ], + "summary": "Get profile", + "description": "Get user profile", + "operationId": "getProfile", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "Successfully got profile", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileDTO" + } + } + } + } + } + } + }, + "/api/users/me": { + "get": { + "tags": [ + "user-controller" + ], + "summary": "Get user", + "description": "Get user information", + "operationId": "getUser", + "responses": { + "200": { + "description": "Successfully got user", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDTO" + } + } + } + } + } + } + }, + "/api/leaderboard": { + "get": { + "tags": [ + "Leaderboard" + ], + "operationId": "getLeaderboard", + "parameters": [ + { + "name": "type", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "filter", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "entryCount", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeaderboardDTO" + } + } + } + } + } + } + }, + "/api/leaderboard/surrounding": { + "get": { + "tags": [ + "Leaderboard" + ], + "operationId": "getSurrounding", + "parameters": [ + { + "name": "type", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "filter", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "entryCount", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeaderboardDTO" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ExceptionResponse": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + }, + "SignUpRequest": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "commitment": { + "type": "string" + }, + "experience": { + "type": "string" + }, + "challengeTypes": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "AuthenticationResponse": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "role": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, + "LoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "UserUpdateDTO": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "commitment": { + "type": "string" + }, + "experience": { + "type": "string" + }, + "challengeTypes": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "UserDTO": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "role": { + "type": "string" + } + } + }, + "ProfileDTO": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + } + }, + "LeaderboardDTO": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "filter": { + "type": "string" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LeaderboardEntryDTO" + } + } + } + }, + "LeaderboardEntryDTO": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/UserDTO" + }, + "score": { + "type": "integer", + "format": "int32" + } + } + } + }, + "securitySchemes": { + "Bearer Authentication": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + } +} \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 12ad03a6b286a47ed6a392e7338562c82b213f64..dc693f471ad9c9828c6519d81cea07c8bd5ce5ee 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import { RouterView } from 'vue-router' -import ErrorBoundaryCatcher from '@/components/Exceptions/ErrorBoundaryCatcher.vue'; +// import ErrorBoundaryCatcher from '@/components/Exceptions/ErrorBoundaryCatcher.vue'; </script> <template> diff --git a/src/api/index.ts b/src/api/index.ts index 2a974f1dd456ed118f2caf5c190038eb3cfa7c3f..22e1808268ec1c7b349141ce999d2e4b668cb45c 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -9,7 +9,14 @@ export type { OpenAPIConfig } from './core/OpenAPI'; export type { AuthenticationResponse } from './models/AuthenticationResponse'; export type { ExceptionResponse } from './models/ExceptionResponse'; +export type { LeaderboardDTO } from './models/LeaderboardDTO'; +export type { LeaderboardEntryDTO } from './models/LeaderboardEntryDTO'; export type { LoginRequest } from './models/LoginRequest'; +export type { ProfileDTO } from './models/ProfileDTO'; export type { SignUpRequest } from './models/SignUpRequest'; +export type { UserDTO } from './models/UserDTO'; +export type { UserUpdateDTO } from './models/UserUpdateDTO'; export { AuthenticationService } from './services/AuthenticationService'; +export { LeaderboardService } from './services/LeaderboardService'; +export { UserControllerService } from './services/UserControllerService'; diff --git a/src/api/models/LeaderboardDTO.ts b/src/api/models/LeaderboardDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..58bddb76f2fc2a4cfbf09ec19b8915ca5f7f3976 --- /dev/null +++ b/src/api/models/LeaderboardDTO.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { LeaderboardEntryDTO } from './LeaderboardEntryDTO'; +export type LeaderboardDTO = { + type?: string; + filter?: string; + entries?: Array<LeaderboardEntryDTO>; +}; + diff --git a/src/api/models/LeaderboardEntryDTO.ts b/src/api/models/LeaderboardEntryDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..9a93b2ff579409b4fa78077ba9f9cb47befc465d --- /dev/null +++ b/src/api/models/LeaderboardEntryDTO.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { UserDTO } from './UserDTO'; +export type LeaderboardEntryDTO = { + user?: UserDTO; + score?: number; +}; + diff --git a/src/api/models/ProfileDTO.ts b/src/api/models/ProfileDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3a7d36f290da3dfa38b6143e83b934b427ee47f --- /dev/null +++ b/src/api/models/ProfileDTO.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ProfileDTO = { + id?: number; + firstName?: string; + lastName?: string; + createdAt?: string; +}; + diff --git a/src/api/models/SignUpRequest.ts b/src/api/models/SignUpRequest.ts index 28a2e78a3a39e8ceb5f28567ebdf02d912017210..f9aa1f6162784c19e4012289fd61c429edf6eea7 100644 --- a/src/api/models/SignUpRequest.ts +++ b/src/api/models/SignUpRequest.ts @@ -7,8 +7,8 @@ export type SignUpRequest = { lastName?: string; email?: string; password?: string; - changeWilling?: string; + commitment?: string; experience?: string; - challenges?: Array<string>; + challengeTypes?: Array<string>; }; diff --git a/src/api/models/UserDTO.ts b/src/api/models/UserDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..56f00b30d40ee5427b6dce81810255504a11b756 --- /dev/null +++ b/src/api/models/UserDTO.ts @@ -0,0 +1,13 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type UserDTO = { + id?: number; + firstName?: string; + lastName?: string; + email?: string; + createdAt?: string; + role?: string; +}; + diff --git a/src/api/models/UserUpdateDTO.ts b/src/api/models/UserUpdateDTO.ts new file mode 100644 index 0000000000000000000000000000000000000000..54cc0f5524b3ca515f3b671e02edf92ec05a54aa --- /dev/null +++ b/src/api/models/UserUpdateDTO.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type UserUpdateDTO = { + firstName?: string; + lastName?: string; + email?: string; + password?: string; + commitment?: string; + experience?: string; + challengeTypes?: Array<string>; +}; + diff --git a/src/api/services/LeaderboardService.ts b/src/api/services/LeaderboardService.ts new file mode 100644 index 0000000000000000000000000000000000000000..f77a3cc9b2e67de48357385ed47ae8c523b2c0e0 --- /dev/null +++ b/src/api/services/LeaderboardService.ts @@ -0,0 +1,56 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { LeaderboardDTO } from '../models/LeaderboardDTO'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class LeaderboardService { + /** + * @returns LeaderboardDTO OK + * @throws ApiError + */ + public static getLeaderboard({ + type, + filter, + entryCount = 10, + }: { + type: string, + filter: string, + entryCount?: number, + }): CancelablePromise<LeaderboardDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/leaderboard', + query: { + 'type': type, + 'filter': filter, + 'entryCount': entryCount, + }, + }); + } + /** + * @returns LeaderboardDTO OK + * @throws ApiError + */ + public static getSurrounding({ + type, + filter, + entryCount = 10, + }: { + type: string, + filter: string, + entryCount?: number, + }): CancelablePromise<LeaderboardDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/leaderboard/surrounding', + query: { + 'type': type, + 'filter': filter, + 'entryCount': entryCount, + }, + }); + } +} diff --git a/src/api/services/UserControllerService.ts b/src/api/services/UserControllerService.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a1ea3560cb016455a4d2c77c2e34e9b0754a684 --- /dev/null +++ b/src/api/services/UserControllerService.ts @@ -0,0 +1,61 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProfileDTO } from '../models/ProfileDTO'; +import type { UserDTO } from '../models/UserDTO'; +import type { UserUpdateDTO } from '../models/UserUpdateDTO'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class UserControllerService { + /** + * Update profile + * Update the profile of the authenticated user + * @returns UserDTO Successfully updated profile + * @throws ApiError + */ + public static update({ + requestBody, + }: { + requestBody: UserUpdateDTO, + }): CancelablePromise<UserDTO> { + return __request(OpenAPI, { + method: 'PATCH', + url: '/api/users', + body: requestBody, + mediaType: 'application/json', + }); + } + /** + * Get profile + * Get user profile + * @returns ProfileDTO Successfully got profile + * @throws ApiError + */ + public static getProfile({ + userId, + }: { + userId: number, + }): CancelablePromise<ProfileDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/users/{userId}/profile', + path: { + 'userId': userId, + }, + }); + } + /** + * Get user + * Get user information + * @returns UserDTO Successfully got user + * @throws ApiError + */ + public static getUser(): CancelablePromise<UserDTO> { + return __request(OpenAPI, { + method: 'GET', + url: '/api/users/me', + }); + } +} diff --git a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue index 91c39045671a85816e70cab8bd81ed8131b4452a..dc849d5cdde9afd61f9e53e53b4eaefba203bf64 100644 --- a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue +++ b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue @@ -22,6 +22,7 @@ const onChangedChallengeEvent = (value) => { console.log('Reached') chosenChallenges.value = chosenChallenges.value.filter(item => item !== value[0]); } + console.log(chosenChallenges.value) } const onClick = () => { diff --git a/src/components/InputFields/BaseInput.vue b/src/components/InputFields/BaseInput.vue index 22ed03466408636bb262bb56fe77aa63f914b6ac..c773e4c203294cabd1dee9d555dd94b013961871 100644 --- a/src/components/InputFields/BaseInput.vue +++ b/src/components/InputFields/BaseInput.vue @@ -22,10 +22,6 @@ const props = defineProps({ type: String, default: "" }, - isValid: { - type: Boolean, - default: false - }, min: { type: String, required: false @@ -33,6 +29,14 @@ const props = defineProps({ pattern: { type: String, default: null + }, + validMessage: { + type: String, + default: '' + }, + invalidMessage: { + type: String, + default: '' } }); @@ -44,17 +48,16 @@ const onInputEvent = (event: any) => { <template> <div> <label :for="inputId">{{ label }}</label> - <input :value="props.modelValue" + <input :value="modelValue" @input="onInputEvent" - :type="props.type" + :type="type" class="form-control" - :placeholder="props.placeholder" + :placeholder="placeholder" :id="inputId" required :min="min" - :pattern="pattern" - /> - <div v-if="props.isValid" class="invalid-feedback">Invalid {{ label }}</div> - <div v-else class="valid-feedback">Valid {{ label }}</div> + :pattern="pattern"/> + <div class="valid-feedback">{{ validMessage }}</div> + <div class="invalid-feedback">{{ invalidMessage }}</div> </div> </template> diff --git a/src/components/Login/LoginForm.vue b/src/components/Login/LoginForm.vue index 953ecb8e13403f5efcd766222a1f936b6280a630..21d4de86686d68850b9ee49b09cb9246be596ced 100644 --- a/src/components/Login/LoginForm.vue +++ b/src/components/Login/LoginForm.vue @@ -8,8 +8,8 @@ import { useRouter, useRoute } from 'vue-router'; import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; import { useErrorStore } from '@/stores/ErrorStore'; -const emailRef = ref() -const passwordRef = ref() +const emailRef = ref('') +const passwordRef = ref('') const formRef = ref() let errorMsg = ref(''); @@ -19,13 +19,18 @@ const userStore = useUserInfoStore(); const handleEmailInputEvent = (newValue: any) => { emailRef.value = newValue + console.log(emailRef.value) } const handlePasswordInputEvent = (newValue: any) => { passwordRef.value = newValue + console.log(passwordRef.value) } const handleSubmit = async () => { + console.log(emailRef.value) + console.log(passwordRef.value) + formRef.value.classList.add("was-validated") const loginUserPayload: LoginRequest = { email: emailRef.value, @@ -64,11 +69,30 @@ const handleSubmit = async () => { <h1>Sparesti.no</h1> </div> <form ref="formRef" id="loginForm" @submit.prevent="handleSubmit" novalidate> - <BaseInput :model-value="emailRef" @input-change-event="handleEmailInputEvent" id="emailInput" input-id="email" - type="email" label="Email" placeholder="Enter your email" /> - <BaseInput pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" :model-value="passwordRef" - @input-change-event="handlePasswordInputEvent" id="passwordInput" input-id="password" type="password" - label="Password" placeholder="Enter password" /> + + <BaseInput :model-value="emailRef" + @input-change-event="handleEmailInputEvent" + id="emailInput" + input-id="email" + type="email" + label="Email" + placeholder="Enter your email" + valid-message="Valid email" + invalid-message="Invalid email" + /> + + <BaseInput :model-value="passwordRef" + @input-change-event="handlePasswordInputEvent" + id="passwordInput" + input-id="password" + type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" + label="Password" + placeholder="Enter password" + valid-message="Valid password" + invalid-message="Password must be between 4 and 16 characters and contain one capital letter, small letter and a number" + /> + <p class="text-danger">{{ errorMsg }}</p> <button1 id="confirmButton" type="submit" @click="handleSubmit" button-text="Login"></button1> </form> diff --git a/src/components/SavingGoalComponents/SavingGoal.vue b/src/components/SavingGoalComponents/SavingGoal.vue index 89cae3a4258a8c705e15c4146f8c2e479530d7d5..e3de05112d6f449f7d2a1f3f0ba047d8b056fcd7 100644 --- a/src/components/SavingGoalComponents/SavingGoal.vue +++ b/src/components/SavingGoalComponents/SavingGoal.vue @@ -36,7 +36,7 @@ export default { </script> <template> - <div class="container"> + <div class="cont"> <div class="row"> <div class="col-lg-4 blue-background overflow-auto" :style="{ 'max-height': bluePanelMaxHeight }"> <h3 style="color: white; margin-bottom: 16px">Your saving goals</h3> @@ -53,9 +53,10 @@ export default { </template> <style scoped> -.container { +.cont { + padding-left: 10px; margin: 0; - width: 100%; + width: 98%; box-sizing: unset; } diff --git a/src/components/SavingGoalComponents/SavingGoalRoadmap.vue b/src/components/SavingGoalComponents/SavingGoalRoadmap.vue index 74e8d2d54cea2c9a2a0d34d0c7435b0a93f6994d..018c4579e1948559e1bbc2f93220fac6f97a3d0b 100644 --- a/src/components/SavingGoalComponents/SavingGoalRoadmap.vue +++ b/src/components/SavingGoalComponents/SavingGoalRoadmap.vue @@ -4,6 +4,7 @@ interface Step { showPanel: boolean; description: string; percentFinished: number; + unFinished: boolean; } export default { data() { @@ -13,18 +14,28 @@ export default { title: 'Spain trip' as string, //This will be changed to info gathered from backend steps: [ - { title: 'Challenge 1', showPanel: false, description: 'Save 50kr on coffee in 3 days', percentFinished: 22 }, - { title: 'Challenge 2', showPanel: false, description: 'Save 500kr on food in 7 days', percentFinished: 73 }, - { title: 'Challenge 3', showPanel: false, description: 'Save 350kr on clothes in 5 days', percentFinished: 50 }, - { title: 'Challenge 4', showPanel: false, description: 'Save 150kr on coffee in 4 days', percentFinished: 10 }, - { title: 'Challenge 5', showPanel: false, description: 'Save 50kr on coffee in 3 days', percentFinished: 90 } + { title: 'Challenge 1', showPanel: false, description: 'Save 50kr on coffee in 3 days', percentFinished: 22, unFinished: false }, + { title: 'Challenge 2', showPanel: false, description: 'Save 500kr on food in 7 days', percentFinished: 73, unFinished: false }, + { title: 'Challenge 3', showPanel: false, description: 'Save 350kr on clothes in 5 days', percentFinished: 50, unFinished: true }, + { title: 'Challenge 4', showPanel: false, description: 'Save 150kr on coffee in 4 days', percentFinished: 10, unFinished: true }, + { title: 'Challenge 5', showPanel: false, description: 'Save 50kr on coffee in 3 days', percentFinished: 90, unFinished: true } ] , bluePanelMaxHeight: 'auto' as string }; }, mounted() { - this.togglePanel(this.steps[2]); + setTimeout(() => { + this.togglePanel(this.steps[2]); + }, 500); + + }, + computed: { + computeImageFilter() { + return (step: Step) => { + return step.unFinished ? 'none' : 'grayscale(100%)'; + }; + } }, methods: { togglePanel(step: Step) { @@ -40,12 +51,8 @@ export default { if (step.showPanel) { this.$nextTick(() => { const panel = document.getElementById(`panel-${this.steps.indexOf(step)}`); - console.log(panel); if (panel) { - setTimeout(() => { - panel.scrollIntoView({ behavior: 'smooth', block: 'center' }); - console.log('I should have scrolled'); - }, 100); // Wait for 1 second before scrolling + panel.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }); } @@ -56,11 +63,24 @@ export default { <template> <div class="col-lg-8"> - <div class="SavingGoalTitle text-center">{{title}}</div> + <div class="SavingGoalTitle text-center"> + {{title}} + <br> + <p class="d-inline-flex gap-1"> + <button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample" style="font-size: 25px;"> + See more info + </button> + </p> + <div class="collapse" id="collapseExample"> + <div class="card card-body bg-primary mx-auto" style="width: 80%;"> + Total saved: 20kr + </div> + </div> + </div> <ul class="timeline"> <li v-for="(step, index) in steps" :key="index" :class="{ 'timeline-inverted': index % 2 !== 0 }"> <div class="timeline-image z-1" @click="togglePanel(step)"> - <img class="circular-image" :src="step.showPanel ? altImage : image" alt=""> + <img class="circular-image" :src="step.showPanel ? altImage : image" :style="{ filter: computeImageFilter(step) }" alt=""> </div> <div class="timeline-panel z-3" :id="'panel-' + index" v-show="step.showPanel"> <div class="timeline-heading"> @@ -95,9 +115,8 @@ export default { <style scoped> .col-lg-8 { - width: 63%; + width: 58%; margin-bottom: 20px; - max-height: 1000px; } .SavingGoalTitle { diff --git a/src/components/SignUp/SignUpForm.vue b/src/components/SignUp/SignUpForm.vue index b085f6ee59b1b3d38c777f8b041fa9d1b76013d6..9a8dab783a1c291575375d0f8ea69fb1d3a6b525 100644 --- a/src/components/SignUp/SignUpForm.vue +++ b/src/components/SignUp/SignUpForm.vue @@ -5,15 +5,18 @@ import { ref } from 'vue' import { useRouter } from 'vue-router' const router = useRouter(); + const firstNameRef = ref('') const surnameRef = ref('') const emailRef = ref('') const passwordRef = ref('') const confirmPasswordRef = ref('') const formRef = ref() +let samePasswords = ref(true) const handleFirstNameInputEvent = (newValue: any) => { firstNameRef.value = newValue + console.log(firstNameRef.value) } const handleSurnameInputEvent = (newValue: any) => { @@ -26,21 +29,30 @@ const handleEmailInputEvent = (newValue: any) => { const handlePasswordInputEvent = (newValue: any) => { passwordRef.value = newValue + console.log(passwordRef.value) } const handleConfirmPasswordInputEvent = (newValue: any) => { confirmPasswordRef.value = newValue + console.log(confirmPasswordRef.value) } -const handleSubmit = () => { +const handleSubmit = async () => { + console.log(firstNameRef.value) + + samePasswords.value = (passwordRef.value === confirmPasswordRef.value) + console.log(samePasswords.value) const form = formRef.value; // Check if the form is valid if (form.checkValidity()) { // Form is valid, submit the form or perform other actions console.log('Form is valid'); + } else { + console.log('Form is not valid'); } + formRef.value.classList.add("was-validated") } @@ -49,46 +61,54 @@ const handleSubmit = () => { <template> <div class="container"> <form ref="formRef" id="signUpForm" @submit.prevent="handleSubmit"> - <BaseInput :model-value="firstNameRef.value" + <BaseInput :model-value="firstNameRef" @input-change-event="handleFirstNameInputEvent" ref="firstNameRef" id="firstNameInput" input-id="first-name" type="text" label="First name" - placeholder="Enter your first name"/> - <BaseInput :model-value="surnameRef.value" + placeholder="Enter your first name" + invalid-message="Please enter your first name"/> + <BaseInput :model-value="surnameRef" @input-change-event="handleSurnameInputEvent" ref="surnameRef" id="surnameInput" input-id="surname" type="text" label="Surname" - placeholder="Enter your surname"/> - <BaseInput :model-value="emailRef.value" + placeholder="Enter your surname" + invalid-message="Please enter your surname"/> + <BaseInput :model-value="emailRef" @input-change-event="handleEmailInputEvent" ref="emailRef" id="emailInput" input-id="email" type="email" label="Email" - placeholder="Enter your email"/> - <BaseInput :model-value="passwordRef.value" + placeholder="Enter your email" + invalid-message="Invalid email"/> + <BaseInput :model-value="passwordRef" @input-change-event="handlePasswordInputEvent" ref="passwordRef" id="passwordInput" input-id="password" type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" label="Password" - placeholder="Enter password"/> - <BaseInput :model-value="confirmPasswordRef.value" + placeholder="Enter password" + invalid-message="Password must be between 4 and 16 characters and contain one capital letter, small letter and a number"/> + <BaseInput :modelValue="confirmPasswordRef" @input-change-event="handleConfirmPasswordInputEvent" ref="confirmPasswordRef" id="confirmPasswordInput" input-id="confirmPassword" type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" label="Confirm Password" - placeholder="Confirm password"/> + placeholder="Confirm password" + invalid-message="Password must be between 4 and 16 characters and contain one capital letter, small letter and a number"/> + <p v-if="samePasswords" class="text-danger">The passwords are not identical</p> <button1 id="confirmButton" @click="handleSubmit" button-text="Sign up"></button1> </form> </div>