Changes to hustletrack.scroll.pub

root
root
25 days ago
Initial commit
body.html
Changed around line 1
+
+
+

HustleTrack

+
+
+
+
+
+
+
+
+

Dashboard

+
+

Total Income This Month

+

$0.00

+
+
+
+
+
+
+
+
+
+
+

Categories

+
+
+ Freelance
+
+
+
+
+ E-commerce
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Track your side hustle income with ease.

+
+
index.scroll
Changed around line 1
+ buildHtml
+ baseUrl https://hustletrack.scroll.pub
+ metaTags
+ editButton /edit.html
+ title HustleTrack - Side Hustle Income Tracker
+ style.css
+ body.html
+ script.js
prompt.json
Changed around line 1
+ {
+ "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
\n
\n

HustleTrack

\n \n
\n
\n\n
\n
\n
\n

Dashboard

\n
\n

Total Income This Month

\n

$0.00

\n
\n
\n \n
\n \n
\n
\n\n
\n
\n

Categories

\n
\n
\n Freelance\n \n \n
\n
\n E-commerce\n \n \n
\n
\n
\n \n \n
\n
\n
\n
\n\n
\n
\n

Track your side hustle income with ease.

\n
\n
\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 ${categoryName}\n \n \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"
+ }
readme.scroll
Changed around line 1
+ # 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.
script.js
Changed around line 1
+ 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 = `
+ ${categoryName}
+
+
+ `;
+ 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;
+ }
+ }
+ });
+ });
style.css
Changed around line 1
+ :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%;
+ }
+ }