Skip to content
10 changes: 5 additions & 5 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
8 changes: 8 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<main class="content-body">
<span>main-content works!</span>
<p>main-content works!</p>
</main>
9 changes: 9 additions & 0 deletions src/app/core/store/auth/auth.actions.ts
Original file line number Diff line number Diff line change
@@ -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';
}
4 changes: 4 additions & 0 deletions src/app/core/store/auth/auth.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface AuthStateModel {
accessToken: string | null;
isAuthenticated: boolean;
}
15 changes: 15 additions & 0 deletions src/app/core/store/auth/auth.selectors.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
30 changes: 30 additions & 0 deletions src/app/core/store/auth/auth.state.ts
Original file line number Diff line number Diff line change
@@ -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<AuthStateModel>({
name: 'auth',
defaults: {
accessToken: null,
isAuthenticated: false,
},
})
@Injectable()
export class AuthState {
@Action(SetAuthToken)
setAuthToken(ctx: StateContext<AuthStateModel>, action: SetAuthToken) {
ctx.patchState({
accessToken: action.accessToken,
isAuthenticated: true,
});
}

@Action(ClearAuth)
clearAuth(ctx: StateContext<AuthStateModel>) {
ctx.patchState({
accessToken: null,
isAuthenticated: false,
});
}
}
4 changes: 4 additions & 0 deletions src/app/core/store/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './auth.actions';
export * from './auth.model';
export * from './auth.selectors';
export * from './auth.state';
8 changes: 8 additions & 0 deletions src/app/features/auth/auth.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface LoginCredentials {
email: string;
password: string;
}

export interface AuthResponse {
accessToken: string;
}
54 changes: 54 additions & 0 deletions src/app/features/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -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: string = 'VALID_API_URL';
private readonly AUTH_TOKEN_KEY: string = '';

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<void> {
try {
const response: AuthResponse = await firstValueFrom(
this.http.post<AuthResponse>(`${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));
}
}
}
1 change: 1 addition & 0 deletions src/app/features/auth/sign-in/sign-in.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>sign-in works!</p>
Empty file.
22 changes: 22 additions & 0 deletions src/app/features/auth/sign-in/sign-in.component.spec.ts
Original file line number Diff line number Diff line change
@@ -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<SignInComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SignInComponent],
}).compileComponents();

fixture = TestBed.createComponent(SignInComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
9 changes: 9 additions & 0 deletions src/app/features/auth/sign-in/sign-in.component.ts
Original file line number Diff line number Diff line change
@@ -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 {}
1 change: 1 addition & 0 deletions src/app/features/auth/sign-up/sign-up.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>sign-up works!</p>
Empty file.
22 changes: 22 additions & 0 deletions src/app/features/auth/sign-up/sign-up.component.spec.ts
Original file line number Diff line number Diff line change
@@ -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<SignUpComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SignUpComponent],
}).compileComponents();

fixture = TestBed.createComponent(SignUpComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
9 changes: 9 additions & 0 deletions src/app/features/auth/sign-up/sign-up.component.ts
Original file line number Diff line number Diff line change
@@ -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 {}