diff --git a/frontend/src/app/core/interceptors/sanctum-interceptor.spec.ts b/frontend/src/app/core/interceptors/sanctum-interceptor.spec.ts
new file mode 100644
index 0000000..6fb3bef
--- /dev/null
+++ b/frontend/src/app/core/interceptors/sanctum-interceptor.spec.ts
@@ -0,0 +1,17 @@
+import { TestBed } from '@angular/core/testing';
+import { HttpInterceptorFn } from '@angular/common/http';
+
+import { sanctumInterceptor } from './sanctum-interceptor';
+
+describe('sanctumInterceptor', () => {
+ const interceptor: HttpInterceptorFn = (req, next) =>
+ TestBed.runInInjectionContext(() => sanctumInterceptor(req, next));
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ });
+
+ it('should be created', () => {
+ expect(interceptor).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/core/interceptors/sanctum-interceptor.ts b/frontend/src/app/core/interceptors/sanctum-interceptor.ts
new file mode 100644
index 0000000..25fcb89
--- /dev/null
+++ b/frontend/src/app/core/interceptors/sanctum-interceptor.ts
@@ -0,0 +1,20 @@
+import { HttpInterceptorFn } from "@angular/common/http";
+
+export const sanctumInterceptor: HttpInterceptorFn = (req, next) => {
+ const getCookie = (name: string): string | null => {
+ const match = document.cookie.match(new RegExp("(^|;\\s*)(" + name + ")=([^;]*)"));
+ return match ? decodeURIComponent(match[3]) : null;
+ };
+
+ let headers = req.headers.set("Accept", "application/json");
+ const xsrfToken = getCookie("XSRF-TOKEN");
+ if (xsrfToken) {
+ headers = headers.set("X-XSRF-TOKEN", xsrfToken);
+ }
+ const clonedRequest = req.clone({
+ withCredentials: true,
+ headers: headers,
+ });
+
+ return next(clonedRequest);
+};
diff --git a/frontend/src/app/core/layout/header/header.css b/frontend/src/app/core/layout/header/header.css
new file mode 100644
index 0000000..e5ccefc
--- /dev/null
+++ b/frontend/src/app/core/layout/header/header.css
@@ -0,0 +1,4 @@
+:host{
+ display: block;
+ width: 100svw;
+}
diff --git a/frontend/src/app/core/layout/header/header.html b/frontend/src/app/core/layout/header/header.html
new file mode 100644
index 0000000..1ec8b7c
--- /dev/null
+++ b/frontend/src/app/core/layout/header/header.html
@@ -0,0 +1,41 @@
+
diff --git a/frontend/src/app/core/layout/header/header.spec.ts b/frontend/src/app/core/layout/header/header.spec.ts
new file mode 100644
index 0000000..9ef7403
--- /dev/null
+++ b/frontend/src/app/core/layout/header/header.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Header } from './header';
+
+describe('Header', () => {
+ let component: Header;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [Header],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(Header);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/core/layout/header/header.ts b/frontend/src/app/core/layout/header/header.ts
new file mode 100644
index 0000000..eb4a806
--- /dev/null
+++ b/frontend/src/app/core/layout/header/header.ts
@@ -0,0 +1,14 @@
+import { Component, inject} from '@angular/core';
+import { RouterLink } from '@angular/router';
+import {AuthStore} from '../../../auth/auth.store';
+
+@Component({
+ selector: 'app-header',
+ imports: [RouterLink],
+ templateUrl: './header.html',
+ styleUrl: './header.css',
+})
+export class Header {
+ protected readonly authStore = inject(AuthStore);
+
+}
diff --git a/frontend/src/app/core/tokens/api-urls.ts b/frontend/src/app/core/tokens/api-urls.ts
new file mode 100644
index 0000000..5dd5387
--- /dev/null
+++ b/frontend/src/app/core/tokens/api-urls.ts
@@ -0,0 +1,12 @@
+import { InjectionToken } from "@angular/core";
+import { environment } from "../../../environments/environment";
+
+export const API_URL = new InjectionToken("API_URL", {
+ providedIn: "root",
+ factory: () => environment.apiUrl,
+});
+
+export const BASE_URL = new InjectionToken("BASE_URL", {
+ providedIn: "root",
+ factory: () => environment.baseApiUrl,
+});
diff --git a/frontend/src/app/shared/validation-errors/validation-errors.css b/frontend/src/app/shared/validation-errors/validation-errors.css
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/app/shared/validation-errors/validation-errors.html b/frontend/src/app/shared/validation-errors/validation-errors.html
new file mode 100644
index 0000000..78f1525
--- /dev/null
+++ b/frontend/src/app/shared/validation-errors/validation-errors.html
@@ -0,0 +1,21 @@
+@if (errorMessages().length > 0) {
+
+
+
+
+
There were errors with your submission
+
+
+ @for (error of errorMessages(); track error) {
+ - {{ error }}
+ }
+
+
+
+
+
+}
diff --git a/frontend/src/app/shared/validation-errors/validation-errors.spec.ts b/frontend/src/app/shared/validation-errors/validation-errors.spec.ts
new file mode 100644
index 0000000..4dbcf87
--- /dev/null
+++ b/frontend/src/app/shared/validation-errors/validation-errors.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ValidationErrors } from './validation-errors';
+
+describe('ValidationErrors', () => {
+ let component: ValidationErrors;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ValidationErrors],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ValidationErrors);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/shared/validation-errors/validation-errors.ts b/frontend/src/app/shared/validation-errors/validation-errors.ts
new file mode 100644
index 0000000..dc33151
--- /dev/null
+++ b/frontend/src/app/shared/validation-errors/validation-errors.ts
@@ -0,0 +1,18 @@
+import { Component, computed, input } from '@angular/core';
+
+@Component({
+ selector: 'app-validation-errors',
+ imports: [],
+ templateUrl: './validation-errors.html',
+ styleUrl: './validation-errors.css',
+})
+export class ValidationErrors {
+ errors = input | null | undefined>();
+
+ errorMessages = computed(() => {
+ const errs = this.errors();
+ if (!errs) return [];
+
+ return Object.values(errs).reduce((acc, curr) => acc.concat(curr), []);
+ });
+}
diff --git a/frontend/src/environments/environment.development.ts b/frontend/src/environments/environment.development.ts
new file mode 100644
index 0000000..4315e2b
--- /dev/null
+++ b/frontend/src/environments/environment.development.ts
@@ -0,0 +1,5 @@
+export const environment = {
+ production: false,
+ apiUrl: 'http://localhost:8000/api',
+ baseApiUrl: 'http://localhost:8000',
+};
diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts
new file mode 100644
index 0000000..d1f00ff
--- /dev/null
+++ b/frontend/src/environments/environment.ts
@@ -0,0 +1,5 @@
+export const environment = {
+ production: true,
+ apiUrl: 'my-deverything-api.com/api',
+ baseApiUrl: 'my-deverything-api.com',
+};
diff --git a/frontend/src/index.html b/frontend/src/index.html
index 3af61ec..06cfd45 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -7,7 +7,7 @@
-
+
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 70d3686..0ed28b4 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -1,3 +1,15 @@
/* You can add global styles to this file, and also import other style files */
-
+@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600&display=swap');
@import 'tailwindcss';
+
+body {
+ color: white;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ font-family: 'Outfit', sans-serif;
+ background: radial-gradient(circle at top right, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
+}