diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e529d68ac..9cf5f4200 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,10 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; -import { SidenavComponent } from '@osf/sidenav/sidenav.component'; +import { SidenavComponent } from '@core/components/sidenav/sidenav.component'; import { RouterOutlet } from '@angular/router'; -import { HeaderComponent } from '@osf/header/header.component'; -import { MainContentComponent } from '@osf/main-content/main-content.component'; -import { FooterComponent } from '@osf/footer/footer.component'; -import { TopnavComponent } from '@osf/topnav/topnav.component'; +import { HeaderComponent } from '@core/components/header/header.component'; +import { MainContentComponent } from '@core/components/main-content/main-content.component'; +import { FooterComponent } from '@core/components/footer/footer.component'; +import { TopnavComponent } from '@core/components/topnav/topnav.component'; import { IS_PORTRAIT } from '@shared/utils/breakpoints.tokens'; import { toSignal } from '@angular/core/rxjs-interop'; diff --git a/src/app/app.module.ts b/src/app/app.module.ts new file mode 100644 index 000000000..87ec29d5d --- /dev/null +++ b/src/app/app.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from '@angular/core'; +import { NgxsModule } from '@ngxs/store'; +import { AuthState } from '@core/store/auth'; + +@NgModule({ + imports: [NgxsModule.forRoot([AuthState])], +}) +export class AppModule {} diff --git a/src/app/footer/footer.component.html b/src/app/core/components/footer/footer.component.html similarity index 100% rename from src/app/footer/footer.component.html rename to src/app/core/components/footer/footer.component.html diff --git a/src/app/footer/footer.component.scss b/src/app/core/components/footer/footer.component.scss similarity index 100% rename from src/app/footer/footer.component.scss rename to src/app/core/components/footer/footer.component.scss diff --git a/src/app/footer/footer.component.spec.ts b/src/app/core/components/footer/footer.component.spec.ts similarity index 100% rename from src/app/footer/footer.component.spec.ts rename to src/app/core/components/footer/footer.component.spec.ts diff --git a/src/app/footer/footer.component.ts b/src/app/core/components/footer/footer.component.ts similarity index 100% rename from src/app/footer/footer.component.ts rename to src/app/core/components/footer/footer.component.ts diff --git a/src/app/header/header.component.html b/src/app/core/components/header/header.component.html similarity index 100% rename from src/app/header/header.component.html rename to src/app/core/components/header/header.component.html diff --git a/src/app/header/header.component.scss b/src/app/core/components/header/header.component.scss similarity index 100% rename from src/app/header/header.component.scss rename to src/app/core/components/header/header.component.scss diff --git a/src/app/header/header.component.spec.ts b/src/app/core/components/header/header.component.spec.ts similarity index 100% rename from src/app/header/header.component.spec.ts rename to src/app/core/components/header/header.component.spec.ts diff --git a/src/app/header/header.component.ts b/src/app/core/components/header/header.component.ts similarity index 100% rename from src/app/header/header.component.ts rename to src/app/core/components/header/header.component.ts diff --git a/src/app/main-content/main-content.component.html b/src/app/core/components/main-content/main-content.component.html similarity index 50% rename from src/app/main-content/main-content.component.html rename to src/app/core/components/main-content/main-content.component.html index aec502cfa..924dcc79b 100644 --- a/src/app/main-content/main-content.component.html +++ b/src/app/core/components/main-content/main-content.component.html @@ -1,3 +1,3 @@
- main-content works! +

main-content works!

