Merge branch 'security_branch' of https://github.com/lumijiez/ExpenseTrackerFAF into security_branch
# Conflicts: # src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java # src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationController.java # src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -43,6 +43,10 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.faf223.expensetrackerfaf.config;
|
package com.faf223.expensetrackerfaf.config;
|
||||||
|
|
||||||
|
import com.faf223.expensetrackerfaf.controller.auth.ErrorResponse;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
@@ -15,7 +18,6 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
@@ -35,23 +37,38 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
final String authHeader = request.getHeader("Authorization");
|
final String authHeader = request.getHeader("Authorization");
|
||||||
final String jwt;
|
final String jwt;
|
||||||
final String userEmail;
|
String userEmail;
|
||||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt = authHeader.substring(7);
|
jwt = authHeader.substring(7);
|
||||||
|
|
||||||
|
try {
|
||||||
userEmail = jwtService.extractUsername(jwt);
|
userEmail = jwtService.extractUsername(jwt);
|
||||||
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
|
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
|
||||||
if (jwtService.isTokenValid(jwt, userDetails)) {
|
if (jwtService.isTokenValid(jwt, userDetails)) {
|
||||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
||||||
userDetails, null, userDetails.getAuthorities());
|
userDetails, null, userDetails.getAuthorities());
|
||||||
authToken.setDetails(new WebAuthenticationDetailsSource()
|
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
.buildDetails(request));
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
// Token is expired; return a custom response
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
response.setContentType("application/json");
|
||||||
|
|
||||||
|
ErrorResponse errorResponse = new ErrorResponse("TokenExpired", "Your session has expired. Please log in again.");
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper(); // You may need to import ObjectMapper
|
||||||
|
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
|
||||||
|
|
||||||
|
|
||||||
|
response.getWriter().flush();
|
||||||
|
return;
|
||||||
|
}
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,10 @@ public class JwtService {
|
|||||||
return generateToken(new HashMap<>(), userDetails);
|
return generateToken(new HashMap<>(), userDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String generateRefreshToken(UserDetails userDetails) {
|
||||||
|
return generateRefreshToken(new HashMap<>(), userDetails);
|
||||||
|
}
|
||||||
|
|
||||||
public String generateToken(
|
public String generateToken(
|
||||||
Map<String, Object> extraClaims,
|
Map<String, Object> extraClaims,
|
||||||
UserDetails userDetails
|
UserDetails userDetails
|
||||||
@@ -45,6 +49,13 @@ public class JwtService {
|
|||||||
return buildToken(extraClaims, userDetails, jwtExpiration);
|
return buildToken(extraClaims, userDetails, jwtExpiration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String generateRefreshToken(
|
||||||
|
Map<String, Object> extraClaims,
|
||||||
|
UserDetails userDetails
|
||||||
|
) {
|
||||||
|
return buildToken(extraClaims, userDetails, refreshExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
private String buildToken(Map<String, Object> extraClaims, UserDetails userDetails, long expiration) {
|
private String buildToken(Map<String, Object> extraClaims, UserDetails userDetails, long expiration) {
|
||||||
return Jwts
|
return Jwts
|
||||||
.builder()
|
.builder()
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
package com.faf223.expensetrackerfaf.config;
|
package com.faf223.expensetrackerfaf.config;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
@@ -23,7 +35,7 @@ public class SecurityConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(csrf -> csrf.disable())
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers("/api/v1/auth/**").permitAll()
|
.requestMatchers("/api/v1/auth/**").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
@@ -31,7 +43,34 @@ public class SecurityConfiguration {
|
|||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authenticationProvider(authenticationProvider)
|
.authenticationProvider(authenticationProvider)
|
||||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // will be executed before UsernamePasswordAuthenticationFilter
|
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // will be executed before UsernamePasswordAuthenticationFilter
|
||||||
|
// .oauth2Login(Customizer.withDefaults());
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ClientRegistrationRepository clientRegistrationRepository(
|
||||||
|
@Value("${spring.security.oauth2.client.registration.google.client-id}") String clientId,
|
||||||
|
@Value("${spring.security.oauth2.client.registration.google.client-secret}") String clientSecret) {
|
||||||
|
|
||||||
|
ClientRegistration registration = ClientRegistration.withRegistrationId("google")
|
||||||
|
.clientId(clientId)
|
||||||
|
.clientSecret(clientSecret)
|
||||||
|
.clientName("Google")
|
||||||
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.redirectUri("http://localhost:8081/login/oauth2/code/{registrationId}")
|
||||||
|
.scope("openid", "profile", "email")
|
||||||
|
.authorizationUri("https://accounts.google.com/o/oauth2/auth")
|
||||||
|
.tokenUri("https://accounts.google.com/o/oauth2/token")
|
||||||
|
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
|
||||||
|
.userNameAttributeName(IdTokenClaimNames.SUB)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new InMemoryClientRegistrationRepository(registration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService() {
|
||||||
|
return new DefaultOAuth2UserService();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
package com.faf223.expensetrackerfaf.controller.auth;
|
package com.faf223.expensetrackerfaf.controller.auth;
|
||||||
|
|
||||||
import com.faf223.expensetrackerfaf.service.AuthenticationService;
|
import com.faf223.expensetrackerfaf.service.AuthenticationService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api/v1/auth")
|
@RequestMapping("api/v1/auth")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class AuthenticationController {
|
public class AuthenticationController {
|
||||||
|
|
||||||
private final AuthenticationService service;
|
private final AuthenticationService service;
|
||||||
|
|
||||||
public AuthenticationController(AuthenticationService service) {
|
|
||||||
this.service = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public ResponseEntity<AuthenticationResponse> register(@RequestBody RegisterRequest request) {
|
public ResponseEntity<AuthenticationResponse> register(@RequestBody RegisterRequest request) {
|
||||||
return ResponseEntity.ok(service.register(request));
|
return ResponseEntity.ok(service.register(request));
|
||||||
@@ -24,8 +22,8 @@ public class AuthenticationController {
|
|||||||
return ResponseEntity.ok(service.authenticate(request));
|
return ResponseEntity.ok(service.authenticate(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/refresh")
|
@PostMapping("/refreshtoken")
|
||||||
public ResponseEntity<AuthenticationResponse> refreshAccessToken(@RequestBody TokenRefreshRequest refreshRequest) {
|
public ResponseEntity<AuthenticationResponse> refreshAccessToken(@RequestBody TokenRefreshRequest request) {
|
||||||
return ResponseEntity.ok(service.refreshAccessToken(refreshRequest));
|
return ResponseEntity.ok(service.refreshAccessToken(request));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.faf223.expensetrackerfaf.controller.auth;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ErrorResponse {
|
||||||
|
private String error;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ErrorResponse(String error, String message) {
|
||||||
|
this.error = error;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,7 +44,7 @@ public class AuthenticationService {
|
|||||||
|
|
||||||
UserDetails userDetails = new PersonDetails(credential);
|
UserDetails userDetails = new PersonDetails(credential);
|
||||||
String jwtToken = jwtService.generateToken(userDetails);
|
String jwtToken = jwtService.generateToken(userDetails);
|
||||||
String refreshToken = jwtService.generateToken(userDetails);
|
String refreshToken = jwtService.generateRefreshToken(userDetails);
|
||||||
|
|
||||||
return AuthenticationResponse.builder()
|
return AuthenticationResponse.builder()
|
||||||
.accessToken(jwtToken)
|
.accessToken(jwtToken)
|
||||||
@@ -59,7 +59,7 @@ public class AuthenticationService {
|
|||||||
|
|
||||||
UserDetails userDetails = new PersonDetails(credential);
|
UserDetails userDetails = new PersonDetails(credential);
|
||||||
String jwtToken = jwtService.generateToken(userDetails);
|
String jwtToken = jwtService.generateToken(userDetails);
|
||||||
String refreshToken = jwtService.generateToken(userDetails);
|
String refreshToken = jwtService.generateRefreshToken(userDetails);
|
||||||
return AuthenticationResponse.builder()
|
return AuthenticationResponse.builder()
|
||||||
.accessToken(jwtToken)
|
.accessToken(jwtToken)
|
||||||
.refreshToken(refreshToken)
|
.refreshToken(refreshToken)
|
||||||
@@ -76,7 +76,7 @@ public class AuthenticationService {
|
|||||||
String jwtToken = jwtService.generateToken(userDetails);
|
String jwtToken = jwtService.generateToken(userDetails);
|
||||||
return AuthenticationResponse.builder()
|
return AuthenticationResponse.builder()
|
||||||
.accessToken(jwtToken)
|
.accessToken(jwtToken)
|
||||||
.refreshToken(refreshToken) // Return the same refresh token
|
.refreshToken(refreshToken)
|
||||||
.build();
|
.build();
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Invalid or expired refresh token");
|
throw new RuntimeException("Invalid or expired refresh token");
|
||||||
|
|||||||
13
src/main/java/com/faf223/expensetrackerfaf/web/.eslintignore
Normal file
13
src/main/java/com/faf223/expensetrackerfaf/web/.eslintignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
14
src/main/java/com/faf223/expensetrackerfaf/web/.eslintrc.cjs
Normal file
14
src/main/java/com/faf223/expensetrackerfaf/web/.eslintrc.cjs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
extraFileExtensions: ['.svelte']
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2017: true,
|
||||||
|
node: true
|
||||||
|
}
|
||||||
|
};
|
||||||
12
src/main/java/com/faf223/expensetrackerfaf/web/.gitignore
vendored
Normal file
12
src/main/java/com/faf223/expensetrackerfaf/web/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
.vercel
|
||||||
|
.output
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
1
src/main/java/com/faf223/expensetrackerfaf/web/.npmrc
Normal file
1
src/main/java/com/faf223/expensetrackerfaf/web/.npmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
engine-strict=true
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"pluginSearchDirs": ["."],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
||||||
6
src/main/java/com/faf223/expensetrackerfaf/web/README.md
Normal file
6
src/main/java/com/faf223/expensetrackerfaf/web/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# ExpenseTracker App
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Responsive flexbox dashboard made with Chart.js and Svelte
|
||||||
|
|
||||||
2491
src/main/java/com/faf223/expensetrackerfaf/web/package-lock.json
generated
Normal file
2491
src/main/java/com/faf223/expensetrackerfaf/web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
src/main/java/com/faf223/expensetrackerfaf/web/package.json
Normal file
33
src/main/java/com/faf223/expensetrackerfaf/web/package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "expensetracker",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"g": "vite dev -- --open",
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||||
|
"format": "prettier --plugin-search-dir . --write ."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@fontsource/fira-mono": "^4.5.10",
|
||||||
|
"@neoconfetti/svelte": "^1.0.0",
|
||||||
|
"@sveltejs/adapter-auto": "^2.0.0",
|
||||||
|
"@sveltejs/kit": "^1.20.4",
|
||||||
|
"eslint": "^8.28.0",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-plugin-svelte": "^2.30.0",
|
||||||
|
"prettier": "^2.8.0",
|
||||||
|
"prettier-plugin-svelte": "^2.10.1",
|
||||||
|
"svelte": "^4.0.5",
|
||||||
|
"vite": "^4.4.2"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/free-brands-svg-icons": "^6.4.2",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
|
"chart.js": "^4.4.0",
|
||||||
|
"email-validator": "^2.0.4",
|
||||||
|
"svelte-fa": "^3.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/com/faf223/expensetrackerfaf/web/src/app.html
Normal file
20
src/main/java/com/faf223/expensetrackerfaf/web/src/app.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<style>
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import LoginForm from './LoginForm.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="wrapper">
|
||||||
|
<LoginForm />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#wrapper {
|
||||||
|
background-color: #041721;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
<script>
|
||||||
|
import * as EmailValidator from 'email-validator';
|
||||||
|
|
||||||
|
let isErrorVisible = false;
|
||||||
|
let username, password;
|
||||||
|
let message = ""
|
||||||
|
|
||||||
|
function submitForm(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log("Tried to submit!");
|
||||||
|
console.log("Valid? ", (validateEmail() && validatePassword() ? "Yes" : "No"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateEmail() {
|
||||||
|
let valid = EmailValidator.validate(username);
|
||||||
|
isErrorVisible = valid ? false : true;
|
||||||
|
message = isErrorVisible ? "Invalid e-mail!" : "";
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePassword() {
|
||||||
|
let valid = password.value != '';
|
||||||
|
isErrorVisible = valid ? false : true;
|
||||||
|
message = isErrorVisible ? "Invalid password!" : "";
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="animated bounceInDown">
|
||||||
|
<div class="container">
|
||||||
|
{#if isErrorVisible}
|
||||||
|
<span class="error animated tada" id="msg">{message}</span>
|
||||||
|
{/if}
|
||||||
|
<form name="loginForm" class="loginForm" on:submit={submitForm}>
|
||||||
|
<h1 id="formTitle">Track<span>.io</span></h1>
|
||||||
|
<h5>Sign in to your account.</h5>
|
||||||
|
<input id="usernameInput" type="text" name="email" placeholder="Email or Username" autocomplete="off" on:input={
|
||||||
|
event => {username = event.target.value}
|
||||||
|
}>
|
||||||
|
<input id="passwordInput" type="password" name="password" placeholder="Password" autocomplete="off" on:input={
|
||||||
|
event => {password = event.target.password}
|
||||||
|
}>
|
||||||
|
<a href="/auth/recovery" class="recoveryPass">Forgot your password?</a>
|
||||||
|
<input type="submit" value="Sign in" class="submitButton">
|
||||||
|
</form>
|
||||||
|
<a href="/auth/register" class="noAccount">Don't have an account? Sign up</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400');
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0;
|
||||||
|
top: 50px;
|
||||||
|
left: 50%;
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background-color: rgb(33, 41, 66);
|
||||||
|
border-radius: 9px;
|
||||||
|
border-top: 10px solid #79a6fe;
|
||||||
|
border-bottom: 10px solid #8BD17C;
|
||||||
|
width: 400px;
|
||||||
|
height: 500px;
|
||||||
|
box-shadow: 1px 1px 108.8px 19.2px rgb(25, 31, 53);
|
||||||
|
}
|
||||||
|
|
||||||
|
#formTitle {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
color: #5c6bc0;
|
||||||
|
margin-top: 94px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#formTitle span {
|
||||||
|
color: #dfdeee;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginForm h5 {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #a1a4ad;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
margin-top: -15px;
|
||||||
|
margin-bottom: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginForm input[type="text"],
|
||||||
|
.loginForm input[type="password"] {
|
||||||
|
display: block;
|
||||||
|
margin: 20px auto;
|
||||||
|
background: #262e49;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 14px 10px;
|
||||||
|
width: 320px;
|
||||||
|
outline: none;
|
||||||
|
color: #d6d6d6;
|
||||||
|
-webkit-transition: all .2s ease-out;
|
||||||
|
-moz-transition: all .2s ease-out;
|
||||||
|
-ms-transition: all .2s ease-out;
|
||||||
|
-o-transition: all .2s ease-out;
|
||||||
|
transition: all .2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginForm input[type="text"]:focus,
|
||||||
|
.loginForm input[type="password"]:focus {
|
||||||
|
border: 1px solid #79A6FE;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #5c7fda;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submitButton {
|
||||||
|
border: 0;
|
||||||
|
background: #7f5feb;
|
||||||
|
color: #dfdeee;
|
||||||
|
border-radius: 100px;
|
||||||
|
width: 340px;
|
||||||
|
height: 49px;
|
||||||
|
font-size: 16px;
|
||||||
|
position: absolute;
|
||||||
|
top: 79%;
|
||||||
|
left: 8%;
|
||||||
|
transition: 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submitButton:hover {
|
||||||
|
background: #5d33e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recoveryPass {
|
||||||
|
position: relative;
|
||||||
|
float: right;
|
||||||
|
right: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noAccount {
|
||||||
|
position: absolute;
|
||||||
|
top: 92%;
|
||||||
|
left: 24%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
text-align: center;
|
||||||
|
width: 337px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 2px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 10px auto 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 31%;
|
||||||
|
left: 7.2%;
|
||||||
|
color: rgb(190, 67, 29);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import RegisterForm from './RegisterForm.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="wrapper">
|
||||||
|
<RegisterForm />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#wrapper {
|
||||||
|
background-color: #041721;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<script>
|
||||||
|
import * as EmailValidator from 'email-validator';
|
||||||
|
|
||||||
|
let isErrorVisible = false;
|
||||||
|
let username, email, password;
|
||||||
|
let message = ""
|
||||||
|
|
||||||
|
function submitForm(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log("Tried to submit!");
|
||||||
|
console.log("Valid? ", (validateEmail() && validateUsername() && va && validatePassword() ? "Yes" : "No"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateEmail() {
|
||||||
|
let valid = EmailValidator.validate(username);
|
||||||
|
isErrorVisible = valid ? false : true;
|
||||||
|
message = isErrorVisible ? "Invalid e-mail!" : "";
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePassword() {
|
||||||
|
let valid = password.value != '';
|
||||||
|
isErrorVisible = valid ? false : true;
|
||||||
|
message = isErrorVisible ? "Invalid password!" : "";
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateUsername() {
|
||||||
|
let valid = username.value != '';
|
||||||
|
isErrorVisible = valid ? false : true;
|
||||||
|
message = isErrorVisible ? "Invalid password!" : "";
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="animated bounceInDown">
|
||||||
|
<div class="container">
|
||||||
|
{#if isErrorVisible}
|
||||||
|
<span class="error animated tada" id="msg">{message}</span>
|
||||||
|
{/if}
|
||||||
|
<form name="registerForm" class="registerForm" on:submit={submitForm}>
|
||||||
|
<h1 id="formTitle">Track<span>.io</span></h1>
|
||||||
|
<h5>Sign up for a new account.</h5>
|
||||||
|
<input id="usernameInput" type="text" name="username" placeholder="Username" autocomplete="off" on:input={
|
||||||
|
event => {username = event.target.value}
|
||||||
|
}>
|
||||||
|
<input id="emailInput" type="text" name="email" placeholder="Email" autocomplete="off" on:input={
|
||||||
|
event => {email = event.target.value}
|
||||||
|
}>
|
||||||
|
<input id="passwordInput" type="password" name="password" placeholder="Password" autocomplete="off" on:input={
|
||||||
|
event => {password = event.target.password}
|
||||||
|
}>
|
||||||
|
<a href="/auth/recovery" class="recoveryPass">Forgot your password?</a>
|
||||||
|
<input type="submit" value="Sign up" class="submitButton">
|
||||||
|
</form>
|
||||||
|
<a href="/auth/login" class="noAccount">Already have an account? Sign in</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400');
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0;
|
||||||
|
top: 50px;
|
||||||
|
left: 50%;
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background-color: rgb(33, 41, 66);
|
||||||
|
border-radius: 9px;
|
||||||
|
border-top: 10px solid #79a6fe;
|
||||||
|
border-bottom: 10px solid #8BD17C;
|
||||||
|
width: 400px;
|
||||||
|
height: 600px;
|
||||||
|
box-shadow: 1px 1px 108.8px 19.2px rgb(25, 31, 53);
|
||||||
|
}
|
||||||
|
|
||||||
|
#formTitle {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
color: #5c6bc0;
|
||||||
|
margin-top: 94px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#formTitle span {
|
||||||
|
color: #dfdeee;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registerForm h5 {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #a1a4ad;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
margin-top: -15px;
|
||||||
|
margin-bottom: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registerForm input[type="text"],
|
||||||
|
.registerForm input[type="password"] {
|
||||||
|
display: block;
|
||||||
|
margin: 20px auto;
|
||||||
|
background: #262e49;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 14px 10px;
|
||||||
|
width: 320px;
|
||||||
|
outline: none;
|
||||||
|
color: #d6d6d6;
|
||||||
|
-webkit-transition: all .2s ease-out;
|
||||||
|
-moz-transition: all .2s ease-out;
|
||||||
|
-ms-transition: all .2s ease-out;
|
||||||
|
-o-transition: all .2s ease-out;
|
||||||
|
transition: all .2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registerForm input[type="text"]:focus,
|
||||||
|
.registerForm input[type="password"]:focus {
|
||||||
|
border: 1px solid #79A6FE;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #5c7fda;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submitButton {
|
||||||
|
border: 0;
|
||||||
|
background: #7f5feb;
|
||||||
|
color: #dfdeee;
|
||||||
|
border-radius: 100px;
|
||||||
|
width: 340px;
|
||||||
|
height: 49px;
|
||||||
|
font-size: 16px;
|
||||||
|
position: absolute;
|
||||||
|
top: 79%;
|
||||||
|
left: 8%;
|
||||||
|
transition: 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submitButton:hover {
|
||||||
|
background: #5d33e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recoveryPass {
|
||||||
|
position: relative;
|
||||||
|
float: right;
|
||||||
|
right: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noAccount {
|
||||||
|
position: absolute;
|
||||||
|
top: 92%;
|
||||||
|
left: 24%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
text-align: center;
|
||||||
|
width: 337px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 2px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 10px auto 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 31%;
|
||||||
|
left: 7.2%;
|
||||||
|
color: rgb(190, 67, 29);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import Dashboard from './board/Dashboard.svelte';
|
||||||
|
import SideMenu from './menu/SideMenu.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="wrapper">
|
||||||
|
<SideMenu />
|
||||||
|
<Dashboard />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400');
|
||||||
|
|
||||||
|
#wrapper {
|
||||||
|
background-color: rgb(23,34,51);
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="header">
|
||||||
|
<div id="dashboardTitleWrapper">
|
||||||
|
<h5>Hello, welcome to your</h5>
|
||||||
|
<h1 id="dashboardTitle">Dashboard</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="icons">
|
||||||
|
<div class="headerbtn searchButton">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="1.3em" viewBox="0 0 512 512"><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>
|
||||||
|
</div>
|
||||||
|
<div class="headerbtn notificationButton">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="1.3em" viewBox="0 0 448 512"><path d="M224 0c-17.7 0-32 14.3-32 32V49.9C119.5 61.4 64 124.2 64 200v33.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416H424c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6C399.5 322.9 384 278.8 384 233.4V200c0-75.8-55.5-138.6-128-150.1V32c0-17.7-14.3-32-32-32zm0 96h8c57.4 0 104 46.6 104 104v33.4c0 47.9 13.9 94.6 39.7 134.6H72.3C98.1 328 112 281.3 112 233.4V200c0-57.4 46.6-104 104-104h8zm64 352H224 160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7s18.7-28.3 18.7-45.3z"/></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
#header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dashboardTitleWrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin:20px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dashboardTitleWrapper h5 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dashboardTitleWrapper h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#icons {
|
||||||
|
display: flex;
|
||||||
|
margin-right:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerbtn {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerbtn:hover::before {
|
||||||
|
content: "";
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: rgb(100, 100, 100, 0.25);
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script>
|
||||||
|
import DashHeader from "./DashHeader.svelte";
|
||||||
|
import DataMenu from "./DataMenu.svelte";
|
||||||
|
import QuickInfobar from "./QuickInfobar.svelte";
|
||||||
|
import NotificationBoard from "./NotificationBoard.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="dashboard">
|
||||||
|
<DashHeader />
|
||||||
|
<QuickInfobar />
|
||||||
|
<DataMenu />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#dashboard {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
background-color: rgb(245,242,243);
|
||||||
|
border-radius: 20px;
|
||||||
|
margin: 20px;
|
||||||
|
min-width: 100px;
|
||||||
|
display: flex;
|
||||||
|
flex:1 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<script>
|
||||||
|
import Graph1 from './graphs/Graph1.svelte';
|
||||||
|
import Graph2 from './graphs/Graph2.svelte';
|
||||||
|
import Graph3 from './graphs/Graph3.svelte';
|
||||||
|
import Graph4 from './graphs/Graph4.svelte';
|
||||||
|
import Graph5 from './graphs/Graph5.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="dataMenu">
|
||||||
|
<div id="twoVertical">
|
||||||
|
<Graph1 />
|
||||||
|
<Graph2 />
|
||||||
|
</div>
|
||||||
|
<div id="oneVertical">
|
||||||
|
<Graph3 />
|
||||||
|
</div>
|
||||||
|
<div id="twoHorizontal">
|
||||||
|
<Graph4 />
|
||||||
|
<Graph5 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#dataMenu {
|
||||||
|
border-bottom-left-radius: 20px;
|
||||||
|
border-bottom-right-radius: 20px;
|
||||||
|
background-color: rgb(245,242,243);
|
||||||
|
display:flex;
|
||||||
|
padding:10px;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: stretch;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#twoVertical {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: stretch;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#oneVertical {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: stretch;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#twoHorizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: stretch;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="notificationBoard">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#notificationBoard {
|
||||||
|
display: none;
|
||||||
|
width:0;
|
||||||
|
height:0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="quickInfobar">
|
||||||
|
<div class="infobarElement">Income for this month 50$</div>
|
||||||
|
<div class="infobarElement">Expense for this month 40$</div>
|
||||||
|
<div class="infobarElement">Income for this month 50$</div>
|
||||||
|
<div class="infobarElement">Expense for this month 40$</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#quickInfobar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infobarElement {
|
||||||
|
margin: 10px;
|
||||||
|
width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #ffdde2;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||||
|
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||||
|
/* border: 1px solid black; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.infobarElement:hover {
|
||||||
|
/* color:white; */
|
||||||
|
/* background-color: black; */
|
||||||
|
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script>
|
||||||
|
import Chart from 'chart.js/auto';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let chartValues = [20, 10, 5, 2, 20, 30, 45];
|
||||||
|
let chartLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||||
|
let ctx;
|
||||||
|
let chartCanvas;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
ctx = chartCanvas.getContext('2d');
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: chartLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Revenue',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: chartValues
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="chart">
|
||||||
|
<canvas bind:this={chartCanvas}></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#chart {
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||||
|
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin:10px;
|
||||||
|
background-color: #ffdde2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chart:hover {
|
||||||
|
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<script>
|
||||||
|
import chartjs from 'chart.js/auto';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||||
|
var config = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||||
|
datasets: [{
|
||||||
|
fill: false,
|
||||||
|
data: [
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
3,
|
||||||
|
4
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
label: "My Second dataset ",
|
||||||
|
fill: false,
|
||||||
|
data: [
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
3,
|
||||||
|
4
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title:{
|
||||||
|
display:true,
|
||||||
|
text: "Chart.js Line Chart - Animation Progress Bar"
|
||||||
|
},
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let chartValues = [20, 10, 5, 2, 20, 30, 45];
|
||||||
|
let chartLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||||
|
let ctx;
|
||||||
|
let chartCanvas;
|
||||||
|
|
||||||
|
onMount(async (promise) => {
|
||||||
|
ctx = chartCanvas.getContext('2d');
|
||||||
|
var chart = new chartjs(ctx, config);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="chart">
|
||||||
|
<canvas bind:this={chartCanvas}></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#chart {
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||||
|
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin:10px;
|
||||||
|
background-color: #ffdde2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chart:hover {
|
||||||
|
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<script>
|
||||||
|
import chartjs from 'chart.js/auto';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
|
||||||
|
var randomScalingFactor = function() {
|
||||||
|
return Math.round(Math.random() * 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
datasets: [{
|
||||||
|
data: [
|
||||||
|
randomScalingFactor(),
|
||||||
|
randomScalingFactor(),
|
||||||
|
randomScalingFactor(),
|
||||||
|
randomScalingFactor(),
|
||||||
|
randomScalingFactor(),
|
||||||
|
],
|
||||||
|
label: 'Dataset 1'
|
||||||
|
}],
|
||||||
|
labels: [
|
||||||
|
"Red",
|
||||||
|
"Orange",
|
||||||
|
"Yellow",
|
||||||
|
"Green",
|
||||||
|
"Blue"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let ctx;
|
||||||
|
let chartCanvas;
|
||||||
|
|
||||||
|
onMount(async (promise) => {
|
||||||
|
ctx = chartCanvas.getContext('2d');
|
||||||
|
var chart = new chartjs(ctx, config);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="chart">
|
||||||
|
<canvas bind:this={chartCanvas}></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#chart {
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||||
|
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin:10px;
|
||||||
|
background-color: #ffdde2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chart:hover {
|
||||||
|
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script>
|
||||||
|
import chartjs from 'chart.js/auto';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let chartValues = [20, 10, 5, 2, 20, 30, 45];
|
||||||
|
let chartLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||||
|
let ctx;
|
||||||
|
let chartCanvas;
|
||||||
|
|
||||||
|
onMount(async (promise) => {
|
||||||
|
ctx = chartCanvas.getContext('2d');
|
||||||
|
var chart = new chartjs(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: chartLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Revenue',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: chartValues
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="chart">
|
||||||
|
<canvas bind:this={chartCanvas}></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#chart {
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||||
|
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin:10px;
|
||||||
|
background-color: #ffdde2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chart:hover {
|
||||||
|
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script>
|
||||||
|
import chartjs from 'chart.js/auto';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let chartValues = [20, 10, 5, 2, 20, 30, 45];
|
||||||
|
let chartLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
||||||
|
let ctx;
|
||||||
|
let chartCanvas;
|
||||||
|
|
||||||
|
onMount(async (promise) => {
|
||||||
|
ctx = chartCanvas.getContext('2d');
|
||||||
|
var chart = new chartjs(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: chartLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Revenue',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: chartValues
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="chart">
|
||||||
|
<canvas bind:this={chartCanvas}></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#chart {
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||||
|
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin:10px;
|
||||||
|
background-color: #ffdde2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chart:hover {
|
||||||
|
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="navbar">
|
||||||
|
<div id="profilePic">PROFILEPIC</div>
|
||||||
|
<div class="menuItem" on:click={() => console.log("help clicked")}>Help</div>
|
||||||
|
<div class="menuItem" on:click={() => console.log("contact clicked")}>Contact</div>
|
||||||
|
<div class="menuItem" on:click={() => console.log("settings clicked")}>Settings</div>
|
||||||
|
<div class="menuItem" on:click={() => console.log("logout clicked")}>Logout</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#navbar {
|
||||||
|
padding:0;
|
||||||
|
background-color: rgb(33, 41, 66);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
color:white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
text-align: center;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem:hover {
|
||||||
|
background-color: rgb(45, 60, 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
#profilePic {
|
||||||
|
margin-right:auto;
|
||||||
|
display: flex;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
align-items: center;
|
||||||
|
color:white;
|
||||||
|
justify-self: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="sideMenu">
|
||||||
|
<div id="iconSpace">
|
||||||
|
<div id="icon">
|
||||||
|
<img id="iconImg" src='./../../../src/lib/images/adidas.png' alt="icon"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="menuSpace">
|
||||||
|
<div class="sideMenuItem">
|
||||||
|
<svg class="svgimg" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"/></svg>
|
||||||
|
<span class="sideMenuItemText">Profile</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sideMenuItem">
|
||||||
|
<svg class="svgimg" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 576 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 64C28.7 64 0 92.7 0 128V384c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H64zm64 320H64V320c35.3 0 64 28.7 64 64zM64 192V128h64c0 35.3-28.7 64-64 64zM448 384c0-35.3 28.7-64 64-64v64H448zm64-192c-35.3 0-64-28.7-64-64h64v64zM288 160a96 96 0 1 1 0 192 96 96 0 1 1 0-192z"/></svg>
|
||||||
|
<span class="sideMenuItemText">Expenses</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sideMenuItem">
|
||||||
|
<svg class="svgimg" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 576 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 64C28.7 64 0 92.7 0 128V384c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H64zm64 320H64V320c35.3 0 64 28.7 64 64zM64 192V128h64c0 35.3-28.7 64-64 64zM448 384c0-35.3 28.7-64 64-64v64H448zm64-192c-35.3 0-64-28.7-64-64h64v64zM288 160a96 96 0 1 1 0 192 96 96 0 1 1 0-192z"/></svg>
|
||||||
|
<span class="sideMenuItemText">Incomes</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sideMenuItem">
|
||||||
|
<svg class="svgimg" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 576 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 64C28.7 64 0 92.7 0 128V384c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H64zm64 320H64V320c35.3 0 64 28.7 64 64zM64 192V128h64c0 35.3-28.7 64-64 64zM448 384c0-35.3 28.7-64 64-64v64H448zm64-192c-35.3 0-64-28.7-64-64h64v64zM288 160a96 96 0 1 1 0 192 96 96 0 1 1 0-192z"/></svg>
|
||||||
|
<span class="sideMenuItemText">General</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sideMenuItem">
|
||||||
|
<svg class="svgimg" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/></svg>
|
||||||
|
<span class="sideMenuItemText">Settings</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="profileSpace">
|
||||||
|
<div id="profileInfo">Profile Info</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#sideMenu {
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-width: 150px;
|
||||||
|
max-width: 200px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#iconSpace {
|
||||||
|
margin-top:20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideMenuItem {
|
||||||
|
min-height: 50px;
|
||||||
|
color:white;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideMenuItem:hover {
|
||||||
|
background-color: rgb(45, 60, 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideMenuItemText {
|
||||||
|
padding:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svgimg {
|
||||||
|
fill:white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#iconImg {
|
||||||
|
max-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#profileSpace {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
color:white;
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import adapter from '@sveltejs/adapter-auto';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
kit: {
|
||||||
|
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||||
|
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||||
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()]
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user