diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 878fe7c..aa9546d 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -341,7 +341,7 @@ dependencies = [ [[package]] name = "backend" -version = "0.1.4" +version = "0.1.6" dependencies = [ "async-trait", "axum", diff --git a/backend/Dockerfile b/backend/Dockerfile index 3ad2172..593849e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -14,23 +14,32 @@ RUN apt-get update && apt-get install -y \ sqlite3 \ && rm -rf /var/lib/apt/lists/* -# Copy dependencies manifests -COPY Cargo.toml Cargo.lock ./ -COPY migration/Cargo.toml ./migration/Cargo.toml -COPY entity/Cargo.toml ./entity/Cargo.toml - -# Create a dummy lib.rs to cache dependencies -RUN mkdir -p src && echo "fn main() {}" > src/main.rs -RUN mkdir -p migration/src && touch migration/src/lib.rs -RUN mkdir -p entity/src && touch entity/src/lib.rs +# Copy workspace Cargo files +COPY Cargo.toml ./ +COPY migration/Cargo.toml ./migration/ +COPY entity/Cargo.toml ./entity/ + +# Create dummy source files for dependency caching +RUN mkdir -p src && \ + echo "fn main() {}" > src/main.rs && \ + mkdir -p migration/src && \ + echo "pub use sea_orm_migration::prelude::*; pub struct Migrator; impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![] } }" > migration/src/lib.rs && \ + mkdir -p entity/src && \ + echo "pub mod entities; pub mod prelude; pub use entities::*;" > entity/src/lib.rs && \ + mkdir -p entity/src/entities && \ + echo "pub mod prelude { pub use super::*; }" > entity/src/entities/mod.rs + +# Build dependencies (this will be cached) +RUN cargo build --release || true + +# Copy all source code +COPY migration/src ./migration/src +COPY entity/src ./entity/src +COPY src ./src + +# Build the actual application RUN cargo build --release -# Copy the rest of the source code -COPY . . - -# Rebuild with real source -RUN touch src/main.rs && cargo build --release - ################### # Final Runtime Stage ################### diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev new file mode 100644 index 0000000..c58f0f5 --- /dev/null +++ b/backend/Dockerfile.dev @@ -0,0 +1,29 @@ +# Development Dockerfile for FinanceVault Backend +FROM rust:latest + +WORKDIR /usr/src/app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + sqlite3 \ + && rm -rf /var/lib/apt/lists/* + +# Install cargo-watch for hot reload +RUN cargo install cargo-watch + +# Create data directory for SQLite +RUN mkdir -p /data + +# Expose backend port +EXPOSE 8000 + +# Set environment variables +ENV DATABASE_URL=sqlite:/data/finance.db +ENV RUST_LOG=debug +ENV CARGO_HOME=/usr/local/cargo + +# The source code will be mounted as a volume +# Command will be overridden in docker-compose.dev.yml +CMD ["cargo", "watch", "-x", "run"] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 30e5597..c387783 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -22,7 +22,7 @@ services: - .env build: context: ./backend - dockerfile: Dockerfile + dockerfile: Dockerfile.dev ports: - "8000:8000" volumes: @@ -34,7 +34,8 @@ services: - DATABASE_URL=sqlite:/data/finance.db - CARGO_HOME=/usr/local/cargo - JWT_SECRET=${JWT_SECRET} - command: sh -c "cargo install cargo-watch && JWT_SECRET=${JWT_SECRET} cargo watch -x run" + - RUST_LOG=debug + command: cargo watch -x run volumes: cargo_registry: diff --git a/frontend/src/app.css b/frontend/src/app.css index ab38e2d..7e97c8d 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -58,10 +58,10 @@ --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.541 0.281 293.009); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); + --chart-1: oklch(59.651% 0.28069 318.49); + --chart-2: oklch(43.953% 0.21478 294.435); --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); + --chart-4: oklch(57.951% 0.2596 315.282); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.21 0.006 285.885); --sidebar-foreground: oklch(0.985 0 0); diff --git a/frontend/src/lib/components/budget/budget-overview-card.svelte b/frontend/src/lib/components/budget/budget-overview-card.svelte new file mode 100644 index 0000000..c1a1065 --- /dev/null +++ b/frontend/src/lib/components/budget/budget-overview-card.svelte @@ -0,0 +1,82 @@ + + +{#if overview} + + + Budget-Übersicht + + {new Date(overview.budget.month + "-01").toLocaleDateString("de-DE", { + month: "long", + year: "numeric", + })} + + + + +
+

Kategorien

+ {#each overview.categories as category} +
+
+ {category.category} + + €{category.spent.toFixed(2)} / €{category.allocated.toFixed(2)} + +
+ +
+ + {#if category.remaining < 0} + Überschritten um €{Math.abs(category.remaining).toFixed(2)} + {:else} + Verbleibend: €{category.remaining.toFixed(2)} + {/if} + + {category.percentage_used.toFixed(1)}% +
+
+ {/each} +
+
+
+{:else} + + +

+ Für diesen Monat ist noch kein Budget festgelegt +

+
+
+{/if} diff --git a/frontend/src/lib/components/budget/budget-radial-chart.svelte b/frontend/src/lib/components/budget/budget-radial-chart.svelte new file mode 100644 index 0000000..6825127 --- /dev/null +++ b/frontend/src/lib/components/budget/budget-radial-chart.svelte @@ -0,0 +1,184 @@ + + +{#if overview} + + + Budget-Verteilung + + {new Date(overview.budget.month + "-01").toLocaleDateString("de-DE", { + month: "long", + year: "numeric", + })} + + + + + + + {#snippet aboveMarks()} + + + + {/snippet} + {#snippet tooltip()} + + {/snippet} + + + +
+
+
+

Gesamtbudget

+

+ €{overview.budget.total_budget.toFixed(2)} +

+
+
+

Ausgegeben

+

+ €{overview.total_spent.toFixed(2)} +

+
+
+ + + +
+
+ {#if overview.remaining < 0} + + + Überschritten um €{Math.abs(overview.remaining).toFixed(2)} + + {:else if overview.percentage_used >= 80} + + + Verbleibend: €{overview.remaining.toFixed(2)} + + {:else} + + + Verbleibend: €{overview.remaining.toFixed(2)} + + {/if} +
+ {overview.percentage_used.toFixed(1)}% verwendet +
+
+
+
+{:else} + + +

+ Keine Budget-Daten verfügbar +

+
+
+{/if} diff --git a/frontend/src/lib/components/budget/budget-set-card.svelte b/frontend/src/lib/components/budget/budget-set-card.svelte new file mode 100644 index 0000000..7f5bdb0 --- /dev/null +++ b/frontend/src/lib/components/budget/budget-set-card.svelte @@ -0,0 +1,244 @@ + + + + + Budget festlegen + + Legen Sie Ihr monatliches Gesamtbudget fest und verteilen Sie es auf + Kategorien + + + + +
+ + +
+ + + {#if chartData.length > 0} +
+ + + {#snippet aboveMarks()} + + + {/snippet} + {#snippet tooltip()} + + {/snippet} + + + + + {#if chartData.length > 0} +
+ {#each chartData as item} +
+
+ {item.category} + €{item.amount.toFixed(2)} +
+ {/each} +
+ {/if} +
+ {/if} + + +
+
+ + +
+ + {#each localCategories as category, i} +
+
+ + +
+
+ + +
+ +
+ {/each} + + {#if localCategories.length === 0} +

+ Noch keine Kategorien hinzugefügt +

+ {/if} +
+ + +
+
diff --git a/frontend/src/lib/components/invoice/invoices-column.svelte b/frontend/src/lib/components/invoice/invoices-column.svelte new file mode 100644 index 0000000..218279a --- /dev/null +++ b/frontend/src/lib/components/invoice/invoices-column.svelte @@ -0,0 +1,120 @@ + + + + +
+
+ Rechnungen & Belege + Hochgeladene Dokumente zur Überprüfung +
+ +
+
+ +
+ {#each invoices as invoice} +
+
+
+ +
+

{invoice.description}

+

+ {formatDate(invoice.date)} +

+ {#if invoice.file_url} + + Dokument ansehen + + {/if} +
+
+
+ €{invoice.amount.toFixed(2)} + +
+
+
+ {invoice.category} + {#if invoice.verified} +
+ + Verifiziert +
+ {:else} +
+ + Nicht verifiziert +
+ {/if} +
+
+ {/each} + + {#if invoices.length === 0} +
+

Keine Belege vorhanden

+

+ Laden Sie Rechnungen oder Belege zur Überprüfung hoch +

+
+ {/if} +
+
+
diff --git a/frontend/src/lib/components/invoice/manual-entries-column.svelte b/frontend/src/lib/components/invoice/manual-entries-column.svelte new file mode 100644 index 0000000..0bb76a0 --- /dev/null +++ b/frontend/src/lib/components/invoice/manual-entries-column.svelte @@ -0,0 +1,107 @@ + + + + +
+
+ Manuelle Einträge + Einträge von Kontoauszügen oder Notizen +
+ +
+
+ +
+ {#each entries as entry} +
+
+
+

{entry.description}

+

+ {formatDate(entry.date)} +

+
+
+ €{entry.amount.toFixed(2)} + + +
+
+
+ {entry.category} + {#if entry.matched} + ✓ Abgeglichen + {:else} + Nicht abgeglichen + {/if} +
+
+ {/each} + + {#if entries.length === 0} +
+

Keine manuellen Einträge vorhanden

+

+ Fügen Sie Einträge von Ihrem Kontoauszug hinzu +

+
+ {/if} +
+
+
diff --git a/frontend/src/lib/components/invoice/match-overview.svelte b/frontend/src/lib/components/invoice/match-overview.svelte new file mode 100644 index 0000000..b70d426 --- /dev/null +++ b/frontend/src/lib/components/invoice/match-overview.svelte @@ -0,0 +1,73 @@ + + + + + Abgleich-Übersicht + Status der Rechnungsprüfung + + + {#if overview} +
+
+
+

Manuelle Einträge

+

{overview.total_manual}

+
+
+

Rechnungen

+

{overview.total_invoices}

+
+
+ +
+
+ +

{overview.matched_count}

+

Abgeglichen

+
+
+ +

{overview.unmatched_manual}

+

+ Nicht abgeglichen (Manuell) +

+
+
+ +

{overview.unmatched_invoices}

+

+ Nicht abgeglichen (Rechnungen) +

+
+
+ + +
+ {:else} +

+ Keine Daten für diesen Monat +

+ {/if} +
+
diff --git a/frontend/src/lib/components/login-form-client.svelte b/frontend/src/lib/components/login-form-client.svelte index 58c54f6..2794ccf 100644 --- a/frontend/src/lib/components/login-form-client.svelte +++ b/frontend/src/lib/components/login-form-client.svelte @@ -24,58 +24,6 @@ let password = $state(""); let isLoading = $state(false); let errorMessage = $state(""); - let particlesContainer: HTMLDivElement; - - // Initialize particles and lines when component mounts - $effect(() => { - if (particlesContainer) { - initializeAnimations(); - } - }); - - function initializeAnimations() { - // Clear existing particles - particlesContainer.innerHTML = ""; - - // Create particles - for (let i = 0; i < 30; i++) { - const particle = document.createElement("div"); - particle.className = "particle"; - - const startX = Math.random() * 100; - const startY = Math.random() * 100; - const endX = (Math.random() - 0.5) * 400; - const endY = (Math.random() - 0.5) * 400; - - particle.style.left = startX + "%"; - particle.style.top = startY + "%"; - particle.style.setProperty("--tx", endX + "px"); - particle.style.setProperty("--ty", endY + "px"); - particle.style.animationDelay = Math.random() * 15 + "s"; - particle.style.animationDuration = 10 + Math.random() * 10 + "s"; - - particlesContainer.appendChild(particle); - } - - // Create lines - for (let i = 0; i < 5; i++) { - const line = document.createElement("div"); - line.className = "animated-line"; - - const width = 100 + Math.random() * 200; - const startY = Math.random() * 100; - const angle = (Math.random() - 0.5) * 30; - - line.style.width = width + "px"; - line.style.top = startY + "%"; - line.style.left = "-200px"; - line.style.setProperty("--angle", angle + "deg"); - line.style.animationDelay = Math.random() * 8 + "s"; - line.style.animationDuration = 6 + Math.random() * 4 + "s"; - - particlesContainer.appendChild(line); - } - } async function handleSubmit(event: Event) { event.preventDefault(); @@ -111,24 +59,6 @@ isLoading = false; } } - - const features = [ - { - icon: "🔒", - title: "Sicherheit", - description: "Bank-grade Verschlüsselung", - }, - { - icon: "📊", - title: "Analytics", - description: "Detaillierte Finanzanalysen", - }, - { - icon: "⚡", - title: "Schnell", - description: "Blitzschnelle Performance", - }, - ];
@@ -214,188 +144,38 @@ diff --git a/frontend/src/lib/components/navbar/app-sidebar.svelte b/frontend/src/lib/components/navbar/app-sidebar.svelte index 41be0a7..abb2143 100644 --- a/frontend/src/lib/components/navbar/app-sidebar.svelte +++ b/frontend/src/lib/components/navbar/app-sidebar.svelte @@ -1,23 +1,12 @@ -
- - -
- -
-

Create account

-

- Sign up for your FinanceVault account -

-
+
+ +
+
+
+

Create your account

+

+ Sign up for your FinanceVault account +

+
- {#if errorMessage} - - {errorMessage} - - {/if} + + {#if errorMessage} + + {errorMessage} + + {/if} + - Username + Username - Password + Password {#if passwordError}

{passwordError}

@@ -129,67 +188,83 @@
- Confirm Password + Confirm Password {#if confirmError}

{confirmError}

{/if}
- -
-

Password requirements:

-
    -
  • = 8}> - • At least 8 characters -
  • -
  • - • One lowercase letter -
  • -
  • - • One uppercase letter -
  • -
  • - • One number -
  • -
-
- - - - + - - Already have an account? + Already have an account? + Sign in - +
- +
+ + +
+ + diff --git a/frontend/src/lib/components/ui/progress/index.ts b/frontend/src/lib/components/ui/progress/index.ts new file mode 100644 index 0000000..25eee61 --- /dev/null +++ b/frontend/src/lib/components/ui/progress/index.ts @@ -0,0 +1,7 @@ +import Root from "./progress.svelte"; + +export { + Root, + // + Root as Progress, +}; diff --git a/frontend/src/lib/components/ui/progress/progress.svelte b/frontend/src/lib/components/ui/progress/progress.svelte new file mode 100644 index 0000000..6833013 --- /dev/null +++ b/frontend/src/lib/components/ui/progress/progress.svelte @@ -0,0 +1,27 @@ + + + +
+
diff --git a/frontend/src/lib/services/budget.ts b/frontend/src/lib/services/budget.ts new file mode 100644 index 0000000..ea49b49 --- /dev/null +++ b/frontend/src/lib/services/budget.ts @@ -0,0 +1,83 @@ +import { ApiClient } from './api-client'; +import type { + Budget, + CreateBudgetRequest, + UpdateBudgetRequest, + BudgetOverview +} from '$lib/types'; + +export class BudgetService { + static async getBudgets(token: string): Promise { + const response = await ApiClient.get('/budgets', token); + + if (!response.ok) { + throw new Error('Failed to fetch budgets'); + } + + return await response.json(); + } + + static async getBudgetByMonth(month: string, token: string): Promise { + const response = await ApiClient.get(`/budgets/${month}`, token); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('Budget not found for this month'); + } + throw new Error('Failed to fetch budget'); + } + + return await response.json(); + } + + static async getBudgetOverview(month: string, token: string): Promise { + const response = await ApiClient.get(`/budgets/${month}/overview`, token); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('Budget not found for this month'); + } + throw new Error('Failed to fetch budget overview'); + } + + return await response.json(); + } + + static async createBudget(budget: CreateBudgetRequest, token: string): Promise { + const response = await ApiClient.post('/budgets', budget, token); + + if (!response.ok) { + throw new Error('Failed to create budget'); + } + + return await response.json(); + } + + static async updateBudget( + month: string, + budget: UpdateBudgetRequest, + token: string + ): Promise { + const response = await ApiClient.put(`/budgets/${month}`, budget, token); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('Budget not found'); + } + throw new Error('Failed to update budget'); + } + + return await response.json(); + } + + static async deleteBudget(month: string, token: string): Promise { + const response = await ApiClient.delete(`/budgets/${month}`, token); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('Budget not found'); + } + throw new Error('Failed to delete budget'); + } + } +} diff --git a/frontend/src/lib/services/invoice.ts b/frontend/src/lib/services/invoice.ts new file mode 100644 index 0000000..057230c --- /dev/null +++ b/frontend/src/lib/services/invoice.ts @@ -0,0 +1,125 @@ +import { ApiClient } from './api-client'; +import type { + Invoice, + ManualEntry, + CreateInvoiceRequest, + CreateManualEntryRequest, + InvoiceOverview +} from '$lib/types'; + +export class InvoiceService { + static async getInvoices(month: string, token: string): Promise { + const response = await ApiClient.get(`/invoices?month=${month}`, token); + + if (!response.ok) { + throw new Error('Failed to fetch invoices'); + } + + return await response.json(); + } + + static async getInvoice(id: string, token: string): Promise { + const response = await ApiClient.get(`/invoices/${id}`, token); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('Invoice not found'); + } + throw new Error('Failed to fetch invoice'); + } + + return await response.json(); + } + + static async createInvoice(invoice: CreateInvoiceRequest, token: string): Promise { + const formData = new FormData(); + formData.append('month', invoice.month); + formData.append('description', invoice.description); + formData.append('amount', invoice.amount.toString()); + formData.append('date', invoice.date); + formData.append('category', invoice.category); + + if (invoice.file) { + formData.append('file', invoice.file); + } + + const response = await ApiClient.fetch('/invoices', { + method: 'POST', + body: formData + }, token); + + if (!response.ok) { + throw new Error('Failed to create invoice'); + } + + return await response.json(); + } + + static async deleteInvoice(id: string, token: string): Promise { + const response = await ApiClient.delete(`/invoices/${id}`, token); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('Invoice not found'); + } + throw new Error('Failed to delete invoice'); + } + } + + static async getManualEntries(month: string, token: string): Promise { + const response = await ApiClient.get(`/manual-entries?month=${month}`, token); + + if (!response.ok) { + throw new Error('Failed to fetch manual entries'); + } + + return await response.json(); + } + + static async createManualEntry( + entry: CreateManualEntryRequest, + token: string + ): Promise { + const response = await ApiClient.post('/manual-entries', entry, token); + + if (!response.ok) { + throw new Error('Failed to create manual entry'); + } + + return await response.json(); + } + + static async deleteManualEntry(id: string, token: string): Promise { + const response = await ApiClient.delete(`/manual-entries/${id}`, token); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('Manual entry not found'); + } + throw new Error('Failed to delete manual entry'); + } + } + + static async getInvoiceOverview(month: string, token: string): Promise { + const response = await ApiClient.get(`/invoices/${month}/overview`, token); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('No data found for this month'); + } + throw new Error('Failed to fetch invoice overview'); + } + + return await response.json(); + } + + static async matchInvoices(month: string, token: string): Promise { + const response = await ApiClient.post(`/invoices/${month}/match`, {}, token); + + if (!response.ok) { + throw new Error('Failed to match invoices'); + } + + return await response.json(); + } +} diff --git a/frontend/src/lib/types/index.ts b/frontend/src/lib/types/index.ts new file mode 100644 index 0000000..e4821be --- /dev/null +++ b/frontend/src/lib/types/index.ts @@ -0,0 +1,116 @@ +// Re-export types from services +export type { Expense, CreateExpenseRequest, UpdateExpenseRequest } from '../services/expenses'; +export type { Subscription, CreateSubscriptionRequest, UpdateSubscriptionRequest } from '../services/subscriptions'; + +// Budget types +export interface Budget { + id: string; + user_id: string; + month: string; // YYYY-MM format + total_budget: number; + categories: BudgetCategory[]; + created_at: string; + updated_at: string; +} + +export interface BudgetCategory { + category: string; + allocated_amount: number; + spent_amount: number; +} + +export interface CreateBudgetRequest { + month: string; + total_budget: number; + categories: { + category: string; + allocated_amount: number; + }[]; +} + +export interface UpdateBudgetRequest { + total_budget?: number; + categories?: { + category: string; + allocated_amount: number; + }[]; +} + +export interface BudgetOverview { + budget: Budget; + total_spent: number; + remaining: number; + percentage_used: number; + categories: { + category: string; + allocated: number; + spent: number; + remaining: number; + percentage_used: number; + }[]; +} + +// Invoice types +export interface Invoice { + id: string; + user_id: string; + month: string; // YYYY-MM format + description: string; + amount: number; + date: string; + category: string; + file_url?: string; + verified: boolean; + created_at: string; + updated_at: string; +} + +export interface ManualEntry { + id: string; + user_id: string; + month: string; + description: string; + amount: number; + date: string; + category: string; + matched: boolean; + matched_invoice_id?: string; + created_at: string; + updated_at: string; +} + +export interface CreateInvoiceRequest { + month: string; + description: string; + amount: number; + date: string; + category: string; + file?: File; +} + +export interface CreateManualEntryRequest { + month: string; + description: string; + amount: number; + date: string; + category: string; +} + +export interface InvoiceMatch { + invoice_id: string; + manual_entry_id: string; + match_confidence: number; + matched_at: string; +} + +export interface InvoiceOverview { + month: string; + invoices: Invoice[]; + manual_entries: ManualEntry[]; + matches: InvoiceMatch[]; + total_invoices: number; + total_manual: number; + matched_count: number; + unmatched_invoices: number; + unmatched_manual: number; +} diff --git a/frontend/src/routes/budgets/+page.svelte b/frontend/src/routes/budgets/+page.svelte new file mode 100644 index 0000000..756249f --- /dev/null +++ b/frontend/src/routes/budgets/+page.svelte @@ -0,0 +1,253 @@ + + +
+
+ +
+

Budget-Verwaltung

+
+ + + +
+
+ + {#if loading} +
+

Lade Budget-Daten...

+
+ {:else} + +
+ +
+ +
+ + +
+ +
+
+ {/if} +
+
diff --git a/frontend/src/routes/invoices/+page.svelte b/frontend/src/routes/invoices/+page.svelte new file mode 100644 index 0000000..36e598a --- /dev/null +++ b/frontend/src/routes/invoices/+page.svelte @@ -0,0 +1,460 @@ + + +
+
+ +
+

Rechnungsprüfung

+
+ + + +
+
+ + {#if loading} +
+

Lade Daten...

+
+ {:else} + + + + +
+ + + + + +
+ {/if} +
+
+ + + + + + Manuellen Eintrag hinzufügen + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + + + {#each categories as category} + (manualEntryForm.category = category)} + > + {category} + + {/each} + + +
+
+ + + + +
+
+ + + + + + Beleg hochladen + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + + + {#each categories as category} + (invoiceForm.category = category)} + > + {category} + + {/each} + + +
+
+ + + {#if invoiceForm.file} +

+ Ausgewählt: {invoiceForm.file.name} +

+ {/if} +
+
+ + + + +
+
diff --git a/frontend/src/routes/signup/+page.svelte b/frontend/src/routes/signup/+page.svelte index 38018ca..913ec9c 100644 --- a/frontend/src/routes/signup/+page.svelte +++ b/frontend/src/routes/signup/+page.svelte @@ -2,8 +2,4 @@ import SignupForm from "$lib/components/signup-form-client.svelte"; -
-
- -
-
+ diff --git a/frontend/static/Gradientsv3.svg b/frontend/static/Gradientsv3.svg new file mode 100644 index 0000000..dce60b2 --- /dev/null +++ b/frontend/static/Gradientsv3.svg @@ -0,0 +1 @@ + \ No newline at end of file