diff --git a/src/app/main-content/main-content.component.scss b/src/app/core/components/main-content/main-content.component.scss similarity index 100% rename from src/app/main-content/main-content.component.scss rename to src/app/core/components/main-content/main-content.component.scss diff --git a/src/app/main-content/main-content.component.spec.ts b/src/app/core/components/main-content/main-content.component.spec.ts similarity index 100% rename from src/app/main-content/main-content.component.spec.ts rename to src/app/core/components/main-content/main-content.component.spec.ts diff --git a/src/app/main-content/main-content.component.ts b/src/app/core/components/main-content/main-content.component.ts similarity index 100% rename from src/app/main-content/main-content.component.ts rename to src/app/core/components/main-content/main-content.component.ts diff --git a/src/app/sidenav/sidenav.component.html b/src/app/core/components/sidenav/sidenav.component.html similarity index 100% rename from src/app/sidenav/sidenav.component.html rename to src/app/core/components/sidenav/sidenav.component.html diff --git a/src/app/sidenav/sidenav.component.scss b/src/app/core/components/sidenav/sidenav.component.scss similarity index 100% rename from src/app/sidenav/sidenav.component.scss rename to src/app/core/components/sidenav/sidenav.component.scss diff --git a/src/app/sidenav/sidenav.component.spec.ts b/src/app/core/components/sidenav/sidenav.component.spec.ts similarity index 100% rename from src/app/sidenav/sidenav.component.spec.ts rename to src/app/core/components/sidenav/sidenav.component.spec.ts diff --git a/src/app/sidenav/sidenav.component.ts b/src/app/core/components/sidenav/sidenav.component.ts similarity index 100% rename from src/app/sidenav/sidenav.component.ts rename to src/app/core/components/sidenav/sidenav.component.ts diff --git a/src/app/topnav/topnav.component.html b/src/app/core/components/topnav/topnav.component.html similarity index 100% rename from src/app/topnav/topnav.component.html rename to src/app/core/components/topnav/topnav.component.html diff --git a/src/app/topnav/topnav.component.scss b/src/app/core/components/topnav/topnav.component.scss similarity index 100% rename from src/app/topnav/topnav.component.scss rename to src/app/core/components/topnav/topnav.component.scss diff --git a/src/app/topnav/topnav.component.spec.ts b/src/app/core/components/topnav/topnav.component.spec.ts similarity index 100% rename from src/app/topnav/topnav.component.spec.ts rename to src/app/core/components/topnav/topnav.component.spec.ts diff --git a/src/app/topnav/topnav.component.ts b/src/app/core/components/topnav/topnav.component.ts similarity index 100% rename from src/app/topnav/topnav.component.ts rename to src/app/core/components/topnav/topnav.component.ts diff --git a/src/app/core/store/auth/auth.actions.ts b/src/app/core/store/auth/auth.actions.ts new file mode 100644 index 000000000..112d4d3c5 --- /dev/null +++ b/src/app/core/store/auth/auth.actions.ts @@ -0,0 +1,9 @@ +export class SetAuthToken { + static readonly type = '[Auth] Set Auth Token'; + + constructor(public accessToken: string) {} +} + +export class ClearAuth { + static readonly type = '[Auth] Clear Auth'; +} diff --git a/src/app/core/store/auth/auth.model.ts b/src/app/core/store/auth/auth.model.ts new file mode 100644 index 000000000..17abed7c1 --- /dev/null +++ b/src/app/core/store/auth/auth.model.ts @@ -0,0 +1,4 @@ +export interface AuthStateModel { + accessToken: string | null; + isAuthenticated: boolean; +} diff --git a/src/app/core/store/auth/auth.selectors.ts b/src/app/core/store/auth/auth.selectors.ts new file mode 100644 index 000000000..f50c3b525 --- /dev/null +++ b/src/app/core/store/auth/auth.selectors.ts @@ -0,0 +1,15 @@ +import { Selector } from '@ngxs/store'; +import { AuthState } from './auth.state'; +import { AuthStateModel } from './auth.model'; + +export class AuthSelectors { + @Selector([AuthState]) + static isAuthenticated(state: AuthStateModel): boolean { + return state.isAuthenticated; + } + + @Selector([AuthState]) + static getAuthToken(state: AuthStateModel): string | null { + return state.accessToken; + } +} diff --git a/src/app/core/store/auth/auth.state.ts b/src/app/core/store/auth/auth.state.ts new file mode 100644 index 000000000..9d14b0b4b --- /dev/null +++ b/src/app/core/store/auth/auth.state.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { State, Action, StateContext } from '@ngxs/store'; +import { AuthStateModel } from './auth.model'; +import { SetAuthToken, ClearAuth } from './auth.actions'; + +@State({ + name: 'auth', + defaults: { + accessToken: null, + isAuthenticated: false, + }, +}) +@Injectable() +export class AuthState { + @Action(SetAuthToken) + setAuthToken(ctx: StateContext, action: SetAuthToken) { + ctx.patchState({ + accessToken: action.accessToken, + isAuthenticated: true, + }); + } + + @Action(ClearAuth) + clearAuth(ctx: StateContext) { + ctx.patchState({ + accessToken: null, + isAuthenticated: false, + }); + } +} diff --git a/src/app/core/store/auth/index.ts b/src/app/core/store/auth/index.ts new file mode 100644 index 000000000..bccd7d73b --- /dev/null +++ b/src/app/core/store/auth/index.ts @@ -0,0 +1,4 @@ +export * from './auth.actions'; +export * from './auth.model'; +export * from './auth.selectors'; +export * from './auth.state'; diff --git a/src/app/features/auth/auth.entity.ts b/src/app/features/auth/auth.entity.ts new file mode 100644 index 000000000..48cafe8b4 --- /dev/null +++ b/src/app/features/auth/auth.entity.ts @@ -0,0 +1,8 @@ +export interface LoginCredentials { + email: string; + password: string; +} + +export interface AuthResponse { + accessToken: string; +} diff --git a/src/app/features/auth/auth.service.ts b/src/app/features/auth/auth.service.ts new file mode 100644 index 000000000..7bacfdd15 --- /dev/null +++ b/src/app/features/auth/auth.service.ts @@ -0,0 +1,54 @@ +import { Injectable, inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Store } from '@ngxs/store'; +import { firstValueFrom } from 'rxjs'; +import { LoginCredentials, AuthResponse } from './auth.entity'; +import { SetAuthToken, ClearAuth } from '@core/store/auth'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthService { + private readonly API_URL = 'VALID_API_URL'; + private readonly AUTH_TOKEN_KEY = ''; + + private readonly http: HttpClient = inject(HttpClient); + private readonly store: Store = inject(Store); + + //TODO: rewrite/refactor methods according to the API + async login(credentials: LoginCredentials): Promise { + try { + const response = await firstValueFrom( + this.http.post(`${this.API_URL}/auth/login`, credentials), + ); + + if (response.accessToken) { + this.setAuthToken(response.accessToken); + this.store.dispatch(new SetAuthToken(response.accessToken)); + } + } catch (error) { + console.error('Login failed:', error); + throw error; + } + } + + logout(): void { + localStorage.removeItem(this.AUTH_TOKEN_KEY); + this.store.dispatch(new ClearAuth()); + } + + getAuthToken(): string | null { + return localStorage.getItem(this.AUTH_TOKEN_KEY); + } + + private setAuthToken(token: string): void { + localStorage.setItem(this.AUTH_TOKEN_KEY, token); + } + + private checkInitialAuthState(): void { + const token: string | null = this.getAuthToken(); + if (token) { + this.store.dispatch(new SetAuthToken(token)); + } + } +} diff --git a/src/app/features/auth/sign-in/sign-in.component.html b/src/app/features/auth/sign-in/sign-in.component.html new file mode 100644 index 000000000..371a36b70 --- /dev/null +++ b/src/app/features/auth/sign-in/sign-in.component.html @@ -0,0 +1 @@ +

sign-in works!

diff --git a/src/app/features/auth/sign-in/sign-in.component.scss b/src/app/features/auth/sign-in/sign-in.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/auth/sign-in/sign-in.component.spec.ts b/src/app/features/auth/sign-in/sign-in.component.spec.ts new file mode 100644 index 000000000..d9432eda1 --- /dev/null +++ b/src/app/features/auth/sign-in/sign-in.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SignInComponent } from './sign-in.component'; + +describe('SignInComponent', () => { + let component: SignInComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SignInComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SignInComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/auth/sign-in/sign-in.component.ts b/src/app/features/auth/sign-in/sign-in.component.ts new file mode 100644 index 000000000..9190b9eb0 --- /dev/null +++ b/src/app/features/auth/sign-in/sign-in.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'osf-sign-in', + imports: [], + templateUrl: './sign-in.component.html', + styleUrl: './sign-in.component.scss', +}) +export class SignInComponent {} diff --git a/src/app/features/auth/sign-up/sign-up.component.html b/src/app/features/auth/sign-up/sign-up.component.html new file mode 100644 index 000000000..7e8b79f67 --- /dev/null +++ b/src/app/features/auth/sign-up/sign-up.component.html @@ -0,0 +1 @@ +

sign-up works!

diff --git a/src/app/features/auth/sign-up/sign-up.component.scss b/src/app/features/auth/sign-up/sign-up.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/auth/sign-up/sign-up.component.spec.ts b/src/app/features/auth/sign-up/sign-up.component.spec.ts new file mode 100644 index 000000000..6402b2691 --- /dev/null +++ b/src/app/features/auth/sign-up/sign-up.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SignUpComponent } from './sign-up.component'; + +describe('SignUpComponent', () => { + let component: SignUpComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SignUpComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SignUpComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/auth/sign-up/sign-up.component.ts b/src/app/features/auth/sign-up/sign-up.component.ts new file mode 100644 index 000000000..98abe8cbe --- /dev/null +++ b/src/app/features/auth/sign-up/sign-up.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'osf-sign-up', + imports: [], + templateUrl: './sign-up.component.html', + styleUrl: './sign-up.component.scss', +}) +export class SignUpComponent {}