login
This commit is contained in:
parent
bdb1fca186
commit
42a2f89ae3
@ -99,7 +99,8 @@
|
|||||||
"buildTarget": "host-one-euro:build:development"
|
"buildTarget": "host-one-euro:build:development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development",
|
||||||
|
|
||||||
},
|
},
|
||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n"
|
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||||
|
|||||||
1131
frontend/package-lock.json
generated
1131
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
|||||||
"@angular/platform-server": "^19.2.0",
|
"@angular/platform-server": "^19.2.0",
|
||||||
"@angular/router": "^19.2.0",
|
"@angular/router": "^19.2.0",
|
||||||
"@angular/ssr": "^19.2.10",
|
"@angular/ssr": "^19.2.10",
|
||||||
|
"@tailwindcss/cli": "^4.1.5",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
@ -30,6 +31,9 @@
|
|||||||
"@angular/compiler-cli": "^19.2.0",
|
"@angular/compiler-cli": "^19.2.0",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/node": "^18.18.0",
|
"@types/node": "^18.18.0",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"postcss": "^8.5.3",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "~5.7.2"
|
"typescript": "~5.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
6
frontend/postcss.config.js
Normal file
6
frontend/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
4
frontend/postcss.config.mjs
Normal file
4
frontend/postcss.config.mjs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export default {
|
||||||
|
plugins: ["@tailwindcss/postcss"],
|
||||||
|
};
|
||||||
|
|
||||||
@ -1,16 +1,21 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
|
import { NavbarComponent } from './shared/layout/navbar/navbar.component';
|
||||||
|
import { FooterComponent } from './shared/layout/footer/footer.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet],
|
standalone: true,
|
||||||
|
imports: [NavbarComponent, FooterComponent, RouterOutlet],
|
||||||
template: `
|
template: `
|
||||||
<h1>Welcome to {{title}}!</h1>
|
<app-navbar />
|
||||||
|
|
||||||
|
<main class="min-h-[calc(100vh-7rem)]">
|
||||||
<router-outlet />
|
<router-outlet />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<app-footer />
|
||||||
`,
|
`,
|
||||||
styles: [],
|
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {}
|
||||||
title = 'host-one-euro';
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,3 +1,9 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { LoginComponent } from './pages/auth/login/login.component';
|
||||||
|
import { HomeComponent } from './pages/landing/home/home.component';
|
||||||
|
import {Routes} from "@angular/router";
|
||||||
|
|
||||||
export const routes: Routes = [];
|
export const routes: Routes = [
|
||||||
|
{ path: '', component: HomeComponent }, // landing
|
||||||
|
{ path: 'login', component: LoginComponent }, // ← new
|
||||||
|
// { path: 'signup', component: SignupComponent },
|
||||||
|
];
|
||||||
140
frontend/src/app/pages/auth/login/login.component.ts
Normal file
140
frontend/src/app/pages/auth/login/login.component.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import { Component, inject } from '@angular/core';
|
||||||
|
import {
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormBuilder,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms';
|
||||||
|
import { Router, RouterLink } from '@angular/router';
|
||||||
|
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { throwError } from 'rxjs';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
standalone: true,
|
||||||
|
imports: [ReactiveFormsModule, RouterLink],
|
||||||
|
template: `
|
||||||
|
<section
|
||||||
|
class="flex min-h-screen items-center justify-center bg-slate-50 px-4">
|
||||||
|
<form
|
||||||
|
[formGroup]="form"
|
||||||
|
(ngSubmit)="submit()"
|
||||||
|
class="w-full max-w-md space-y-6 rounded-xl bg-white p-8 shadow-lg">
|
||||||
|
<h1 class="text-center text-2xl font-semibold">Log in</h1>
|
||||||
|
|
||||||
|
<!-- email -->
|
||||||
|
<div>
|
||||||
|
<label for="email" class="mb-1 block font-medium">Email</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
formControlName="email"
|
||||||
|
autocomplete="email"
|
||||||
|
required
|
||||||
|
class="w-full rounded-lg border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
*ngIf="
|
||||||
|
form.controls.email.invalid && form.controls.email.touched
|
||||||
|
"
|
||||||
|
class="mt-1 text-xs text-red-600">
|
||||||
|
Enter a valid email.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- password -->
|
||||||
|
<div>
|
||||||
|
<label for="password" class="mb-1 block font-medium">Password</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
formControlName="password"
|
||||||
|
autocomplete="current-password"
|
||||||
|
required
|
||||||
|
class="w-full rounded-lg border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
*ngIf="
|
||||||
|
form.controls.password.invalid && form.controls.password.touched
|
||||||
|
"
|
||||||
|
class="mt-1 text-xs text-red-600">
|
||||||
|
Password is required.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- remember + error -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<label class="flex items-center gap-2 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
formControlName="remember"
|
||||||
|
class="rounded border-slate-300 text-sky-600 focus:ring-sky-500" />
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<a routerLink="/forgot" class="text-xs text-sky-600 hover:underline"
|
||||||
|
>Forgot?</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<p *ngIf="error" class="text-sm text-red-600">{{ error }}</p>
|
||||||
|
|
||||||
|
<!-- submit -->
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
[disabled]="form.invalid || loading"
|
||||||
|
class="w-full rounded-full bg-sky-500 px-6 py-2 font-semibold text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60">
|
||||||
|
{{ loading ? 'Signing in…' : 'Sign in' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p class="text-center text-xs">
|
||||||
|
No account?
|
||||||
|
<a routerLink="/signup" class="text-sky-600 hover:underline"
|
||||||
|
>Create one</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class LoginComponent {
|
||||||
|
loading = false;
|
||||||
|
error = '';
|
||||||
|
|
||||||
|
private readonly fb = inject(FormBuilder);
|
||||||
|
|
||||||
|
readonly form = this.fb.group({
|
||||||
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
password: ['', Validators.required],
|
||||||
|
remember: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
if (this.form.invalid || this.loading) return;
|
||||||
|
this.loading = true;
|
||||||
|
this.error = '';
|
||||||
|
|
||||||
|
this.http
|
||||||
|
.post<{ token: string }>('/api/login', this.form.value)
|
||||||
|
.pipe(
|
||||||
|
catchError((err: HttpErrorResponse) => {
|
||||||
|
this.error =
|
||||||
|
err.status === 401
|
||||||
|
? 'Invalid credentials.'
|
||||||
|
: 'Login failed, please try again.';
|
||||||
|
this.loading = false;
|
||||||
|
return throwError(() => err);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.loading = false;
|
||||||
|
// TODO: store token in localStorage/cookie
|
||||||
|
this.router.navigateByUrl('/dashboard');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
33
frontend/src/app/pages/landing/faq/faq.component.ts
Normal file
33
frontend/src/app/pages/landing/faq/faq.component.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-faq',
|
||||||
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
<section class="py-16">
|
||||||
|
<h2 class="mb-8 text-center text-2xl font-bold">FAQ</h2>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-2xl space-y-4">
|
||||||
|
<details class="rounded-lg bg-slate-50 p-4">
|
||||||
|
<summary class="cursor-pointer font-medium">
|
||||||
|
Can I use my own domain?
|
||||||
|
</summary>
|
||||||
|
<p class="mt-2 text-sm text-slate-600">
|
||||||
|
Yes—add a CNAME to <code>yoursite.host-1euro.com</code> and we’ll
|
||||||
|
provision HTTPS automatically.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="rounded-lg bg-slate-50 p-4">
|
||||||
|
<summary class="cursor-pointer font-medium">
|
||||||
|
Is there a free trial?
|
||||||
|
</summary>
|
||||||
|
<p class="mt-2 text-sm text-slate-600">
|
||||||
|
You get 7 days free; no credit card required.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class FaqComponent {}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { NgFor } from '@angular/common';
|
||||||
|
|
||||||
|
interface Feature {
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-feature-grid',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgFor],
|
||||||
|
template: `
|
||||||
|
<section id="features" class="py-16">
|
||||||
|
<div class="mx-auto grid max-w-6xl gap-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<article *ngFor="let f of features" class="space-y-3">
|
||||||
|
<div class="text-2xl">{{ f.icon }}</div>
|
||||||
|
<h3 class="text-lg font-semibold">{{ f.title }}</h3>
|
||||||
|
<p class="text-sm text-slate-600">{{ f.text }}</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class FeatureGridComponent {
|
||||||
|
protected readonly features: Feature[] = [
|
||||||
|
{ icon: '🚀', title: 'Fast CDN', text: 'Edge-cached assets worldwide.' },
|
||||||
|
{ icon: '🔒', title: 'Free HTTPS', text: 'Auto-renewed Let’s Encrypt.' },
|
||||||
|
{ icon: '♻️', title: '1-click deploy', text: 'Push to Git, live in 30 s.' },
|
||||||
|
{ icon: '📈', title: 'Analytics', text: 'See traffic & referrers.' },
|
||||||
|
{ icon: '📦', title: 'Daily backups', text: 'Off-node, encrypted.' },
|
||||||
|
{ icon: '🛡', title: 'DDoS guard', text: 'Layer-7 filtering.' },
|
||||||
|
];
|
||||||
|
}
|
||||||
36
frontend/src/app/pages/landing/hero/hero.component.ts
Normal file
36
frontend/src/app/pages/landing/hero/hero.component.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterLink],
|
||||||
|
template: `
|
||||||
|
<section class="grid gap-10 py-24 md:grid-cols-2">
|
||||||
|
<div class="place-self-center text-center md:text-left">
|
||||||
|
<h1 class="max-w-xl text-4xl font-bold md:text-5xl">
|
||||||
|
Host your portfolio for
|
||||||
|
<span class="text-sky-500">1 € / month</span>
|
||||||
|
</h1>
|
||||||
|
<p class="mx-auto mt-4 max-w-prose text-slate-600 md:mx-0">
|
||||||
|
Lightning-fast global edge, free HTTPS, zero-click deploys.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a
|
||||||
|
routerLink="/signup"
|
||||||
|
class="mt-8 inline-block rounded-full bg-sky-500 px-8 py-3 font-semibold text-white transition hover:scale-105">
|
||||||
|
Get started
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<figure
|
||||||
|
class="relative aspect-video w-full overflow-hidden rounded-xl shadow-lg">
|
||||||
|
<img
|
||||||
|
src="assets/demo-dashboard.png"
|
||||||
|
alt="Dashboard preview"
|
||||||
|
class="absolute inset-0 h-full w-full object-cover" />
|
||||||
|
</figure>
|
||||||
|
</section>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class HeroComponent {}
|
||||||
26
frontend/src/app/pages/landing/home/home.component.ts
Normal file
26
frontend/src/app/pages/landing/home/home.component.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { HeroComponent } from '../hero/hero.component';
|
||||||
|
import { LogoStripComponent } from '../logo-strip/logo-strip.component';
|
||||||
|
import { FeatureGridComponent } from '../feature-grid/feature-grid.component';
|
||||||
|
import { PricingComponent } from '../pricing/pricing.component';
|
||||||
|
import { FaqComponent } from '../faq/faq.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
HeroComponent,
|
||||||
|
LogoStripComponent,
|
||||||
|
FeatureGridComponent,
|
||||||
|
PricingComponent,
|
||||||
|
FaqComponent,
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<app-hero />
|
||||||
|
<app-logo-strip />
|
||||||
|
<app-feature-grid />
|
||||||
|
<app-pricing />
|
||||||
|
<app-faq />
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class HomeComponent {}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-logo-strip',
|
||||||
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
<section class="bg-slate-50 py-6">
|
||||||
|
<p class="mb-4 text-center text-sm text-slate-500">
|
||||||
|
Trusted by 300+ creators
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-wrap items-center justify-center gap-8">
|
||||||
|
<img src="assets/logos/figma.svg" class="h-6 opacity-70" />
|
||||||
|
<img src="assets/logos/vue.svg" class="h-6 opacity-70" />
|
||||||
|
<img src="assets/logos/react.svg" class="h-6 opacity-70" />
|
||||||
|
<img src="assets/logos/aws.svg" class="h-6 opacity-70" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class LogoStripComponent {}
|
||||||
31
frontend/src/app/pages/landing/pricing/pricing.component.ts
Normal file
31
frontend/src/app/pages/landing/pricing/pricing.component.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-pricing',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterLink],
|
||||||
|
template: `
|
||||||
|
<section id="pricing" class="bg-slate-50 py-20">
|
||||||
|
<div
|
||||||
|
class="mx-auto max-w-sm rounded-2xl bg-white p-10 text-center shadow-lg ring-1 ring-slate-200">
|
||||||
|
<h2 class="mb-2 text-xl font-semibold">Portfolio tier</h2>
|
||||||
|
<p class="text-4xl font-bold text-sky-500">1 €</p>
|
||||||
|
<p class="mb-6 text-sm text-slate-500">per month</p>
|
||||||
|
|
||||||
|
<ul class="mb-8 space-y-2 text-left text-sm">
|
||||||
|
<li>• 500 MB disk</li>
|
||||||
|
<li>• 10 GB bandwidth</li>
|
||||||
|
<li>• 24/7 chat support</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a
|
||||||
|
routerLink="/signup"
|
||||||
|
class="inline-block rounded-full bg-sky-500 px-8 py-3 font-semibold text-white transition hover:scale-105">
|
||||||
|
Start free trial
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class PricingComponent {}
|
||||||
23
frontend/src/app/shared/layout/footer/footer.component.ts
Normal file
23
frontend/src/app/shared/layout/footer/footer.component.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-footer',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterLink],
|
||||||
|
template: `
|
||||||
|
<footer class="bg-slate-900 py-12 text-center text-slate-100">
|
||||||
|
<p class="mb-4 space-x-4 text-sm">
|
||||||
|
<a routerLink="/terms" class="hover:underline">Terms</a>
|
||||||
|
<a routerLink="/privacy" class="hover:underline">Privacy</a>
|
||||||
|
<a routerLink="/status" class="hover:underline">Status</a>
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-slate-400">
|
||||||
|
© {{ year }} host-1€
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class FooterComponent {
|
||||||
|
readonly year = new Date().getFullYear();
|
||||||
|
}
|
||||||
21
frontend/src/app/shared/layout/navbar/navbar.component.ts
Normal file
21
frontend/src/app/shared/layout/navbar/navbar.component.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-navbar',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterLink],
|
||||||
|
template: `
|
||||||
|
<nav
|
||||||
|
class="sticky top-0 z-50 flex h-14 items-center justify-between bg-white/90 px-6 shadow-sm backdrop-blur">
|
||||||
|
<a routerLink="/" class="text-lg font-bold text-sky-500">host-1€</a>
|
||||||
|
|
||||||
|
<ul class="hidden gap-6 md:flex">
|
||||||
|
<li><a href="#features" class="hover:text-sky-500">Features</a></li>
|
||||||
|
<li><a href="#pricing" class="hover:text-sky-500">Pricing</a></li>
|
||||||
|
<li><a routerLink="/login" class="hover:text-sky-500">Login</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class NavbarComponent {}
|
||||||
@ -1,6 +1,13 @@
|
|||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
import { provideRouter } from '@angular/router';
|
||||||
import { AppComponent } from './app/app.component';
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig)
|
import { AppComponent } from './app/app.component';
|
||||||
.catch((err) => console.error(err));
|
import {routes} from "./app/app.routes";
|
||||||
|
|
||||||
|
bootstrapApplication(AppComponent, {
|
||||||
|
providers: [
|
||||||
|
provideRouter(routes),
|
||||||
|
provideHttpClient(),
|
||||||
|
],
|
||||||
|
}).catch(console.error);
|
||||||
@ -1 +1,8 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* Tailwind v3 setup — works inside .scss */
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* your own tweaks below … */
|
||||||
|
body { font-family: system-ui, sans-serif; }
|
||||||
|
|
||||||
|
|||||||
6
frontend/tailwind.config.js
Normal file
6
frontend/tailwind.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ['./src/**/*.{html,ts}'], // keep this line
|
||||||
|
theme: { extend: {} },
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user