From b3f7d6bf98fff9040f621aaf098d1104e1780f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Thu, 9 Apr 2026 23:42:49 +0200 Subject: [PATCH] =?UTF-8?q?Versi=C3=B3n=201.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 23 + .gitignore | 7 +- README.md | 144 +++- package-lock.json | 261 +++++++ package.json | 8 +- public/LOGO_FICOSA.svg | 56 ++ public/favicon.ico | Bin 3870 -> 6841 bytes public/index.html | 2 +- public/logo192.png | Bin 5347 -> 0 bytes public/logo512.png | Bin 9664 -> 0 bytes public/logo_ficosa.png | Bin 0 -> 4534 bytes public/manifest.json | 6 +- setup.js | 51 ++ src/App.css | 1260 +++++++++++++++++++++++++++++++- src/App.js | 184 ++++- src/context/AuthContext.js | 539 ++++++++++++++ src/index.css | 1 + src/pages/Calendar.js | 273 +++++++ src/pages/Dashboard.js | 147 ++++ src/pages/Login.js | 100 +++ src/pages/Profile.js | 351 +++++++++ src/pages/Sessions.js | 716 ++++++++++++++++++ src/services/sessionService.js | 246 +++++++ src/services/supabaseClient.js | 8 + 24 files changed, 4335 insertions(+), 48 deletions(-) create mode 100644 .env.example create mode 100644 public/LOGO_FICOSA.svg delete mode 100644 public/logo192.png delete mode 100644 public/logo512.png create mode 100644 public/logo_ficosa.png create mode 100644 setup.js create mode 100644 src/context/AuthContext.js create mode 100644 src/pages/Calendar.js create mode 100644 src/pages/Dashboard.js create mode 100644 src/pages/Login.js create mode 100644 src/pages/Profile.js create mode 100644 src/pages/Sessions.js create mode 100644 src/services/sessionService.js create mode 100644 src/services/supabaseClient.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..04296ec --- /dev/null +++ b/.env.example @@ -0,0 +1,23 @@ +# Supabase Configuration +# Sign up at https://supabase.io to get your project URL and anon key +REACT_APP_SUPABASE_URL=https://your-supabase-project.supabase.co +REACT_APP_SUPABASE_ANON_KEY=your-supabase-anon-key-here + +# Database Password (for reference - not used directly in app) +SUPABASE_DB_PASSWORD=your-database-password-here + +# Application Settings +REACT_APP_APP_NAME=Time Tracker +REACT_APP_VERSION=1.0.0 + +# Development Settings +GENERATE_SOURCEMAP=false + +# Instructions: +# 1. Copy this file to .env +# 2. Replace placeholder values with your actual credentials +# 3. Restart the development server after making changes + +# Security Note: +# Never commit .env file to version control +# Only variables prefixed with REACT_APP_ are embedded in the client bundle \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4d29575..c2d6a6b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,12 @@ .env.development.local .env.test.local .env.production.local +.env + +# Additional environment files +.env.* +!.env.example npm-debug.log* yarn-debug.log* -yarn-error.log* +yarn-error.log* \ No newline at end of file diff --git a/README.md b/README.md index 58beeac..b2af5b4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,144 @@ -# Getting Started with Create React App +# Time Tracker App + +A React-based time tracking application with user authentication and session management. + +## Features + +- User authentication system with Supabase Auth +- Start/stop time tracking functionality +- Pause/resume functionality for breaks +- Session history management +- Edit current and past sessions +- Create new sessions with simplified time input +- Integration with Supabase for data persistence +- Responsive design for desktop and mobile + +## Pages + +1. **Login/Register Page** - Secure authentication entry point +2. **Dashboard** - Main page with timer controls +3. **Session History** - View, edit, and create sessions + +## Setup Instructions + +1. Clone the repository (if applicable) +2. Install dependencies: + ``` + npm install + ``` +3. Set up environment variables: + - Copy `.env.example` to `.env` + - Update the values in `.env` with your Supabase credentials: + ``` + REACT_APP_SUPABASE_URL=your_supabase_project_url_here + REACT_APP_SUPABASE_ANON_KEY=your_supabase_anon_key_here + ``` +4. Start the development server: + ``` + npm start + ``` + +## Database Setup + +### Supabase Tables + +Create these tables in your Supabase project: + +#### Users Table +```sql +-- Users table (handled by Supabase Auth) +-- Supabase automatically creates auth.users table +``` + +#### Timers Table +```sql +-- Create timers table +create table if not exists timers ( + id uuid primary key default gen_random_uuid(), + user_id uuid references auth.users(id) not null, + start_time timestamptz not null, + end_time timestamptz, + duration_ms integer, + pauses json, + created_at timestamptz default now() +); + +-- Enable RLS (Row Level Security) +alter table timers enable row level security; + +-- Create policy to allow users to access their own timers only +create policy "Users can access their own timers" on timers + for all using (auth.uid() = user_id); + +-- Grant permissions +grant usage on schema public to anon, authenticated; +grant all on table timers to anon, authenticated; +``` + +## Session Management Features + +### Creating New Sessions +- Click "Create New Session" in Session History +- Select date and enter start/end times (HH:MM format) +- Add pauses with start/end times as needed +- Save to store in Supabase database + +### Editing Sessions +- Edit current active session +- Edit past sessions from history +- Modify start/end times and pause periods +- Changes automatically saved to database + +### Timer Controls +- Start/Stop schedule tracking +- Take breaks with Pause/Resume functionality +- Real-time work time calculation (excluding pause time) + +## Security + +- Environment variables are stored in `.env` (excluded from git) +- Sensitive data never stored in browser localStorage or sessionStorage +- All session data persists only in user session and external database +- Review `.gitignore` to ensure sensitive files are not committed +- Supabase Auth handles user authentication securely + +## Implementation Details + +- Built with React and React Router for navigation +- Uses Context API for state management +- Implements a clean, responsive UI with CSS +- Session data persists in user session and Supabase database +- Integrates with Supabase API for data persistence and authentication + +## File Structure + +``` +src/ +├── components/ # Reusable UI components +├── context/ # Authentication and state context +├── pages/ # Page components (Login, Dashboard, Sessions) +├── services/ # API integration services +└── App.js # Main app component with routing +``` + +## Database Integration + +The app includes a service layer for Supabase integration: +- Sessions are saved to the database when stopped +- Session history is loaded from the database on login +- All database operations are handled through the sessionService +- User authentication is managed by Supabase Auth + +To connect to your own Supabase project: +1. Create a Supabase account at https://supabase.io +2. Create a new project +3. Get your project URL and anon key from project settings +4. Create the required tables using the SQL provided above +5. Update the environment variables with your credentials + +--- + +# Original Create React App Documentation This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). @@ -67,4 +207,4 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/d ### `npm run build` fails to minify -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f6c92a6..4d5bb56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,17 @@ "name": "time-tracker-app", "version": "0.1.0", "dependencies": { + "@fortawesome/free-solid-svg-icons": "^7.2.0", + "@fortawesome/react-fontawesome": "^3.3.0", + "@supabase/supabase-js": "^2.103.0", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^13.5.0", + "axios": "^1.15.0", "react": "^19.2.5", "react-dom": "^19.2.5", + "react-router-dom": "^7.14.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" } @@ -2456,6 +2461,53 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.2.0.tgz", + "integrity": "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.2.0.tgz", + "integrity": "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.2.0.tgz", + "integrity": "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-3.3.0.tgz", + "integrity": "sha512-EHmHeTf8WgO29sdY3iX/7ekE3gNUdlc2RW6mm/FzELlHFKfTrA9S4MlyquRR+RRCRCn8+jXfLFpLGB2l7wCWyw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~6 || ~7", + "react": "^18.0.0 || ^19.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -3100,6 +3152,113 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@supabase/auth-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.103.0.tgz", + "integrity": "sha512-6zAanO6c+6gpHOlt5Lb9TlBBkJdZiUWkWCJKAxzkywBDcwaHlLJKXnjQGX6GyVCyKRR1e7sTq4re/yRTH6U/9A==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.103.0.tgz", + "integrity": "sha512-YrneV2NjskUkkmkZ2Jt2n3elBgbWzV4Y1M9MM370z2Zd5ZPFqFbY8KIoPwuNjtAGE9YrpKBxnbZqeF07BiN9Og==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz", + "integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.103.0.tgz", + "integrity": "sha512-rC3sRxYdPZymkp2CZR1MiNQgbOleD01bGsW8VxEKRR5nMkLZ1NgAS1QTQf78Wh30czFyk505ZYr9Od8/mWT2TA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.103.0.tgz", + "integrity": "sha512-gcPtXzZ6izyyBVf2of7K3dEt8CScPJn8VcSlQq6oWL9QoE1kqfQl0oFrOMHd5qrcADewxI7OxxosLB8W4XqtIQ==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.0", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js/node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.103.0.tgz", + "integrity": "sha512-DHmlvdAXwtOmZNbkIZi4lkobPR3XjIzoOgzoz5duMf6G+sDeY015YrzMJCnqdccuYr7X5x4yYuSwF//RoN2dvQ==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.103.0.tgz", + "integrity": "sha512-j/6q5+LtXbR/YOLSLhy7Na74RD1cV2v+KwIIuuqMEjk1JpLEEyu0ynwDHpGoxMncDQl+R5FogaVqZm+85lZvtw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.103.0", + "@supabase/functions-js": "2.103.0", + "@supabase/postgrest-js": "2.103.0", + "@supabase/realtime-js": "2.103.0", + "@supabase/storage-js": "2.103.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -4803,6 +4962,33 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -9020,6 +9206,15 @@ "node": ">=10.17.0" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -13472,6 +13667,15 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -13763,6 +13967,57 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz", + "integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.0.tgz", + "integrity": "sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.14.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -14641,6 +14896,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", diff --git a/package.json b/package.json index 17f3cd1..2354cbb 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,17 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fortawesome/free-solid-svg-icons": "^7.2.0", + "@fortawesome/react-fontawesome": "^3.3.0", + "@supabase/supabase-js": "^2.103.0", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^13.5.0", + "axios": "^1.15.0", "react": "^19.2.5", "react-dom": "^19.2.5", + "react-router-dom": "^7.14.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -16,7 +21,8 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "setup": "node setup.js" }, "eslintConfig": { "extends": [ diff --git a/public/LOGO_FICOSA.svg b/public/LOGO_FICOSA.svg new file mode 100644 index 0000000..ee22b3e --- /dev/null +++ b/public/LOGO_FICOSA.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + diff --git a/public/favicon.ico b/public/favicon.ico index a11777cc471a4344702741ab1c8a588998b1311a..45952a37bdd4b86cbefc7c52a76136d159b8a1d1 100644 GIT binary patch literal 6841 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 literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ diff --git a/public/index.html b/public/index.html index aa069f2..f3e7dee 100644 --- a/public/index.html +++ b/public/index.html @@ -9,7 +9,7 @@ name="description" content="Web site created using create-react-app" /> - +