diff --git a/client/package-lock.json b/client/package-lock.json index 307623a..a36a529 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,7 @@ "@angular/platform-browser-dynamic": "^16.0.0", "@angular/router": "^16.0.0", "@angular/service-worker": "^16.0.0", + "@auth0/angular-jwt": "^5.1.2", "@capacitor/app": "5.0.2", "@capacitor/core": "5.0.4", "@capacitor/haptics": "5.0.2", @@ -832,6 +833,17 @@ "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", "dev": true }, + "node_modules/@auth0/angular-jwt": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-5.1.2.tgz", + "integrity": "sha512-8ulz24cPpEkZb9/AdAaWfYIkomQDbZqvB9LproF/48Klnr30EJx09AYF9sbKTN4qLSgIZSlCb/Y7XQJZ51vSzA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=12.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.21.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", diff --git a/client/package.json b/client/package.json index cf6b10c..bd46596 100644 --- a/client/package.json +++ b/client/package.json @@ -23,6 +23,7 @@ "@angular/platform-browser-dynamic": "^16.0.0", "@angular/router": "^16.0.0", "@angular/service-worker": "^16.0.0", + "@auth0/angular-jwt": "^5.1.2", "@capacitor/app": "5.0.2", "@capacitor/core": "5.0.4", "@capacitor/haptics": "5.0.2", diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index bf907a7..facc763 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts @@ -1,16 +1,22 @@ import { NgModule } from '@angular/core'; import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; +import { AuthGuard } from './auth.guard'; const routes: Routes = [ { path: '', - redirectTo: 'folder/Inbox', - pathMatch: 'full' + canActivate: [AuthGuard], + loadChildren: () => import('./home/home.module').then( m => m.HomePageModule) + }, + { + path: 'login', + loadChildren: () => import('./login/login.module').then( m => m.LoginPageModule) }, { - path: 'folder/:id', - loadChildren: () => import('./folder/folder.module').then( m => m.FolderPageModule) - } + path: '**', + redirectTo: '', + pathMatch: 'full' + }, ]; @NgModule({ diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 3fd9ace..7dc66a5 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -2,9 +2,10 @@ - - Inbox - hi@ionicframework.com + + + Eat Oats, Nerd + @@ -13,15 +14,6 @@ - - - Labels - - - - {{ label }} - - diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index 0aeb6fe..0343f50 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss @@ -1,119 +1,4 @@ -ion-menu ion-content { - --background: var(--ion-item-background, var(--ion-background-color, #fff)); -} - -ion-menu.md ion-content { - --padding-start: 8px; - --padding-end: 8px; - --padding-top: 20px; - --padding-bottom: 20px; -} - -ion-menu.md ion-list { - padding: 20px 0; -} - -ion-menu.md ion-note { - margin-bottom: 30px; -} - -ion-menu.md ion-list-header, -ion-menu.md ion-note { - padding-left: 10px; -} - -ion-menu.md ion-list#inbox-list { - border-bottom: 1px solid var(--ion-color-step-150, #d7d8da); -} - -ion-menu.md ion-list#inbox-list ion-list-header { - font-size: 22px; - font-weight: 600; - - min-height: 20px; -} - -ion-menu.md ion-list#labels-list ion-list-header { - font-size: 16px; - - margin-bottom: 18px; - - color: #757575; - - min-height: 26px; -} - -ion-menu.md ion-item { - --padding-start: 10px; - --padding-end: 10px; - border-radius: 4px; -} - -ion-menu.md ion-item.selected { - --background: rgba(var(--ion-color-primary-rgb), 0.14); -} - -ion-menu.md ion-item.selected ion-icon { - color: var(--ion-color-primary); -} -ion-menu.md ion-item ion-icon { - color: #616e7e; +.hero-header { + min-height: 300px; } - -ion-menu.md ion-item ion-label { - font-weight: 500; -} - -ion-menu.ios ion-content { - --padding-bottom: 20px; -} - -ion-menu.ios ion-list { - padding: 20px 0 0 0; -} - -ion-menu.ios ion-note { - line-height: 24px; - margin-bottom: 20px; -} - -ion-menu.ios ion-item { - --padding-start: 16px; - --padding-end: 16px; - --min-height: 50px; -} - -ion-menu.ios ion-item.selected ion-icon { - color: var(--ion-color-primary); -} - -ion-menu.ios ion-item ion-icon { - font-size: 24px; - color: #73849a; -} - -ion-menu.ios ion-list#labels-list ion-list-header { - margin-bottom: 8px; -} - -ion-menu.ios ion-list-header, -ion-menu.ios ion-note { - padding-left: 16px; - padding-right: 16px; -} - -ion-menu.ios ion-note { - margin-bottom: 8px; -} - -ion-note { - display: inline-block; - font-size: 16px; - - color: var(--ion-color-medium-shade); -} - -ion-item.selected { - --color: var(--ion-color-primary); -} \ No newline at end of file diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 724e1d7..ec47298 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -1,18 +1,24 @@ import { Component } from '@angular/core'; + +interface IMenuItem { + title: string; + url: string; + icon: string; +} + @Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.scss'], }) export class AppComponent { - public appPages = [ - { title: 'Inbox', url: '/folder/inbox', icon: 'mail' }, - { title: 'Outbox', url: '/folder/outbox', icon: 'paper-plane' }, - { title: 'Favorites', url: '/folder/favorites', icon: 'heart' }, - { title: 'Archived', url: '/folder/archived', icon: 'archive' }, - { title: 'Trash', url: '/folder/trash', icon: 'trash' }, - { title: 'Spam', url: '/folder/spam', icon: 'warning' }, + public appPages: IMenuItem[] = [ + { + title: 'Home', + url: '', + icon: 'home' + } ]; - public labels = ['Family', 'Friends', 'Notes', 'Work', 'Travel', 'Reminders']; + constructor() {} } diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 0e534ac..79944d4 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -1,6 +1,8 @@ +import { HttpClientModule } from "@angular/common/http"; import { NgModule, isDevMode } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouteReuseStrategy } from '@angular/router'; +import { JwtModule } from "@auth0/angular-jwt"; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin'; @@ -15,12 +17,23 @@ import { AppComponent } from './app.component'; const Migrations: Record = {}; const allStores: any[] = []; +export function getAuthToken() { + return localStorage.getItem('authToken'); +} + @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, IonicModule.forRoot(), AppRoutingModule, + HttpClientModule, + JwtModule.forRoot({ + config: { + tokenGetter: getAuthToken, + allowedDomains: ["ateoat.com"], + }, + }), ServiceWorkerModule.register('ngsw-worker.js', { enabled: !isDevMode(), // Register the ServiceWorker as soon as the application is stable diff --git a/client/src/app/auth.guard.ts b/client/src/app/auth.guard.ts new file mode 100644 index 0000000..8079ab1 --- /dev/null +++ b/client/src/app/auth.guard.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from './auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard { + constructor(public auth: AuthService, public router: Router) {} + + canActivate(): boolean { + if (!this.auth.isAuthenticated()) { + this.router.navigate(['login']); + return false; + } + + return true; + } +} diff --git a/client/src/app/auth.service.ts b/client/src/app/auth.service.ts new file mode 100644 index 0000000..4ab4eb8 --- /dev/null +++ b/client/src/app/auth.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { JwtHelperService } from '@auth0/angular-jwt'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + constructor(public jwtHelper: JwtHelperService) { } + + public isAuthenticated(): boolean { + const token = localStorage.getItem('token'); + return !this.jwtHelper.isTokenExpired(token); + } + + public login(email: string, password: string): void { + + } + + public register(email: string, password: string, username: string): void { + + } +} diff --git a/client/src/app/folder/folder.page.html b/client/src/app/folder/folder.page.html deleted file mode 100644 index 39aaa6a..0000000 --- a/client/src/app/folder/folder.page.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - {{ folder }} - - - - - - - {{ folder }} - - - -
- {{ folder }} -

