diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b58d3ef --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,28 @@ +# Global code owners +* @devel + +# Frontend specific +/client-ts/ @devel + +# Backend specific +/Api/ @devel +/Models/ @devel +/Services/ @devel +/Data/ @devel +/Dtos/ @devel +/Validators/ @devel +/ViewModels/ @devel + +# Configuration files +*.csproj @devel +*.sln @devel +appsettings*.json @devel +Program.cs @devel + +# Documentation +*.md @devel +README.md @devel +DEVELOPMENT_GUIDE.md @devel + +# CI/CD +/.github/ @devel diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..2929d43 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,58 @@ +version: 2 +updates: + # Enable version updates for npm + - package-ecosystem: "npm" + directory: "/client-ts" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 10 + reviewers: + - "devel" + assignees: + - "devel" + commit-message: + prefix: "chore" + include: "scope" + labels: + - "dependencies" + - "frontend" + + # Enable version updates for NuGet + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 10 + reviewers: + - "devel" + assignees: + - "devel" + commit-message: + prefix: "chore" + include: "scope" + labels: + - "dependencies" + - "backend" + + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 5 + reviewers: + - "devel" + assignees: + - "devel" + commit-message: + prefix: "chore" + include: "scope" + labels: + - "dependencies" + - "github-actions" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d4ba13..bdd02f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,10 @@ on: branches: [ main, develop ] pull_request: branches: [ main, develop ] - release: - types: [ published ] + +env: + NODE_VERSION: '18' + DOTNET_VERSION: '8.0.x' jobs: version-check: @@ -15,215 +17,229 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '18' + node-version: ${{ env.NODE_VERSION }} cache: 'npm' cache-dependency-path: client-ts/package-lock.json - + - name: Install dependencies - run: | - cd client-ts - npm ci - + working-directory: ./client-ts + run: npm ci + - name: Check version consistency run: | - node scripts/check-version.js check + cd client-ts + npm run version:check + continue-on-error: false frontend-tests: name: Frontend Tests runs-on: ubuntu-latest + needs: version-check steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '18' + node-version: ${{ env.NODE_VERSION }} cache: 'npm' cache-dependency-path: client-ts/package-lock.json - + - name: Install dependencies - run: | - cd client-ts - npm ci - + working-directory: ./client-ts + run: npm ci + - name: Run linting - run: | - cd client-ts - npm run lint - + working-directory: ./client-ts + run: npm run lint + - name: Run type checking - run: | - cd client-ts - npm run type-check - + working-directory: ./client-ts + run: npm run type-check + - name: Run tests - run: | - cd client-ts - npm run test:ci - - - name: Upload coverage reports - uses: codecov/codecov-action@v3 - with: - file: client-ts/coverage/lcov.info - flags: frontend + working-directory: ./client-ts + run: npm run test:ci backend-tests: name: Backend Tests runs-on: ubuntu-latest + needs: version-check + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: testpassword + MYSQL_DATABASE: TaskManagerDbTest + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '9.0.x' - + dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Restore dependencies run: dotnet restore - - - name: Build application + + - name: Build project run: dotnet build --configuration Release --no-restore - + - name: Run tests - run: dotnet test --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage" + run: dotnet test --configuration Release --no-build + env: + ConnectionStrings__DefaultConnection: "Server=localhost;Database=TaskManagerDbTest;Uid=root;Pwd=testpassword;" - - name: Upload coverage reports - uses: codecov/codecov-action@v3 + security-scan: + name: Security Scan + runs-on: ubuntu-latest + needs: [version-check] + permissions: + contents: read + security-events: write + actions: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master with: - file: '**/coverage.cobertura.xml' - flags: backend - - build: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + - name: Run npm audit for frontend + working-directory: ./client-ts + run: npm audit --audit-level moderate || true + + - name: Run dotnet list package vulnerabilities + run: dotnet list package --vulnerable || true + + build-application: name: Build Application runs-on: ubuntu-latest - needs: [version-check, frontend-tests, backend-tests] + needs: [frontend-tests, backend-tests] steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '18' + node-version: ${{ env.NODE_VERSION }} cache: 'npm' cache-dependency-path: client-ts/package-lock.json - + - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '9.0.x' - + dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Install frontend dependencies - run: | - cd client-ts - npm ci - + working-directory: ./client-ts + run: npm ci + - name: Build frontend - run: | - cd client-ts - npm run build - + working-directory: ./client-ts + run: npm run build + - name: Restore backend dependencies run: dotnet restore - + - name: Build backend run: dotnet build --configuration Release --no-restore - + - name: Publish backend - run: dotnet publish --configuration Release --no-build --output ./publish - + run: dotnet publish --configuration Release --no-build + - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: build-artifacts path: | - publish/ - client-ts/build/ + ./client-ts/build/ + ./publish/ + retention-days: 7 - security-scan: - name: Security Scan - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - scan-type: 'fs' - scan-ref: '.' - format: 'sarif' - output: 'trivy-results.sarif' - - - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v3 - if: always() && success() - with: - sarif_file: 'trivy-results.sarif' - - release: + create-release: name: Create Release runs-on: ubuntu-latest - needs: [build] - if: startsWith(github.ref, 'refs/tags/v') + needs: [build-application, security-scan] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + permissions: + contents: write + pull-requests: write steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v4 + + - name: Download build artifacts + uses: actions/download-artifact@v4 with: - node-version: '18' - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '9.0.x' - - - name: Get version + name: build-artifacts + + - name: Get version from package.json id: version - run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - - - name: Install dependencies + working-directory: ./client-ts run: | - cd client-ts - npm ci - - - name: Build and package release + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=v$VERSION" >> $GITHUB_OUTPUT + + - name: Check if tag exists + id: check-tag run: | - node scripts/release.js --skip-git - - - name: Upload release artifacts - uses: actions/upload-artifact@v4 - with: - name: release-artifacts - path: releases/ - - - name: Create GitHub Release + if git rev-parse "refs/tags/${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Create Release + if: steps.check-tag.outputs.exists == 'false' uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ steps.version.outputs.VERSION }} - release_name: Release ${{ steps.version.outputs.VERSION }} - body_path: releases/${{ steps.version.outputs.VERSION }}/RELEASE_NOTES.md + tag_name: ${{ steps.version.outputs.tag }} + release_name: Release ${{ steps.version.outputs.version }} draft: false prerelease: false - - - name: Upload release files - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: releases/TaskManagerApp-${{ steps.version.outputs.VERSION }}.tar.gz - asset_name: TaskManagerApp-${{ steps.version.outputs.VERSION }}.tar.gz - asset_content_type: application/gzip + body: | + ## 🚀 Release ${{ steps.version.outputs.version }} + + ### Changes + - Automated release from CI/CD pipeline + - All tests passed ✅ + - Security scan completed ✅ + - Application built successfully ✅ + + ### Build Artifacts + - Frontend build ready for deployment + - Backend publish ready for deployment + + ### Installation + Download the artifacts from the Actions tab or use the built application directly. + files: | + build-artifacts/client-ts/build/* + build-artifacts/publish/* diff --git a/client-ts/package-lock.json b/client-ts/package-lock.json index d5349fb..4e6ff44 100644 --- a/client-ts/package-lock.json +++ b/client-ts/package-lock.json @@ -8,7 +8,7 @@ "name": "client-ts", "version": "0.1.0", "dependencies": { - "@headlessui/react": "2.2.1", + "@headlessui/react": "2.2.8", "@tailwindcss/forms": "0.5.10", "@tailwindcss/typography": "0.5.16", "@testing-library/dom": "10.4.0", @@ -2645,15 +2645,16 @@ "license": "MIT" }, "node_modules/@headlessui/react": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.1.tgz", - "integrity": "sha512-daiUqVLae8CKVjEVT19P/izW0aGK0GNhMSAeMlrDebKmoVZHcRRwbxzgtnEadUVDXyBsWo9/UH4KHeniO+0tMg==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.8.tgz", + "integrity": "sha512-vkiZulDC0lFeTrZTbA4tHvhZHvkUb2PFh5xJ1BvWAZdRK0fayMKO1QEO4inWkXxK1i0I1rcwwu1d6mo0K7Pcbw==", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.26.16", - "@react-aria/focus": "^3.17.1", - "@react-aria/interactions": "^3.21.3", - "@tanstack/react-virtual": "^3.11.1" + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.9", + "use-sync-external-store": "^1.5.0" }, "engines": { "node": ">=10" @@ -3306,14 +3307,14 @@ } }, "node_modules/@react-aria/focus": { - "version": "3.20.1", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.1.tgz", - "integrity": "sha512-lgYs+sQ1TtBrAXnAdRBQrBo0/7o5H6IrfDxec1j+VRpcXL0xyk0xPq+m3lZp8typzIghqDgpnKkJ5Jf4OrzPIw==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.1.tgz", + "integrity": "sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ==", "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.24.1", - "@react-aria/utils": "^3.28.1", - "@react-types/shared": "^3.28.0", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, @@ -3323,15 +3324,15 @@ } }, "node_modules/@react-aria/interactions": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.24.1.tgz", - "integrity": "sha512-OWEcIC6UQfWq4Td5Ptuh4PZQ4LHLJr/JL2jGYvuNL6EgL3bWvzPrRYIF/R64YbfVxIC7FeZpPSkS07sZ93/NoA==", + "version": "3.25.5", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.5.tgz", + "integrity": "sha512-EweYHOEvMwef/wsiEqV73KurX/OqnmbzKQa2fLxdULbec5+yDj6wVGaRHIzM4NiijIDe+bldEl5DG05CAKOAHA==", "license": "Apache-2.0", "dependencies": { - "@react-aria/ssr": "^3.9.7", - "@react-aria/utils": "^3.28.1", - "@react-stately/flags": "^3.1.0", - "@react-types/shared": "^3.28.0", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.1", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { @@ -3340,9 +3341,9 @@ } }, "node_modules/@react-aria/ssr": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", - "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" @@ -3355,15 +3356,15 @@ } }, "node_modules/@react-aria/utils": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.28.1.tgz", - "integrity": "sha512-mnHFF4YOVu9BRFQ1SZSKfPhg3z+lBRYoW5mLcYTQihbKhz48+I1sqRkP7ahMITr8ANH3nb34YaMME4XWmK2Mgg==", + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.30.1.tgz", + "integrity": "sha512-zETcbDd6Vf9GbLndO6RiWJadIZsBU2MMm23rBACXLmpRztkrIqPEb2RVdlLaq1+GklDx0Ii6PfveVjx+8S5U6A==", "license": "Apache-2.0", "dependencies": { - "@react-aria/ssr": "^3.9.7", - "@react-stately/flags": "^3.1.0", - "@react-stately/utils": "^3.10.5", - "@react-types/shared": "^3.28.0", + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, @@ -3373,18 +3374,18 @@ } }, "node_modules/@react-stately/flags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.0.tgz", - "integrity": "sha512-KSHOCxTFpBtxhIRcKwsD1YDTaNxFtCYuAUb0KEihc16QwqZViq4hasgPBs2gYm7fHRbw7WYzWKf6ZSo/+YsFlg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", + "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } }, "node_modules/@react-stately/utils": { - "version": "3.10.5", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.5.tgz", - "integrity": "sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==", + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", + "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" @@ -3394,9 +3395,9 @@ } }, "node_modules/@react-types/shared": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.28.0.tgz", - "integrity": "sha512-9oMEYIDc3sk0G5rysnYvdNrkSg7B04yTKl50HHSZVbokeHpnU0yRmsDaWb9B/5RprcKj8XszEk5guBO8Sa/Q+Q==", + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.0.tgz", + "integrity": "sha512-t+cligIJsZYFMSPFMvsJMjzlzde06tZMOIOFa1OV5Z0BcMowrb2g4mB57j/9nP28iJIRYn10xCniQts+qadrqQ==", "license": "Apache-2.0", "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" @@ -3763,9 +3764,9 @@ } }, "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -3812,12 +3813,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.6.tgz", - "integrity": "sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==", + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz", + "integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.13.6" + "@tanstack/virtual-core": "3.13.12" }, "funding": { "type": "github", @@ -3829,9 +3830,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz", - "integrity": "sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==", + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", + "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==", "license": "MIT", "funding": { "type": "github", @@ -18691,6 +18692,15 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/client-ts/package.json b/client-ts/package.json index 3a6c580..a688d13 100644 --- a/client-ts/package.json +++ b/client-ts/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@headlessui/react": "2.2.1", + "@headlessui/react": "2.2.8", "@tailwindcss/forms": "0.5.10", "@tailwindcss/typography": "0.5.16", "@testing-library/dom": "10.4.0", diff --git a/client-ts/src/components/LoginForm.tsx b/client-ts/src/components/LoginForm.tsx index f97823f..ee33d8b 100644 --- a/client-ts/src/components/LoginForm.tsx +++ b/client-ts/src/components/LoginForm.tsx @@ -34,7 +34,7 @@ export default function LoginForm() { navigate("/dashboard"); } catch (err: any) { if (axios.isAxiosError(err)) { - console.error("Axios error:", err.response); + // Axios error occurred // Identity-style array errors if (Array.isArray(err.response?.data)) { diff --git a/client-ts/src/components/ui/Checkbox.tsx b/client-ts/src/components/ui/Checkbox.tsx index 2c3716b..262fe11 100644 --- a/client-ts/src/components/ui/Checkbox.tsx +++ b/client-ts/src/components/ui/Checkbox.tsx @@ -52,7 +52,7 @@ const Checkbox = forwardRef( ref={ref} id={checkboxId} type="checkbox" - className={`${sizeClasses[size]} text-blue-600 bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-slate-800 focus:ring-2 dark:bg-slate-700 dark:border-slate-600 ${className}`} + className={`${sizeClasses[size]} text-blue-600 bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-slate-800 focus:ring-2 ${className}`} {...props} /> {animated && ( diff --git a/client-ts/src/context/AuthContext.tsx b/client-ts/src/context/AuthContext.tsx index 3e7a487..c410b8f 100644 --- a/client-ts/src/context/AuthContext.tsx +++ b/client-ts/src/context/AuthContext.tsx @@ -49,19 +49,18 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const newDecoded = jwtDecode(newToken); setUser({ email: newDecoded.email }); - console.log("🔄 Access token refreshed on app load"); + // Token refreshed successfully } catch (refreshErr) { - console.warn("🔒 Refresh failed, clearing localStorage token"); + // Refresh failed, clearing token localStorage.removeItem("token"); setUser(null); } } else { // Token is still valid setUser({ email: decoded.email }); - console.log("✅ Token is still valid"); } } catch (err) { - console.warn("🔒 Invalid token, clearing localStorage"); + // Invalid token, clearing localStorage localStorage.removeItem("token"); setUser(null); } finally { diff --git a/client-ts/src/pages/Dashboard.tsx b/client-ts/src/pages/Dashboard.tsx index 82cf96d..f075a91 100644 --- a/client-ts/src/pages/Dashboard.tsx +++ b/client-ts/src/pages/Dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { format } from 'date-fns'; import { toast } from 'react-toastify'; @@ -94,6 +94,16 @@ const Dashboard = () => { } }; + // Memoized calculations for better performance + const taskStats = useMemo(() => { + const total = tasks.length; + const completed = tasks.filter(task => task.isCompleted).length; + const pending = total - completed; + const highPriority = tasks.filter(task => task.priority === 3 || task.priority === 4).length; + + return { total, completed, pending, highPriority }; + }, [tasks]); + if (loadingUser) { return (
@@ -323,7 +333,7 @@ const Dashboard = () => {

Total Tasks

-

{tasks.length}

+

{taskStats.total}

@@ -337,7 +347,7 @@ const Dashboard = () => {

Completed

-

{tasks.filter(task => task.isCompleted).length}

+

{taskStats.completed}

@@ -351,7 +361,7 @@ const Dashboard = () => {

Pending

-

{tasks.filter(task => !task.isCompleted).length}

+

{taskStats.pending}

@@ -365,7 +375,7 @@ const Dashboard = () => {

High Priority

-

{tasks.filter(task => task.priority === 3 || task.priority === 4).length}

+

{taskStats.highPriority}

diff --git a/client-ts/src/pages/Home.tsx b/client-ts/src/pages/Home.tsx index a6124bc..140f847 100644 --- a/client-ts/src/pages/Home.tsx +++ b/client-ts/src/pages/Home.tsx @@ -10,7 +10,7 @@ const Home = () => { const data = await fetchTasks(); setTasks(data); } catch (error) { - console.error("Failed to load tasks", error); + // Failed to load tasks } }; diff --git a/scripts/start-professional.ps1 b/scripts/start-professional.ps1 index 9719d13..3ab8cf7 100644 --- a/scripts/start-professional.ps1 +++ b/scripts/start-professional.ps1 @@ -57,7 +57,7 @@ if (-not (Test-Path "wwwroot")) { # Verificar si el backend está ejecutándose $backendRunning = $false try { - $response = Invoke-WebRequest -Uri "https://localhost:$appPort/api/tasks" -UseBasicParsing -TimeoutSec 5 + Invoke-WebRequest -Uri "https://localhost:$appPort/api/tasks" -UseBasicParsing -TimeoutSec 5 | Out-Null $backendRunning = $true } catch { $backendRunning = $false @@ -76,7 +76,7 @@ if (-not $backendRunning) { Start-Sleep -Seconds 2 $attempts++ try { - $response = Invoke-WebRequest -Uri "https://localhost:$appPort/api/tasks" -UseBasicParsing -TimeoutSec 5 + Invoke-WebRequest -Uri "https://localhost:$appPort/api/tasks" -UseBasicParsing -TimeoutSec 5 | Out-Null $backendRunning = $true } catch { $backendRunning = $false