diff --git a/docker/startup.sh b/docker/startup.sh
index 19336ff..5953e49 100644
--- a/docker/startup.sh
+++ b/docker/startup.sh
@@ -1,5 +1,15 @@
#!/bin/bash
+echo "> Configure cache"
+ mkdir -p /tmp/app/{vendor/node_modules}
+ ln -s /tmp/app/node_modules /web/frontend/node_modules
+ ln -s /tmp/app/vendor /web/backend/vendor
+
+echo "> Configure user"
+ useradd developer
+ chown developer:developer /web/frontend -R
+ chown developer:developer /web/backend -R
+
echo "> Configure logs"
mkdir -p /var/log/{apache2,nginx,pgadmin,postgresql}
chown postgres:adm /var/log/postgresql -R
@@ -39,7 +49,7 @@ echo "> Configure symfony"
echo "> Configure angular"
cd /web/frontend
- npm ci
+ su developer -c 'time npm ci'
echo "> Starting angular"
- ng serve --disable-host-check --host 0.0.0.0 --poll
+ su developer -c 'ng serve --disable-host-check --host 0.0.0.0 --poll'
diff --git a/src/frontend/angular.json b/src/frontend/angular.json
index 08083a6..732b756 100644
--- a/src/frontend/angular.json
+++ b/src/frontend/angular.json
@@ -48,7 +48,17 @@
"src/assets"
],
"styles": [
- "src/styles.scss"
+ "src/styles.scss",
+ {
+ "input": "src/app/styles/light.scss",
+ "bundleName": "light",
+ "inject": false
+ },
+ {
+ "input": "src/app/styles/dark.scss",
+ "bundleName": "dark",
+ "inject": false
+ }
],
"scripts": [],
"vendorChunk": true,
diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
index f586e94..e77112f 100644
--- a/src/frontend/package-lock.json
+++ b/src/frontend/package-lock.json
@@ -291,6 +291,23 @@
"tslib": "^2.2.0"
}
},
+ "@angular/cdk": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.1.3.tgz",
+ "integrity": "sha512-uCWHk/PjddNJsdrmexasphWGbf4kYtYyhUCSd4HEBrIDjYz166MTVSr3FHgn/s8/tlVou7uTnaEZM+ILWoe2iQ==",
+ "requires": {
+ "parse5": "^5.0.0",
+ "tslib": "^2.2.0"
+ },
+ "dependencies": {
+ "parse5": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+ "optional": true
+ }
+ }
+ },
"@angular/cli": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.1.3.tgz",
@@ -491,6 +508,14 @@
"tslib": "^2.2.0"
}
},
+ "@angular/material": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-12.1.3.tgz",
+ "integrity": "sha512-ekkg3MDEN133NhcE0du7a69lFAIFyvK6Va+YRTxYQE0BCNVMTWXtGbOXZLovDMjgo1xdXwrEJrMRBeYo+FIttA==",
+ "requires": {
+ "tslib": "^2.2.0"
+ }
+ },
"@angular/platform-browser": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.1.3.tgz",
@@ -1699,6 +1724,22 @@
"enhanced-resolve": "5.8.2"
}
},
+ "@ngx-translate/core": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-13.0.0.tgz",
+ "integrity": "sha512-+tzEp8wlqEnw0Gc7jtVRAJ6RteUjXw6JJR4O65KlnxOmJrCGPI0xjV/lKRnQeU0w4i96PQs/jtpL921Wrb7PWg==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
+ "@ngx-translate/http-loader": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-6.0.0.tgz",
+ "integrity": "sha512-LCekn6qCbeXWlhESCxU1rAbZz33WzDG0lI7Ig0pYC1o5YxJWrkU9y3Y4tNi+jakQ7R6YhTR2D3ox6APxDtA0wA==",
+ "requires": {
+ "tslib": "^2.0.0"
+ }
+ },
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2725,6 +2766,16 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
+ "bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
"bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
@@ -2819,6 +2870,11 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
+ "bootstrap": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz",
+ "integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q=="
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -4978,6 +5034,13 @@
"escape-string-regexp": "^1.0.5"
}
},
+ "file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "dev": true,
+ "optional": true
+ },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -5075,6 +5138,11 @@
"integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==",
"dev": true
},
+ "font-awesome": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
+ "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
+ },
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -7430,6 +7498,13 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
+ "nan": {
+ "version": "2.14.2",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
+ "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
+ "dev": true,
+ "optional": true
+ },
"nanoid": {
"version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
@@ -12599,7 +12674,11 @@
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"dev": true,
- "optional": true
+ "optional": true,
+ "requires": {
+ "bindings": "^1.5.0",
+ "nan": "^2.12.1"
+ }
},
"glob-parent": {
"version": "3.1.0",
diff --git a/src/frontend/package.json b/src/frontend/package.json
index 80d28eb..b9be3ea 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -12,13 +12,19 @@
"private": true,
"dependencies": {
"@angular/animations": "~12.1.3",
+ "@angular/cdk": "^12.1.3",
"@angular/common": "~12.1.3",
"@angular/compiler": "~12.1.3",
"@angular/core": "~12.1.3",
"@angular/forms": "~12.1.3",
+ "@angular/material": "^12.1.3",
"@angular/platform-browser": "~12.1.3",
"@angular/platform-browser-dynamic": "~12.1.3",
"@angular/router": "~12.1.3",
+ "@ngx-translate/core": "^13.0.0",
+ "@ngx-translate/http-loader": "^6.0.0",
+ "bootstrap": "^5.0.2",
+ "font-awesome": "^4.7.0",
"rxjs": "~6.5.5",
"tslib": "^2.0.0",
"zone.js": "~0.11.4"
@@ -27,9 +33,9 @@
"@angular-devkit/build-angular": "~12.1.3",
"@angular/cli": "~12.1.3",
"@angular/compiler-cli": "~12.1.3",
- "@types/node": "^12.11.1",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
+ "@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
diff --git a/src/frontend/shell b/src/frontend/shell
index a2468e8..7ad2572 100644
--- a/src/frontend/shell
+++ b/src/frontend/shell
@@ -1 +1,3 @@
-docker.exe compose -p curenet exec app bash -c 'cd /web/frontend && bash'
\ No newline at end of file
+cd ../../docker
+docker.exe compose -p curenet exec app bash -c 'cd /web/frontend && bash'
+cd ../src/frontend
\ No newline at end of file
diff --git a/src/frontend/src/app/app-routing.module.ts b/src/frontend/src/app/app-routing.module.ts
index d425c6f..8458c35 100644
--- a/src/frontend/src/app/app-routing.module.ts
+++ b/src/frontend/src/app/app-routing.module.ts
@@ -1,10 +1,102 @@
-import { NgModule } from '@angular/core';
-import { Routes, RouterModule } from '@angular/router';
-
-const routes: Routes = [];
-
-@NgModule({
- imports: [RouterModule.forRoot(routes)],
- exports: [RouterModule]
-})
-export class AppRoutingModule { }
+import { NgModule } from '@angular/core';
+import { Route, RouterModule } from '@angular/router';
+import { NotFoundComponent } from './modules/shared/components/not-found/not-found.component';
+
+interface AppMenuEntry {
+ label: string;
+ icon: string;
+ path?: string;
+ matchExact?: boolean;
+ level?: number;
+}
+
+interface AppRoute extends Route {
+ menuEntries?: AppMenuEntry[];
+}
+
+export const appRoutes: AppRoute[] = [
+ {
+ path: '',
+ component: NotFoundComponent,
+ menuEntries: [
+ {
+ label: 'MENU.HOME',
+ icon: 'fa fa-home',
+ matchExact: true,
+ },
+ ],
+ },
+ {
+ path: 'profile',
+ component: NotFoundComponent,
+ menuEntries: [
+ {
+ label: 'MENU.PROFILE',
+ icon: 'fa fa-user',
+ },
+ ],
+ },
+ {
+ path: 'community',
+ component: NotFoundComponent,
+ menuEntries: [
+ {
+ label: 'MENU.COMMUNITY',
+ icon: 'fa fa-users',
+ },
+ ],
+ },
+ {
+ path: 'messages',
+ component: NotFoundComponent,
+ menuEntries: [
+ {
+ label: 'MENU.MESSAGES',
+ icon: 'fa fa-comments',
+ },
+ ],
+ },
+ {
+ path: 'tests',
+ component: NotFoundComponent,
+ menuEntries: [
+ {
+ label: 'MENU.TESTS',
+ icon: 'fa fa-heartbeat',
+ },
+ ],
+ },
+ {
+ path: 'research',
+ component: NotFoundComponent,
+ menuEntries: [
+ {
+ label: 'MENU.RESEARCH',
+ icon: 'fa fa-flask',
+ },
+ ],
+ },
+ {
+ path: 'admin',
+ loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule),
+ menuEntries: [
+ {
+ label: 'MENU.ADMIN',
+ icon: 'fa fa-id-card',
+ matchExact: true,
+ },
+ {
+ path: 'admin/users',
+ label: 'MENU.USERS',
+ icon: 'fa fa-users',
+ level: 1,
+ }
+ ],
+ },
+];
+
+@NgModule({
+ imports: [RouterModule.forRoot(appRoutes)],
+ exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git a/src/frontend/src/app/app.component.html b/src/frontend/src/app/app.component.html
index d06582e..175f57e 100644
--- a/src/frontend/src/app/app.component.html
+++ b/src/frontend/src/app/app.component.html
@@ -1,2 +1,73 @@
-
-It's works, you can start coding :)
\ No newline at end of file
+
+
+
+
+ {{ 'APP.NAME' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/frontend/src/app/app.component.scss b/src/frontend/src/app/app.component.scss
index e69de29..844bf98 100644
--- a/src/frontend/src/app/app.component.scss
+++ b/src/frontend/src/app/app.component.scss
@@ -0,0 +1,91 @@
+$sidebar-width: 250px;
+:host{
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+.menu{
+ width: $sidebar-width;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ > :first-child{
+ flex-grow: 1;
+ }
+ > * {
+ width: $sidebar-width;
+ }
+}
+.avatar{
+ width: 48px;
+ height: 48px;
+ border-radius: 10px;
+ background-color: rgba(0,0,0, 0.25);
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: cover;
+}
+.menu-item{
+ display: flex;
+ position: relative;
+ cursor: pointer;
+ padding: 10px;
+ transition-duration: 400ms !important;
+ transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !important;
+ transition-property: all !important;
+ &:after{
+ display: block;
+ content: "";
+ position: absolute;
+ top: 50%;
+ right: 0px;
+ width: 3px;
+ height: 0%;
+ background: var(--primary-color);
+ transition-duration: 400ms !important;
+ transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !important;
+ transition-property: all !important;
+ }
+ &.active{
+ &:after{
+ top: 0%;
+ height: 100%;
+ }
+ }
+ i {
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 10px;
+ transition: all 0.4s ease;
+ }
+ span{
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+ opacity: 0.75;
+ }
+}
+.user-box{
+ padding: 6px;
+}
+.user-menu-container{
+ width: 46px;
+ .user-menu{
+ position: absolute;
+ bottom: 6px;
+ right: 6px;
+ height: 48px;
+ width: 46px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+}
\ No newline at end of file
diff --git a/src/frontend/src/app/app.component.ts b/src/frontend/src/app/app.component.ts
index c6a5451..446080d 100644
--- a/src/frontend/src/app/app.component.ts
+++ b/src/frontend/src/app/app.component.ts
@@ -1,10 +1,127 @@
-import { Component } from '@angular/core';
-
-@Component({
- selector: 'app-root',
- templateUrl: './app.component.html',
- styleUrls: ['./app.component.scss']
-})
-export class AppComponent {
- title = 'CureNet';
-}
+import { Component } from '@angular/core';
+import { FormControl } from '@angular/forms';
+import { appRoutes } from './app-routing.module';
+import { AppService, WindowSize } from './modules/shared/services/app/app.service';
+import { BrowserStorageService } from './modules/shared/services/browser-storage/browser-storage.service';
+import { ThemeService } from './modules/shared/services/theme/theme.service';
+import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
+
+enum SidebarOpenEnum {
+ Default,
+ Open,
+ Closed,
+}
+@Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss']
+})
+export class AppComponent {
+ isMobile = true;
+ title = 'CureNet';
+ sidebarOpen = SidebarOpenEnum.Default;
+ sidebarMode = 'over';
+ defaultSidebarOpen = false;
+ lang: string;
+ theme: string;
+ langs: string[] = [];
+ themes: string[] = [];
+ themeControl = new FormControl();
+ langControl = new FormControl();
+ isDark = true;
+ appRoutes = appRoutes;
+ user = {
+ name: 'Name',
+ surname: 'Surname',
+ avatar: 'https://www.gravatar.com/avatar/81b206a89f89d5b1123b87606075c6a8?s=514&d=robohash',
+ };
+
+ get isSidebarOpen() {
+ switch (this.sidebarOpen) {
+ case SidebarOpenEnum.Open:
+ return true;
+ break;
+ case SidebarOpenEnum.Closed:
+ return false;
+ break;
+ default:
+ return this.defaultSidebarOpen;
+ break;
+ }
+ }
+
+ constructor(
+ appService: AppService,
+ themeService: ThemeService,
+ private browserStorageService: BrowserStorageService,
+ private sanitizer: DomSanitizer,
+ ) {
+ this.themes = themeService.getThemes();
+ this.langs = appService.getLangs();
+ this.lang = appService.getLang();
+
+ this.themeControl.valueChanges.subscribe(theme => {
+ themeService.setTheme(theme);
+ });
+ appService.changeLang(this.lang);
+ this.langControl.valueChanges.subscribe(lang => {
+ appService.changeLang(lang);
+ });
+ this.onThemeChanged(themeService.getTheme());
+ themeService.themeChange.subscribe(theme => {
+ this.onThemeChanged(theme);
+ });
+ this.onResize(appService.size);
+ appService.sizeChange.subscribe(size => {
+ this.onResize(size);
+ });
+ this.onLangChanged(appService.getLang());
+ appService.langChange.subscribe(lang => {
+ this.onLangChanged(lang);
+ });
+
+ const sidebarUserPrefference = this.browserStorageService.getItem('sidebar.open');
+ if (sidebarUserPrefference !== null) {
+ this.sidebarOpen = sidebarUserPrefference;
+ }
+ }
+
+ onThemeChanged(theme: string) {
+ if (theme === undefined) {
+ return;
+ }
+ this.theme = theme;
+ this.themeControl.setValue(theme);
+ this.isDark = theme.indexOf('dark') !== -1;
+ }
+
+ onResize(size: WindowSize) {
+ if (size === undefined) {
+ return;
+ }
+ this.isMobile = size.isMobile;
+ this.defaultSidebarOpen = !size.isMobile;
+ this.sidebarMode = this.isMobile ? 'over' : 'side';
+ }
+
+ onLangChanged(lang: string) {
+ if (lang === undefined) {
+ return;
+ }
+ this.lang = lang;
+ this.langControl.setValue(lang);
+ }
+
+ toggleSidebar() {
+ this.sidebarOpen = this.isSidebarOpen ? SidebarOpenEnum.Closed : SidebarOpenEnum.Open;
+ this.browserStorageService.setItem('sidebar.open', this.sidebarOpen);
+ }
+
+ onSidebarOpenedChange(opened: boolean) {
+ this.sidebarOpen = opened ? SidebarOpenEnum.Open : SidebarOpenEnum.Closed;
+ }
+
+ url(url: string): SafeStyle {
+ return this.sanitizer.bypassSecurityTrustStyle(`url('${url}')`);
+ }
+}
\ No newline at end of file
diff --git a/src/frontend/src/app/app.module.ts b/src/frontend/src/app/app.module.ts
index 2c3ba29..6b83379 100644
--- a/src/frontend/src/app/app.module.ts
+++ b/src/frontend/src/app/app.module.ts
@@ -1,18 +1,47 @@
-import { BrowserModule } from '@angular/platform-browser';
-import { NgModule } from '@angular/core';
-
-import { AppRoutingModule } from './app-routing.module';
-import { AppComponent } from './app.component';
-
-@NgModule({
- declarations: [
- AppComponent
- ],
- imports: [
- BrowserModule,
- AppRoutingModule
- ],
- providers: [],
- bootstrap: [AppComponent]
-})
-export class AppModule { }
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+import { HttpClientModule, HttpClient } from '@angular/common/http';
+import { AppRoutingModule } from './app-routing.module';
+import { AppComponent } from './app.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { MaterialModule } from './modules/material/material.module';
+import { AppService } from './modules/shared/services/app/app.service';
+import { ThemeService } from './modules/shared/services/theme/theme.service';
+import { BrowserStorageService } from './modules/shared/services/browser-storage/browser-storage.service';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { TranslateHttpLoader } from '@ngx-translate/http-loader';
+
+export function HttpLoaderFactory(http: HttpClient) {
+ return new TranslateHttpLoader(http, '/assets/lang/', '.json');
+}
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ ],
+ imports: [
+ BrowserModule,
+ AppRoutingModule,
+ BrowserAnimationsModule,
+ MaterialModule,
+ FormsModule,
+ ReactiveFormsModule,
+ HttpClientModule,
+ TranslateModule.forRoot({
+ defaultLanguage: 'en',
+ loader: {
+ provide: TranslateLoader,
+ useFactory: HttpLoaderFactory,
+ deps: [HttpClient]
+ }
+ }),
+ ],
+ providers: [
+ AppService,
+ ThemeService,
+ BrowserStorageService,
+ ],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/src/frontend/src/app/modules/admin/admin-routing.module.ts b/src/frontend/src/app/modules/admin/admin-routing.module.ts
new file mode 100644
index 0000000..af0b82e
--- /dev/null
+++ b/src/frontend/src/app/modules/admin/admin-routing.module.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Route } from '@angular/router';
+import { NotFoundComponent } from '../shared/components/not-found/not-found.component';
+
+const routes: Route[] = [
+ {
+ path: '',
+ component: NotFoundComponent,
+ },
+ {
+ path: 'users',
+ component: NotFoundComponent,
+ },
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class AdminRoutingModule { }
diff --git a/src/frontend/src/app/modules/admin/admin.module.ts b/src/frontend/src/app/modules/admin/admin.module.ts
new file mode 100644
index 0000000..6261d17
--- /dev/null
+++ b/src/frontend/src/app/modules/admin/admin.module.ts
@@ -0,0 +1,16 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { AdminRoutingModule } from './admin-routing.module';
+import { SharedModule } from '../shared/shared.module';
+
+
+@NgModule({
+ declarations: [],
+ imports: [
+ CommonModule,
+ AdminRoutingModule,
+ SharedModule,
+ ]
+})
+export class AdminModule { }
diff --git a/src/frontend/src/app/modules/material/material.module.ts b/src/frontend/src/app/modules/material/material.module.ts
new file mode 100644
index 0000000..99357e6
--- /dev/null
+++ b/src/frontend/src/app/modules/material/material.module.ts
@@ -0,0 +1,27 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import {MatToolbarModule} from '@angular/material/toolbar';
+import {MatButtonModule} from '@angular/material/button';
+import {MatSidenavModule} from '@angular/material/sidenav';
+import {MatSelectModule} from '@angular/material/select';
+import {MatMenuModule} from '@angular/material/menu';
+
+const itemsToExport = [
+ MatToolbarModule,
+ MatButtonModule,
+ MatSidenavModule,
+ MatSelectModule,
+ MatMenuModule,
+];
+
+@NgModule({
+ declarations: [],
+ imports: [
+ CommonModule,
+ ...itemsToExport,
+ ],
+ exports: [
+ ...itemsToExport,
+ ]
+})
+export class MaterialModule { }
diff --git a/src/frontend/src/app/modules/shared/components/not-found/not-found.component.html b/src/frontend/src/app/modules/shared/components/not-found/not-found.component.html
new file mode 100644
index 0000000..a8a991b
--- /dev/null
+++ b/src/frontend/src/app/modules/shared/components/not-found/not-found.component.html
@@ -0,0 +1,3 @@
+
+ {{ 'ERROR.PAGE_NOT_FOUND' | translate }}
+
\ No newline at end of file
diff --git a/src/frontend/src/app/modules/shared/components/not-found/not-found.component.scss b/src/frontend/src/app/modules/shared/components/not-found/not-found.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/frontend/src/app/modules/shared/components/not-found/not-found.component.ts b/src/frontend/src/app/modules/shared/components/not-found/not-found.component.ts
new file mode 100644
index 0000000..7cb4124
--- /dev/null
+++ b/src/frontend/src/app/modules/shared/components/not-found/not-found.component.ts
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-not-found',
+ templateUrl: './not-found.component.html',
+ styleUrls: ['./not-found.component.scss']
+})
+export class NotFoundComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/src/frontend/src/app/modules/shared/services/app/app.service.ts b/src/frontend/src/app/modules/shared/services/app/app.service.ts
new file mode 100644
index 0000000..40ae29e
--- /dev/null
+++ b/src/frontend/src/app/modules/shared/services/app/app.service.ts
@@ -0,0 +1,72 @@
+import { Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { fromEvent, Subject } from 'rxjs';
+import { BrowserStorageService } from '../browser-storage/browser-storage.service';
+
+export class WindowSize {
+ isMobile: boolean;
+
+ constructor(
+ public width: number,
+ public height: number,
+ ) {
+ this.isMobile = this.width <= 700;
+ }
+
+ update() {
+ this.width = window.innerWidth;
+ this.height = window.innerHeight;
+ }
+
+ static generate() {
+ return new WindowSize(window.innerWidth, window.innerHeight);
+ }
+}
+
+@Injectable()
+export class AppService {
+ private lang = 'en';
+ private defaultLang = 'en';
+ langChange = new Subject();
+ size: WindowSize;
+ sizeChange = new Subject();
+
+ constructor(
+ browserStorageService: BrowserStorageService,
+ private translate: TranslateService,
+ ) {
+ this.langChange.subscribe(lang => {
+ this.lang = lang;
+ browserStorageService.setItem('language', lang);
+ });
+ this.sizeChange.subscribe(size => this.size = size);
+ this.sizeChange.next(WindowSize.generate());
+ let lang = browserStorageService.getItem('language');
+ if (!lang) {
+ lang = this.defaultLang;
+ }
+ this.translate.setDefaultLang(this.defaultLang);
+ this.changeLang(lang);
+ fromEvent(window, 'resize').subscribe(e => {
+ this.sizeChange.next(WindowSize.generate());
+ })
+ }
+
+ changeLang(lang: string) {
+ if (lang !== this.lang) {
+ this.translate.use(lang);
+ this.langChange.next(lang);
+ }
+ }
+
+ getLang() {
+ return this.lang;
+ }
+
+ getLangs() {
+ return [
+ 'pl',
+ 'en',
+ ];
+ }
+}
diff --git a/src/frontend/src/app/modules/shared/services/browser-storage/browser-storage.service.ts b/src/frontend/src/app/modules/shared/services/browser-storage/browser-storage.service.ts
new file mode 100644
index 0000000..5ed4972
--- /dev/null
+++ b/src/frontend/src/app/modules/shared/services/browser-storage/browser-storage.service.ts
@@ -0,0 +1,37 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class BrowserStorageService {
+
+ private storage: Storage;
+ private namespace: string;
+ constructor() {
+ this.storage = localStorage;
+ this.namespace = 'default';
+ }
+
+ setNamespace(namespace: string) {
+ this.namespace = namespace;
+ }
+
+ getNamespace(namespace: string | null = null): string {
+ return namespace ?? this.namespace;
+ }
+
+ setItem(key: string, value: any, namespace: string | null = null) {
+ namespace = this.getNamespace(namespace);
+ const path = `${namespace}.${key}`;
+ this.storage.setItem(path, JSON.stringify(value));
+ }
+
+ getItem(key: string, namespace: string | null = null): any {
+ namespace = this.getNamespace(namespace);
+ const path = `${namespace}.${key}`;
+ return JSON.parse(this.storage.getItem(path) ?? 'null');
+ }
+
+ getItemOrDefault(key: string, defaultValue: any, namespace: string | null = null): any {
+ return this.getItem(key, namespace) ?? defaultValue;
+ }
+
+}
diff --git a/src/frontend/src/app/modules/shared/services/theme/theme.service.ts b/src/frontend/src/app/modules/shared/services/theme/theme.service.ts
new file mode 100644
index 0000000..080d21c
--- /dev/null
+++ b/src/frontend/src/app/modules/shared/services/theme/theme.service.ts
@@ -0,0 +1,41 @@
+import { Injectable } from '@angular/core';
+import { Subject } from 'rxjs';
+import { BrowserStorageService } from '../browser-storage/browser-storage.service';
+
+@Injectable()
+export class ThemeService {
+ private theme: string;
+ themeChange = new Subject();
+
+ constructor(
+ browserStorageService: BrowserStorageService
+ ) {
+ this.themeChange.subscribe(theme => {
+ this.theme = theme;
+ browserStorageService.setItem('current-theme', theme);
+ });
+ let userTheme = browserStorageService.getItem('current-theme');
+ if (!userTheme) {
+ userTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ }
+ this.setTheme(userTheme);
+ }
+
+ getTheme() {
+ return this.theme;
+ }
+
+ setTheme(theme: string) {
+ if (theme !== this.theme) {
+ this.themeChange.next(theme);
+ document.getElementById('current-theme').setAttribute('href', `/${theme}.css`);
+ }
+ }
+
+ getThemes(): string[] {
+ return [
+ 'light',
+ 'dark',
+ ];
+ };
+}
diff --git a/src/frontend/src/app/modules/shared/shared.module.ts b/src/frontend/src/app/modules/shared/shared.module.ts
new file mode 100644
index 0000000..344df7e
--- /dev/null
+++ b/src/frontend/src/app/modules/shared/shared.module.ts
@@ -0,0 +1,24 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { NotFoundComponent } from './components/not-found/not-found.component';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { HttpLoaderFactory } from 'src/app/app.module';
+import { HttpClient } from '@angular/common/http';
+
+@NgModule({
+ declarations: [
+ NotFoundComponent
+ ],
+ imports: [
+ CommonModule,
+ TranslateModule.forRoot({
+ defaultLanguage: 'en',
+ loader: {
+ provide: TranslateLoader,
+ useFactory: HttpLoaderFactory,
+ deps: [HttpClient]
+ }
+ }),
+ ],
+})
+export class SharedModule { }
diff --git a/src/frontend/src/app/styles/_base.scss b/src/frontend/src/app/styles/_base.scss
new file mode 100644
index 0000000..300670b
--- /dev/null
+++ b/src/frontend/src/app/styles/_base.scss
@@ -0,0 +1,6 @@
+@use 'sass:map';
+
+$primary-color: map.get($theme, color, primary, 500);
+body {
+ --primary-color: #{$primary-color};
+}
\ No newline at end of file
diff --git a/src/frontend/src/app/styles/_template.scss b/src/frontend/src/app/styles/_template.scss
new file mode 100644
index 0000000..8d184f4
--- /dev/null
+++ b/src/frontend/src/app/styles/_template.scss
@@ -0,0 +1,398 @@
+$theme: (
+ color: (
+ primary: (
+ 50: #e8eaf6,
+ 100: #c5cae9,
+ 200: #9fa8da,
+ 300: #7986cb,
+ 400: #5c6bc0,
+ 500: #3f51b5,
+ 600: #3949ab,
+ 700: #303f9f,
+ 800: #283593,
+ 900: #1a237e,
+ A100: #8c9eff,
+ A200: #536dfe,
+ A400: #3d5afe,
+ A700: #304ffe,
+ contrast: (
+ 50: rgba(0, 0, 0, 0.87),
+ 100: rgba(0, 0, 0, 0.87),
+ 200: rgba(0, 0, 0, 0.87),
+ 300: white,
+ 400: white,
+ 500: white,
+ 600: white,
+ 700: white,
+ 800: white,
+ 900: white,
+ A100: rgba(0, 0, 0, 0.87),
+ A200: white,
+ A400: white,
+ A700: white,
+ ),
+ default: #3f51b5,
+ lighter: #c5cae9,
+ darker: #303f9f,
+ text: #3f51b5,
+ default-contrast: white,
+ lighter-contrast: rgba(0, 0, 0, 0.87),
+ darker-contrast: white,
+ "50-contrast": rgba(0, 0, 0, 0.87),
+ "100-contrast": rgba(0, 0, 0, 0.87),
+ "200-contrast": rgba(0, 0, 0, 0.87),
+ "300-contrast": white,
+ "400-contrast": white,
+ "500-contrast": white,
+ "600-contrast": white,
+ "700-contrast": white,
+ "800-contrast": white,
+ "900-contrast": white,
+ "A100-contrast": rgba(0, 0, 0, 0.87),
+ "A200-contrast": white,
+ "A400-contrast": white,
+ "A700-contrast": white,
+ "contrast-contrast": null,
+ ),
+ accent: (
+ 50: #fce4ec,
+ 100: #f8bbd0,
+ 200: #f48fb1,
+ 300: #f06292,
+ 400: #ec407a,
+ 500: #e91e63,
+ 600: #d81b60,
+ 700: #c2185b,
+ 800: #ad1457,
+ 900: #880e4f,
+ A100: #ff80ab,
+ A200: #ff4081,
+ A400: #f50057,
+ A700: #c51162,
+ contrast: (
+ 50: rgba(0, 0, 0, 0.87),
+ 100: rgba(0, 0, 0, 0.87),
+ 200: rgba(0, 0, 0, 0.87),
+ 300: rgba(0, 0, 0, 0.87),
+ 400: rgba(0, 0, 0, 0.87),
+ 500: white,
+ 600: white,
+ 700: white,
+ 800: white,
+ 900: white,
+ A100: rgba(0, 0, 0, 0.87),
+ A200: white,
+ A400: white,
+ A700: white,
+ ),
+ default: #ff4081,
+ lighter: #ff80ab,
+ darker: #f50057,
+ text: #ff4081,
+ default-contrast: white,
+ lighter-contrast: rgba(0, 0, 0, 0.87),
+ darker-contrast: white,
+ "50-contrast": rgba(0, 0, 0, 0.87),
+ "100-contrast": rgba(0, 0, 0, 0.87),
+ "200-contrast": rgba(0, 0, 0, 0.87),
+ "300-contrast": rgba(0, 0, 0, 0.87),
+ "400-contrast": rgba(0, 0, 0, 0.87),
+ "500-contrast": white,
+ "600-contrast": white,
+ "700-contrast": white,
+ "800-contrast": white,
+ "900-contrast": white,
+ "A100-contrast": rgba(0, 0, 0, 0.87),
+ "A200-contrast": white,
+ "A400-contrast": white,
+ "A700-contrast": white,
+ "contrast-contrast": null,
+ ),
+ warn: (
+ 50: #ffebee,
+ 100: #ffcdd2,
+ 200: #ef9a9a,
+ 300: #e57373,
+ 400: #ef5350,
+ 500: #f44336,
+ 600: #e53935,
+ 700: #d32f2f,
+ 800: #c62828,
+ 900: #b71c1c,
+ A100: #ff8a80,
+ A200: #ff5252,
+ A400: #ff1744,
+ A700: #d50000,
+ contrast: (
+ 50: rgba(0, 0, 0, 0.87),
+ 100: rgba(0, 0, 0, 0.87),
+ 200: rgba(0, 0, 0, 0.87),
+ 300: rgba(0, 0, 0, 0.87),
+ 400: rgba(0, 0, 0, 0.87),
+ 500: white,
+ 600: white,
+ 700: white,
+ 800: white,
+ 900: white,
+ A100: rgba(0, 0, 0, 0.87),
+ A200: white,
+ A400: white,
+ A700: white,
+ ),
+ default: #f44336,
+ lighter: #ffcdd2,
+ darker: #d32f2f,
+ text: #f44336,
+ default-contrast: white,
+ lighter-contrast: rgba(0, 0, 0, 0.87),
+ darker-contrast: white,
+ "50-contrast": rgba(0, 0, 0, 0.87),
+ "100-contrast": rgba(0, 0, 0, 0.87),
+ "200-contrast": rgba(0, 0, 0, 0.87),
+ "300-contrast": rgba(0, 0, 0, 0.87),
+ "400-contrast": rgba(0, 0, 0, 0.87),
+ "500-contrast": white,
+ "600-contrast": white,
+ "700-contrast": white,
+ "800-contrast": white,
+ "900-contrast": white,
+ "A100-contrast": rgba(0, 0, 0, 0.87),
+ "A200-contrast": white,
+ "A400-contrast": white,
+ "A700-contrast": white,
+ "contrast-contrast": null,
+ ),
+ is-dark: true,
+ foreground: (
+ base: white,
+ divider: rgba(255, 255, 255, 0.12),
+ dividers: rgba(255, 255, 255, 0.12),
+ disabled: rgba(255, 255, 255, 0.5),
+ disabled-button: rgba(255, 255, 255, 0.3),
+ disabled-text: rgba(255, 255, 255, 0.5),
+ elevation: black,
+ hint-text: rgba(255, 255, 255, 0.5),
+ secondary-text: rgba(255, 255, 255, 0.7),
+ icon: white,
+ icons: white,
+ text: white,
+ slider-min: white,
+ slider-off: rgba(255, 255, 255, 0.3),
+ slider-off-active: rgba(255, 255, 255, 0.3),
+ ),
+ background: (
+ status-bar: black,
+ app-bar: #212121,
+ background: #303030,
+ hover: rgba(255, 255, 255, 0.04),
+ card: #424242,
+ dialog: #424242,
+ disabled-button: rgba(255, 255, 255, 0.12),
+ raised-button: #424242,
+ focused-button: rgba(255, 255, 255, 0.12),
+ selected-button: #212121,
+ selected-disabled-button: #424242,
+ disabled-button-toggle: black,
+ unselected-chip: #616161,
+ disabled-list-option: black,
+ tooltip: #616161,
+ ),
+ ),
+ primary: (
+ 50: #e8eaf6,
+ 100: #c5cae9,
+ 200: #9fa8da,
+ 300: #7986cb,
+ 400: #5c6bc0,
+ 500: #3f51b5,
+ 600: #3949ab,
+ 700: #303f9f,
+ 800: #283593,
+ 900: #1a237e,
+ A100: #8c9eff,
+ A200: #536dfe,
+ A400: #3d5afe,
+ A700: #304ffe,
+ contrast: (
+ 50: rgba(0, 0, 0, 0.87),
+ 100: rgba(0, 0, 0, 0.87),
+ 200: rgba(0, 0, 0, 0.87),
+ 300: white,
+ 400: white,
+ 500: white,
+ 600: white,
+ 700: white,
+ 800: white,
+ 900: white,
+ A100: rgba(0, 0, 0, 0.87),
+ A200: white,
+ A400: white,
+ A700: white,
+ ),
+ default: #3f51b5,
+ lighter: #c5cae9,
+ darker: #303f9f,
+ text: #3f51b5,
+ default-contrast: white,
+ lighter-contrast: rgba(0, 0, 0, 0.87),
+ darker-contrast: white,
+ "50-contrast": rgba(0, 0, 0, 0.87),
+ "100-contrast": rgba(0, 0, 0, 0.87),
+ "200-contrast": rgba(0, 0, 0, 0.87),
+ "300-contrast": white,
+ "400-contrast": white,
+ "500-contrast": white,
+ "600-contrast": white,
+ "700-contrast": white,
+ "800-contrast": white,
+ "900-contrast": white,
+ "A100-contrast": rgba(0, 0, 0, 0.87),
+ "A200-contrast": white,
+ "A400-contrast": white,
+ "A700-contrast": white,
+ "contrast-contrast": null,
+ ),
+ accent: (
+ 50: #fce4ec,
+ 100: #f8bbd0,
+ 200: #f48fb1,
+ 300: #f06292,
+ 400: #ec407a,
+ 500: #e91e63,
+ 600: #d81b60,
+ 700: #c2185b,
+ 800: #ad1457,
+ 900: #880e4f,
+ A100: #ff80ab,
+ A200: #ff4081,
+ A400: #f50057,
+ A700: #c51162,
+ contrast: (
+ 50: rgba(0, 0, 0, 0.87),
+ 100: rgba(0, 0, 0, 0.87),
+ 200: rgba(0, 0, 0, 0.87),
+ 300: rgba(0, 0, 0, 0.87),
+ 400: rgba(0, 0, 0, 0.87),
+ 500: white,
+ 600: white,
+ 700: white,
+ 800: white,
+ 900: white,
+ A100: rgba(0, 0, 0, 0.87),
+ A200: white,
+ A400: white,
+ A700: white,
+ ),
+ default: #ff4081,
+ lighter: #ff80ab,
+ darker: #f50057,
+ text: #ff4081,
+ default-contrast: white,
+ lighter-contrast: rgba(0, 0, 0, 0.87),
+ darker-contrast: white,
+ "50-contrast": rgba(0, 0, 0, 0.87),
+ "100-contrast": rgba(0, 0, 0, 0.87),
+ "200-contrast": rgba(0, 0, 0, 0.87),
+ "300-contrast": rgba(0, 0, 0, 0.87),
+ "400-contrast": rgba(0, 0, 0, 0.87),
+ "500-contrast": white,
+ "600-contrast": white,
+ "700-contrast": white,
+ "800-contrast": white,
+ "900-contrast": white,
+ "A100-contrast": rgba(0, 0, 0, 0.87),
+ "A200-contrast": white,
+ "A400-contrast": white,
+ "A700-contrast": white,
+ "contrast-contrast": null,
+ ),
+ warn: (
+ 50: #ffebee,
+ 100: #ffcdd2,
+ 200: #ef9a9a,
+ 300: #e57373,
+ 400: #ef5350,
+ 500: #f44336,
+ 600: #e53935,
+ 700: #d32f2f,
+ 800: #c62828,
+ 900: #b71c1c,
+ A100: #ff8a80,
+ A200: #ff5252,
+ A400: #ff1744,
+ A700: #d50000,
+ contrast: (
+ 50: rgba(0, 0, 0, 0.87),
+ 100: rgba(0, 0, 0, 0.87),
+ 200: rgba(0, 0, 0, 0.87),
+ 300: rgba(0, 0, 0, 0.87),
+ 400: rgba(0, 0, 0, 0.87),
+ 500: white,
+ 600: white,
+ 700: white,
+ 800: white,
+ 900: white,
+ A100: rgba(0, 0, 0, 0.87),
+ A200: white,
+ A400: white,
+ A700: white,
+ ),
+ default: #f44336,
+ lighter: #ffcdd2,
+ darker: #d32f2f,
+ text: #f44336,
+ default-contrast: white,
+ lighter-contrast: rgba(0, 0, 0, 0.87),
+ darker-contrast: white,
+ "50-contrast": rgba(0, 0, 0, 0.87),
+ "100-contrast": rgba(0, 0, 0, 0.87),
+ "200-contrast": rgba(0, 0, 0, 0.87),
+ "300-contrast": rgba(0, 0, 0, 0.87),
+ "400-contrast": rgba(0, 0, 0, 0.87),
+ "500-contrast": white,
+ "600-contrast": white,
+ "700-contrast": white,
+ "800-contrast": white,
+ "900-contrast": white,
+ "A100-contrast": rgba(0, 0, 0, 0.87),
+ "A200-contrast": white,
+ "A400-contrast": white,
+ "A700-contrast": white,
+ "contrast-contrast": null,
+ ),
+ is-dark: true,
+ foreground: (
+ base: white,
+ divider: rgba(255, 255, 255, 0.12),
+ dividers: rgba(255, 255, 255, 0.12),
+ disabled: rgba(255, 255, 255, 0.5),
+ disabled-button: rgba(255, 255, 255, 0.3),
+ disabled-text: rgba(255, 255, 255, 0.5),
+ elevation: black,
+ hint-text: rgba(255, 255, 255, 0.5),
+ secondary-text: rgba(255, 255, 255, 0.7),
+ icon: white,
+ icons: white,
+ text: white,
+ slider-min: white,
+ slider-off: rgba(255, 255, 255, 0.3),
+ slider-off-active: rgba(255, 255, 255, 0.3),
+ ),
+ background: (
+ status-bar: black,
+ app-bar: #212121,
+ background: #303030,
+ hover: rgba(255, 255, 255, 0.04),
+ card: #424242,
+ dialog: #424242,
+ disabled-button: rgba(255, 255, 255, 0.12),
+ raised-button: #424242,
+ focused-button: rgba(255, 255, 255, 0.12),
+ selected-button: #212121,
+ selected-disabled-button: #424242,
+ disabled-button-toggle: black,
+ unselected-chip: #616161,
+ disabled-list-option: black,
+ tooltip: #616161,
+ ),
+);
diff --git a/src/frontend/src/app/styles/dark.scss b/src/frontend/src/app/styles/dark.scss
new file mode 100644
index 0000000..df3b111
--- /dev/null
+++ b/src/frontend/src/app/styles/dark.scss
@@ -0,0 +1,35 @@
+// Custom Theming for Angular Material
+// For more information: https://material.angular.io/guide/theming
+@use '~@angular/material' as mat;
+// Plus imports for other components in your app.
+
+// Include the common styles for Angular Material. We include this here so that you only
+// have to load a single css file for Angular Material in your app.
+// Be sure that you only ever include this mixin once!
+@include mat.core();
+
+// Define the palettes for your theme using the Material Design palettes available in palette.scss
+// (imported above). For each palette, you can optionally specify a default, lighter, and darker
+// hue. Available color palettes: https://material.io/design/color/
+$theme-primary: mat.define-palette(mat.$blue-palette);
+$theme-accent: mat.define-palette(mat.$green-palette, A200, A100, A400);
+
+// The warn palette is optional (defaults to red).
+$theme-warn: mat.define-palette(mat.$red-palette);
+
+// Create the theme object. A theme consists of configurations for individual
+// theming systems such as "color" or "typography".
+$theme: mat.define-dark-theme((
+ color: (
+ primary: $theme-primary,
+ accent: $theme-accent,
+ warn: $theme-warn,
+ )
+));
+
+// Include theme styles for core and each component used in your app.
+// Alternatively, you can import and @include the theme mixins for each component
+// that you are using.
+@include mat.all-component-themes($theme);
+
+@import "./base";
diff --git a/src/frontend/src/app/styles/light.scss b/src/frontend/src/app/styles/light.scss
new file mode 100644
index 0000000..423617d
--- /dev/null
+++ b/src/frontend/src/app/styles/light.scss
@@ -0,0 +1,35 @@
+// Custom Theming for Angular Material
+// For more information: https://material.angular.io/guide/theming
+@use '~@angular/material' as mat;
+// Plus imports for other components in your app.
+
+// Include the common styles for Angular Material. We include this here so that you only
+// have to load a single css file for Angular Material in your app.
+// Be sure that you only ever include this mixin once!
+@include mat.core();
+
+// Define the palettes for your theme using the Material Design palettes available in palette.scss
+// (imported above). For each palette, you can optionally specify a default, lighter, and darker
+// hue. Available color palettes: https://material.io/design/color/
+$theme-primary: mat.define-palette(mat.$indigo-palette);
+$theme-accent: mat.define-palette(mat.$light-blue-palette, A200, A100, A400);
+
+// The warn palette is optional (defaults to red).
+$theme-warn: mat.define-palette(mat.$red-palette);
+
+// Create the theme object. A theme consists of configurations for individual
+// theming systems such as "color" or "typography".
+$theme: mat.define-light-theme((
+ color: (
+ primary: $theme-primary,
+ accent: $theme-accent,
+ warn: $theme-warn,
+ )
+));
+
+// Include theme styles for core and each component used in your app.
+// Alternatively, you can import and @include the theme mixins for each component
+// that you are using.
+@include mat.all-component-themes($theme);
+
+@import "./base";
\ No newline at end of file
diff --git a/src/frontend/src/assets/lang/en.json b/src/frontend/src/assets/lang/en.json
new file mode 100644
index 0000000..58dc61a
--- /dev/null
+++ b/src/frontend/src/assets/lang/en.json
@@ -0,0 +1,28 @@
+{
+ "APP": {
+ "NAME": "CureNet",
+ "THEME": "Theme",
+ "LANGUAGE": "Language"
+ },
+ "ERROR": {
+ "PAGE_NOT_FOUND": "Page not found or not implemented yet!"
+ },
+ "THEME": {
+ "LIGHT": "Light",
+ "DARK": "Dark"
+ },
+ "LANGUAGE": {
+ "PL": "Polish",
+ "EN": "English"
+ },
+ "MENU": {
+ "HOME": "Dashboard",
+ "PROFILE": "Profile",
+ "COMMUNITY": "Community",
+ "MESSAGES": "Messages",
+ "TESTS": "Laboratory tests",
+ "RESEARCH": "Research",
+ "ADMIN": "Admin",
+ "USERS": "Users"
+ }
+}
\ No newline at end of file
diff --git a/src/frontend/src/assets/lang/pl.json b/src/frontend/src/assets/lang/pl.json
new file mode 100644
index 0000000..6855253
--- /dev/null
+++ b/src/frontend/src/assets/lang/pl.json
@@ -0,0 +1,28 @@
+{
+ "APP": {
+ "NAME": "CureNet",
+ "THEME": "Motyw",
+ "LANGUAGE": "Język"
+ },
+ "ERROR": {
+ "PAGE_NOT_FOUND": "Nie znaleziono strony lub nie została ona jeszcze zaimplementowana!"
+ },
+ "THEME": {
+ "LIGHT": "Jasny",
+ "DARK": "Ciemny"
+ },
+ "LANGUAGE": {
+ "PL": "Polski",
+ "EN": "Angielski"
+ },
+ "MENU": {
+ "HOME": "Kokpit",
+ "PROFILE": "Profil",
+ "COMMUNITY": "Społeczność",
+ "MESSAGES": "Wiadomości",
+ "TESTS": "Wyniki badań",
+ "RESEARCH": "Badania naukowe",
+ "ADMIN": "Panel administratora",
+ "USERS": "Użytkownicy"
+ }
+}
\ No newline at end of file
diff --git a/src/frontend/src/index.html b/src/frontend/src/index.html
index 3af61ec..0b791e2 100644
--- a/src/frontend/src/index.html
+++ b/src/frontend/src/index.html
@@ -1,13 +1,17 @@
-
-
-
-
- Frontend
-
-
-
-
-
-
-
-
+
+
+
+
+ CureNet
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/frontend/src/styles.scss b/src/frontend/src/styles.scss
index 90d4ee0..ef52523 100644
--- a/src/frontend/src/styles.scss
+++ b/src/frontend/src/styles.scss
@@ -1 +1,45 @@
/* You can add global styles to this file, and also import other style files */
+
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
+
+@import "~bootstrap/dist/css/bootstrap.css";
+@import "~font-awesome/css/font-awesome.css";
+
+body .mat-drawer.mat-drawer-side {
+ visibility: visible !important;
+ transform: none !important;
+ width: 250px;
+ &,
+ & .mat-drawer-inner-container{
+ transition-duration: 400ms !important;
+ transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !important;
+ transition-property: transform, margin-left, margin-right, width !important;
+ overflow: hidden;
+ }
+ & ~ .mat-drawer-content{
+ margin-left: 250px !important;
+ }
+ &:not(.mat-drawer-opened) {
+ width: 60px;
+ .menu-item{
+ margin-left: 0px !important;
+ &:after{
+ right: 190px;
+ }
+ & > i{
+ width: 40px;
+ font-size: 120%;
+ }
+ }
+ .hide-on-side-opened{
+ display: none;
+ }
+ & ~ .mat-drawer-content {
+ margin-left: 60px !important;
+ }
+ }
+}
+.w-220{
+ width: 220px;
+}
\ No newline at end of file