Explore UI Components

-
-
diff --git a/client/src/app/folder/folder.page.scss b/client/src/app/folder/folder.page.scss deleted file mode 100644 index 3797ba5..0000000 --- a/client/src/app/folder/folder.page.scss +++ /dev/null @@ -1,28 +0,0 @@ -ion-menu-button { - color: var(--ion-color-primary); -} - -#container { - text-align: center; - position: absolute; - left: 0; - right: 0; - top: 50%; - transform: translateY(-50%); -} - -#container strong { - font-size: 20px; - line-height: 26px; -} - -#container p { - font-size: 16px; - line-height: 22px; - color: #8c8c8c; - margin: 0; -} - -#container a { - text-decoration: none; -} \ No newline at end of file diff --git a/client/src/app/folder/folder.page.ts b/client/src/app/folder/folder.page.ts deleted file mode 100644 index 00aee83..0000000 --- a/client/src/app/folder/folder.page.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Component, inject, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; - -@Component({ - selector: 'app-folder', - templateUrl: './folder.page.html', - styleUrls: ['./folder.page.scss'], -}) -export class FolderPage implements OnInit { - public folder!: string; - private activatedRoute = inject(ActivatedRoute); - constructor() {} - - ngOnInit() { - this.folder = this.activatedRoute.snapshot.paramMap.get('id') as string; - } -} diff --git a/client/src/app/folder/folder-routing.module.ts b/client/src/app/home/home-routing.module.ts similarity index 68% rename from client/src/app/folder/folder-routing.module.ts rename to client/src/app/home/home-routing.module.ts index 200d440..720fb1c 100644 --- a/client/src/app/folder/folder-routing.module.ts +++ b/client/src/app/home/home-routing.module.ts @@ -1,12 +1,12 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { FolderPage } from './folder.page'; +import { HomePage } from './home.page'; const routes: Routes = [ { path: '', - component: FolderPage + component: HomePage } ]; @@ -14,4 +14,4 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) -export class FolderPageRoutingModule {} +export class HomePageRoutingModule {} diff --git a/client/src/app/folder/folder.module.ts b/client/src/app/home/home.module.ts similarity index 57% rename from client/src/app/folder/folder.module.ts rename to client/src/app/home/home.module.ts index 172c9f8..aca3da5 100644 --- a/client/src/app/folder/folder.module.ts +++ b/client/src/app/home/home.module.ts @@ -4,17 +4,17 @@ import { FormsModule } from '@angular/forms'; import { IonicModule } from '@ionic/angular'; -import { FolderPageRoutingModule } from './folder-routing.module'; +import { HomePageRoutingModule } from './home-routing.module'; -import { FolderPage } from './folder.page'; +import { HomePage } from './home.page'; @NgModule({ imports: [ CommonModule, FormsModule, IonicModule, - FolderPageRoutingModule + HomePageRoutingModule ], - declarations: [FolderPage] + declarations: [HomePage] }) -export class FolderPageModule {} +export class HomePageModule {} diff --git a/client/src/app/home/home.page.html b/client/src/app/home/home.page.html new file mode 100644 index 0000000..e9fb362 --- /dev/null +++ b/client/src/app/home/home.page.html @@ -0,0 +1,13 @@ + + + home + + + + + + + home + + + diff --git a/client/src/app/home/home.page.scss b/client/src/app/home/home.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/home/home.page.ts b/client/src/app/home/home.page.ts new file mode 100644 index 0000000..8aa0f78 --- /dev/null +++ b/client/src/app/home/home.page.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-home', + templateUrl: './home.page.html', + styleUrls: ['./home.page.scss'], +}) +export class HomePage implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/client/src/app/login/login-routing.module.ts b/client/src/app/login/login-routing.module.ts new file mode 100644 index 0000000..29ef3a2 --- /dev/null +++ b/client/src/app/login/login-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { LoginPage } from './login.page'; + +const routes: Routes = [ + { + path: '', + component: LoginPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class LoginPageRoutingModule {} diff --git a/client/src/app/login/login.module.ts b/client/src/app/login/login.module.ts new file mode 100644 index 0000000..b35477a --- /dev/null +++ b/client/src/app/login/login.module.ts @@ -0,0 +1,21 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { LoginPageRoutingModule } from './login-routing.module'; + +import { LoginPage } from './login.page'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + IonicModule, + LoginPageRoutingModule + ], + declarations: [LoginPage] +}) +export class LoginPageModule {} diff --git a/client/src/app/login/login.page.html b/client/src/app/login/login.page.html new file mode 100644 index 0000000..1be48ef --- /dev/null +++ b/client/src/app/login/login.page.html @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + Login + + + Register + + + + + + + + + + + + + + + +
+ Clear + Login +
+
+
+ + + + + + + + + + + + + + + + + +
+ Clear + Register +
+
+
+
+
+
+
diff --git a/client/src/app/login/login.page.scss b/client/src/app/login/login.page.scss new file mode 100644 index 0000000..b7035ef --- /dev/null +++ b/client/src/app/login/login.page.scss @@ -0,0 +1,31 @@ + +ion-row { + justify-content: center; +} + +ion-item { + min-height: 85px; +} + +.image-container { + margin-top: 10vh; +} + +.auth-container { + margin-top: 5vh; +} + +.image-container, .auth-container { + width: 100vw; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.actions { + margin-top: 2em; + display: flex; + justify-content: space-between; +} diff --git a/client/src/app/login/login.page.ts b/client/src/app/login/login.page.ts new file mode 100644 index 0000000..3549dda --- /dev/null +++ b/client/src/app/login/login.page.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { MenuController } from '@ionic/angular'; +import { AuthService } from '../auth.service'; + +@Component({ + selector: 'app-login', + templateUrl: './login.page.html', + styleUrls: ['./login.page.scss'], +}) +export class LoginPage implements OnInit { + + authType: 'login'|'register' = 'login'; + + loginForm = new FormGroup({ + email: new FormControl('', [Validators.required, Validators.email]), + password: new FormControl('', [Validators.required, Validators.minLength(8)]), + }); + + registerForm = new FormGroup({ + email: new FormControl('', [Validators.required, Validators.email]), + password: new FormControl('', [Validators.required, Validators.minLength(8)]), + username: new FormControl('', [Validators.required, Validators.minLength(2), Validators.maxLength(20)]), + }); + + constructor(public menu: MenuController, private authService: AuthService) { } + + ngOnInit() { + this.loginForm.controls.email.errors + } + + ionViewDidEnter() { + this.menu.enable(false); + this.menu.swipeGesture(false); + } + + ionViewWillLeave() { + this.menu.enable(true); + this.menu.swipeGesture(true); + } + + login() { + if(!this.loginForm.value.email || !this.loginForm.value.password) return; + + this.authService.login(this.loginForm.value.email, this.loginForm.value.password) + } + + register() { + if(!this.registerForm.value.email || !this.registerForm.value.password || !this.registerForm.value.username) return; + + this.authService.register(this.registerForm.value.email, this.registerForm.value.password, this.registerForm.value.username); + } + +} diff --git a/client/src/assets/bg/home.png b/client/src/assets/bg/home.png new file mode 100644 index 0000000..647829a Binary files /dev/null and b/client/src/assets/bg/home.png differ diff --git a/client/src/assets/bg/splash-dark.png b/client/src/assets/bg/splash-dark.png new file mode 100644 index 0000000..6f2f2f5 Binary files /dev/null and b/client/src/assets/bg/splash-dark.png differ diff --git a/client/src/assets/bg/splash.png b/client/src/assets/bg/splash.png new file mode 100644 index 0000000..6127614 Binary files /dev/null and b/client/src/assets/bg/splash.png differ diff --git a/client/src/global.scss b/client/src/global.scss index d89e39b..497cadc 100644 --- a/client/src/global.scss +++ b/client/src/global.scss @@ -24,3 +24,18 @@ @import "@ionic/angular/css/text-alignment.css"; @import "@ionic/angular/css/text-transformation.css"; @import "@ionic/angular/css/flex-utils.css"; + +ion-content { + --background: url(assets/bg/splash-dark.png) no-repeat center center/cover; + &.bright { + --background: url(assets/bg/splash.png) no-repeat center center/cover; + } +} + +.full-width { + width: 100%; +} + +.hidden { + display: none; +} diff --git a/client/src/index.html b/client/src/index.html index afaf9d7..863abf2 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -3,7 +3,7 @@ - Ionic App + After the End of the World diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts index ed6ffd8..04d5049 100644 --- a/server/src/auth/auth.controller.ts +++ b/server/src/auth/auth.controller.ts @@ -22,9 +22,9 @@ export class AuthController { if (!signInDto.username || !signInDto.password || !signInDto.email) throw new BadRequestException('Missing username, password or email'); - if (signInDto.username.length < 3 || signInDto.username.length > 20) + if (signInDto.username.length < 2 || signInDto.username.length > 20) throw new BadRequestException( - 'Username must be between 3 and 20 characters long', + 'Username must be between 2 and 20 characters long', ); if (signInDto.password.length < 8)