commit 29c1e8ce279dffb14e346383e1b8ecd732bd8ad6
Author: root <root@hub.scroll.pub>
Date: 2024-12-27 17:33:41 +0000
Subject: Initial commit
diff --git a/body.html b/body.html
new file mode 100644
index 0000000..456c4c7
--- /dev/null
+++ b/body.html
@@ -0,0 +1,54 @@
+<header class="header">
+ <div class="container">
+ <h1 class="logo">HustleTrack</h1>
+ <nav class="nav">
+ <a href="#dashboard" class="nav-link">Dashboard</a>
+ <a href="#categories" class="nav-link">Categories</a>
+ <a href="#logout" class="nav-link">Logout</a>
+ </nav>
+ </div>
+</header>
+
+<main class="main">
+ <section id="dashboard" class="dashboard">
+ <div class="container">
+ <h2 class="section-title">Dashboard</h2>
+ <div class="summary-card">
+ <h3>Total Income This Month</h3>
+ <p class="income-total">$0.00</p>
+ </div>
+ <div class="chart-container">
+ <canvas id="incomeChart"></canvas>
+ </div>
+ <button class="add-income-btn">Add Income Entry</button>
+ </div>
+ </section>
+
+ <section id="categories" class="categories">
+ <div class="container">
+ <h2 class="section-title">Categories</h2>
+ <div class="category-list">
+ <div class="category-item">
+ <span>Freelance</span>
+ <button class="edit-btn">Edit</button>
+ <button class="delete-btn">Delete</button>
+ </div>
+ <div class="category-item">
+ <span>E-commerce</span>
+ <button class="edit-btn">Edit</button>
+ <button class="delete-btn">Delete</button>
+ </div>
+ </div>
+ <form class="add-category-form">
+ <input type="text" placeholder="New Category Name" required>
+ <button type="submit">Add Category</button>
+ </form>
+ </div>
+ </section>
+</main>
+
+<footer class="footer">
+ <div class="container">
+ <p>Track your side hustle income with ease.</p>
+ </div>
+</footer>
\ No newline at end of file
diff --git a/index.scroll b/index.scroll
new file mode 100644
index 0000000..4efffab
--- /dev/null
+++ b/index.scroll
@@ -0,0 +1,8 @@
+buildHtml
+baseUrl https://hustletrack.scroll.pub
+metaTags
+editButton /edit.html
+title HustleTrack - Side Hustle Income Tracker
+style.css
+body.html
+script.js
\ No newline at end of file
diff --git a/prompt.json b/prompt.json
new file mode 100644
index 0000000..27dae71
--- /dev/null
+++ b/prompt.json
@@ -0,0 +1,25 @@
+{
+ "id": "d5a0a19c-873e-48ac-8715-a8db4d5d38db",
+ "object": "chat.completion",
+ "created": 1735320797,
+ "model": "deepseek-chat",
+ "choices": [
+ {
+ "index": 0,
+ "message": {
+ "role": "assistant",
+ "content": "---domain---\nhustletrack.scroll.pub\n---index.scroll---\nbuildHtml\nbaseUrl https://hustletrack.scroll.pub\nmetaTags\neditButton /edit.html\ntitle HustleTrack - Side Hustle Income Tracker\nstyle.css\nbody.html\nscript.js\n---body.html---\n<header class=\"header\">\n <div class=\"container\">\n <h1 class=\"logo\">HustleTrack</h1>\n <nav class=\"nav\">\n <a href=\"#dashboard\" class=\"nav-link\">Dashboard</a>\n <a href=\"#categories\" class=\"nav-link\">Categories</a>\n <a href=\"#logout\" class=\"nav-link\">Logout</a>\n </nav>\n </div>\n</header>\n\n<main class=\"main\">\n <section id=\"dashboard\" class=\"dashboard\">\n <div class=\"container\">\n <h2 class=\"section-title\">Dashboard</h2>\n <div class=\"summary-card\">\n <h3>Total Income This Month</h3>\n <p class=\"income-total\">$0.00</p>\n </div>\n <div class=\"chart-container\">\n <canvas id=\"incomeChart\"></canvas>\n </div>\n <button class=\"add-income-btn\">Add Income Entry</button>\n </div>\n </section>\n\n <section id=\"categories\" class=\"categories\">\n <div class=\"container\">\n <h2 class=\"section-title\">Categories</h2>\n <div class=\"category-list\">\n <div class=\"category-item\">\n <span>Freelance</span>\n <button class=\"edit-btn\">Edit</button>\n <button class=\"delete-btn\">Delete</button>\n </div>\n <div class=\"category-item\">\n <span>E-commerce</span>\n <button class=\"edit-btn\">Edit</button>\n <button class=\"delete-btn\">Delete</button>\n </div>\n </div>\n <form class=\"add-category-form\">\n <input type=\"text\" placeholder=\"New Category Name\" required>\n <button type=\"submit\">Add Category</button>\n </form>\n </div>\n </section>\n</main>\n\n<footer class=\"footer\">\n <div class=\"container\">\n <p>Track your side hustle income with ease.</p>\n </div>\n</footer>\n---style.css---\n:root {\n --primary-color: #4f46e5;\n --secondary-color: #1e293b;\n --background-color: #f8fafc;\n --text-color: #1e293b;\n --border-color: #e2e8f0;\n --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n}\n\nbody {\n font-family: 'Inter', sans-serif;\n color: var(--text-color);\n background-color: var(--background-color);\n margin: 0;\n padding: 0;\n line-height: 1.6;\n}\n\n.container {\n max-width: 1200px;\n margin: 0 auto;\n padding: 0 20px;\n}\n\n.header {\n background-color: var(--primary-color);\n color: white;\n padding: 20px 0;\n box-shadow: var(--shadow);\n}\n\n.logo {\n font-size: 1.5rem;\n font-weight: bold;\n margin: 0;\n}\n\n.nav {\n display: flex;\n gap: 20px;\n margin-top: 10px;\n}\n\n.nav-link {\n color: white;\n text-decoration: none;\n font-weight: 500;\n}\n\n.main {\n padding: 40px 0;\n}\n\n.section-title {\n font-size: 1.75rem;\n margin-bottom: 20px;\n color: var(--secondary-color);\n}\n\n.summary-card {\n background: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: var(--shadow);\n margin-bottom: 20px;\n}\n\n.income-total {\n font-size: 2rem;\n font-weight: bold;\n color: var(--primary-color);\n margin: 10px 0;\n}\n\n.chart-container {\n background: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: var(--shadow);\n margin-bottom: 20px;\n}\n\n.add-income-btn {\n background: var(--primary-color);\n color: white;\n border: none;\n padding: 10px 20px;\n border-radius: 5px;\n cursor: pointer;\n font-weight: 500;\n}\n\n.category-list {\n margin-bottom: 20px;\n}\n\n.category-item {\n background: white;\n padding: 15px;\n border-radius: 8px;\n box-shadow: var(--shadow);\n margin-bottom: 10px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.edit-btn, .delete-btn {\n background: none;\n border: none;\n color: var(--primary-color);\n cursor: pointer;\n font-weight: 500;\n}\n\n.add-category-form {\n display: flex;\n gap: 10px;\n}\n\n.add-category-form input {\n flex: 1;\n padding: 10px;\n border: 1px solid var(--border-color);\n border-radius: 5px;\n}\n\n.add-category-form button {\n background: var(--primary-color);\n color: white;\n border: none;\n padding: 10px 20px;\n border-radius: 5px;\n cursor: pointer;\n font-weight: 500;\n}\n\n.footer {\n background: var(--secondary-color);\n color: white;\n text-align: center;\n padding: 20px 0;\n margin-top: 40px;\n}\n\n@media (max-width: 768px) {\n .nav {\n flex-direction: column;\n gap: 10px;\n }\n\n .container {\n padding: 0 10px;\n }\n\n .summary-card, .chart-container, .category-item {\n padding: 15px;\n }\n\n .add-category-form {\n flex-direction: column;\n }\n\n .add-category-form button {\n width: 100%;\n }\n}\n---script.js---\ndocument.addEventListener('DOMContentLoaded', function() {\n const ctx = document.getElementById('incomeChart').getContext('2d');\n const incomeChart = new Chart(ctx, {\n type: 'line',\n data: {\n labels: ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7'],\n datasets: [{\n label: 'Income',\n data: [50, 100, 75, 125, 150, 200, 175],\n borderColor: '#4f46e5',\n borderWidth: 2,\n fill: false\n }]\n },\n options: {\n scales: {\n y: {\n beginAtZero: true\n }\n }\n }\n });\n\n document.querySelector('.add-income-btn').addEventListener('click', function() {\n alert('Add Income Entry functionality will be implemented soon.');\n });\n\n document.querySelector('.add-category-form').addEventListener('submit', function(e) {\n e.preventDefault();\n const categoryName = this.querySelector('input').value;\n if (categoryName) {\n const categoryItem = document.createElement('div');\n categoryItem.className = 'category-item';\n categoryItem.innerHTML = `\n <span>${categoryName}</span>\n <button class=\"edit-btn\">Edit</button>\n <button class=\"delete-btn\">Delete</button>\n `;\n document.querySelector('.category-list').appendChild(categoryItem);\n this.querySelector('input').value = '';\n }\n });\n\n document.querySelector('.category-list').addEventListener('click', function(e) {\n if (e.target.classList.contains('delete-btn')) {\n e.target.closest('.category-item').remove();\n } else if (e.target.classList.contains('edit-btn')) {\n const categoryItem = e.target.closest('.category-item');\n const categoryName = categoryItem.querySelector('span').textContent;\n const newName = prompt('Edit category name:', categoryName);\n if (newName) {\n categoryItem.querySelector('span').textContent = newName;\n }\n }\n });\n});\n---end---"
+ },
+ "logprobs": null,
+ "finish_reason": "stop"
+ }
+ ],
+ "usage": {
+ "prompt_tokens": 1056,
+ "completion_tokens": 1829,
+ "total_tokens": 2885,
+ "prompt_cache_hit_tokens": 0,
+ "prompt_cache_miss_tokens": 1056
+ },
+ "system_fingerprint": "fp_f1afce2943"
+}
\ No newline at end of file
diff --git a/readme.scroll b/readme.scroll
new file mode 100644
index 0000000..5831d5f
--- /dev/null
+++ b/readme.scroll
@@ -0,0 +1,2 @@
+# hustletrack.scroll.pub
+Website generated by DeepSeek from prompt: You are a skilled full-stack developer tasked with creating an MVP (Minimum Viable Product) of a Side Hustle Income Tracker web application. The goal is to build a functional prototype that allows users to sign up, authenticate via Google, track their daily income, categorize earnings, and view basic analytics. Key Requirements: Technology Stack: Frontend: Vite, React, TypeScript, Tailwind CSS, shadcn-ui Backend: NestJS framework. Database: PostgreSQL (use an ORM like TypeORM). Authentication: Implement Google OAuth integration for user sign-in. Use JWT for session handling. Deployment Setup: Assume we will eventually deploy on AWS, but for now, focus on local development and a containerized setup (Docker). Initial Features: User Authentication: Implement Google OAuth for sign-in. Store user information (e.g., email, display name) in the database upon first login. Income Entries: A form to add a new income entry with fields: amount, category, date, and optional notes. A simple backend endpoint to store income entries associated with the authenticated user. Categories: Ability for users to create and manage their own income categories (e.g., "Freelance", "E-commerce Sales"). Store categories in the database, linked to each user. Basic Analytics: A dashboard page displaying a summary of total income over the last 30 days. A simple chart (using Chart.js) to visualize daily income totals over time (mock data first, then integrate after entries are recorded). User Interface Requirements: Home/Dashboard Page: After login, show a summary card of total income this month. Include a line chart of the last 30 days’ income. Provide a button to add a new income entry. Income Entry Page: Form to input amount, select category, pick a date, and add optional notes. Upon submission, send data to backend and return to dashboard or show success message. Category Management Page: Simple list of categories with "Add New Category" form. Options to edit or delete categories. Header & Navigation: Show user’s name or avatar if available. Include links to Home, Categories, and Logout. Data Structures & API Endpoints: User: id, email, displayName, createdAt, updatedAt Category: id, userId, name, createdAt, updatedAt IncomeEntry: id, userId, categoryId, amount, date, notes, createdAt, updatedAt API Endpoints (initial set): POST /auth/google - Handle Google OAuth callback, issue JWT. GET /me - Returns authenticated user’s profile. GET /categories / POST /categories / PUT /categories/:id / DELETE /categories/:id GET /income (optionally with date filters) / POST /income / PUT /income/:id / DELETE /income/:id GET /analytics/summary - Returns summarized income data for the dashboard. Development Approach: Start by setting up the repository structure. Implement basic NestJS backend with authentication and dummy endpoints. Integrate PostgreSQL using TypeORM for models and migrations. Set up frontend with React and Vite, configure Tailwind CSS, and create placeholder pages. Add Google OAuth flow on frontend and backend. Connect frontend forms and charts to backend data. Use Docker Compose to define local development environment with a Postgres service and the Node.js services. What to Produce First: Scaffold the backend NestJS application with basic modules: AuthModule, UserModule, CategoryModule, IncomeModule. Implement Google OAuth logic and JWT token issuing. Set up database entities and run migrations. Scaffold the frontend React app, integrate Tailwind, and create placeholder pages for login, dashboard, and category management. Implement a basic login page that redirects users to Google OAuth, and then displays a dashboard with a static chart and a placeholder add-income form. Next Steps After MVP: Integrate dynamic data into charts. Improve the UI design and add responsive layouts. Add form validations and user-friendly error messages.
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..b45f600
--- /dev/null
+++ b/script.js
@@ -0,0 +1,56 @@
+document.addEventListener('DOMContentLoaded', function() {
+ const ctx = document.getElementById('incomeChart').getContext('2d');
+ const incomeChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7'],
+ datasets: [{
+ label: 'Income',
+ data: [50, 100, 75, 125, 150, 200, 175],
+ borderColor: '#4f46e5',
+ borderWidth: 2,
+ fill: false
+ }]
+ },
+ options: {
+ scales: {
+ y: {
+ beginAtZero: true
+ }
+ }
+ }
+ });
+
+ document.querySelector('.add-income-btn').addEventListener('click', function() {
+ alert('Add Income Entry functionality will be implemented soon.');
+ });
+
+ document.querySelector('.add-category-form').addEventListener('submit', function(e) {
+ e.preventDefault();
+ const categoryName = this.querySelector('input').value;
+ if (categoryName) {
+ const categoryItem = document.createElement('div');
+ categoryItem.className = 'category-item';
+ categoryItem.innerHTML = `
+ <span>${categoryName}</span>
+ <button class="edit-btn">Edit</button>
+ <button class="delete-btn">Delete</button>
+ `;
+ document.querySelector('.category-list').appendChild(categoryItem);
+ this.querySelector('input').value = '';
+ }
+ });
+
+ document.querySelector('.category-list').addEventListener('click', function(e) {
+ if (e.target.classList.contains('delete-btn')) {
+ e.target.closest('.category-item').remove();
+ } else if (e.target.classList.contains('edit-btn')) {
+ const categoryItem = e.target.closest('.category-item');
+ const categoryName = categoryItem.querySelector('span').textContent;
+ const newName = prompt('Edit category name:', categoryName);
+ if (newName) {
+ categoryItem.querySelector('span').textContent = newName;
+ }
+ }
+ });
+});
\ No newline at end of file
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..719284a
--- /dev/null
+++ b/style.css
@@ -0,0 +1,167 @@
+:root {
+ --primary-color: #4f46e5;
+ --secondary-color: #1e293b;
+ --background-color: #f8fafc;
+ --text-color: #1e293b;
+ --border-color: #e2e8f0;
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+}
+
+body {
+ font-family: 'Inter', sans-serif;
+ color: var(--text-color);
+ background-color: var(--background-color);
+ margin: 0;
+ padding: 0;
+ line-height: 1.6;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 20px;
+}
+
+.header {
+ background-color: var(--primary-color);
+ color: white;
+ padding: 20px 0;
+ box-shadow: var(--shadow);
+}
+
+.logo {
+ font-size: 1.5rem;
+ font-weight: bold;
+ margin: 0;
+}
+
+.nav {
+ display: flex;
+ gap: 20px;
+ margin-top: 10px;
+}
+
+.nav-link {
+ color: white;
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.main {
+ padding: 40px 0;
+}
+
+.section-title {
+ font-size: 1.75rem;
+ margin-bottom: 20px;
+ color: var(--secondary-color);
+}
+
+.summary-card {
+ background: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: var(--shadow);
+ margin-bottom: 20px;
+}
+
+.income-total {
+ font-size: 2rem;
+ font-weight: bold;
+ color: var(--primary-color);
+ margin: 10px 0;
+}
+
+.chart-container {
+ background: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: var(--shadow);
+ margin-bottom: 20px;
+}
+
+.add-income-btn {
+ background: var(--primary-color);
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.category-list {
+ margin-bottom: 20px;
+}
+
+.category-item {
+ background: white;
+ padding: 15px;
+ border-radius: 8px;
+ box-shadow: var(--shadow);
+ margin-bottom: 10px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.edit-btn, .delete-btn {
+ background: none;
+ border: none;
+ color: var(--primary-color);
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.add-category-form {
+ display: flex;
+ gap: 10px;
+}
+
+.add-category-form input {
+ flex: 1;
+ padding: 10px;
+ border: 1px solid var(--border-color);
+ border-radius: 5px;
+}
+
+.add-category-form button {
+ background: var(--primary-color);
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.footer {
+ background: var(--secondary-color);
+ color: white;
+ text-align: center;
+ padding: 20px 0;
+ margin-top: 40px;
+}
+
+@media (max-width: 768px) {
+ .nav {
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .container {
+ padding: 0 10px;
+ }
+
+ .summary-card, .chart-container, .category-item {
+ padding: 15px;
+ }
+
+ .add-category-form {
+ flex-direction: column;
+ }
+
+ .add-category-form button {
+ width: 100%;
+ }
+}
\ No newline at end of file