diff --git a/README.md b/README.md index 2ee40f3..b973e36 100644 --- a/README.md +++ b/README.md @@ -143,17 +143,20 @@ src/ ### ⚡ Quick Start 1. **Clone the repository** + ```bash git clone https://github.com/guibranco/gstraccini-bot-portal.git cd gstraccini-bot-portal ``` 2. **Install dependencies** + ```bash npm install ``` 3. **Start development server** + ```bash npm run dev ``` @@ -165,14 +168,14 @@ src/ ### 🛠️ Available Scripts -| Command | Description | -|---------|-------------| -| `npm run dev` | Start development server with hot reload | -| `npm run build` | Build production-ready application | -| `npm run preview` | Preview production build locally | -| `npm run lint` | Run ESLint for code quality checks | -| `npm run test` | Run test suite with Vitest | -| `npm run test:coverage` | Run tests with coverage report | +| Command | Description | +| ----------------------- | ---------------------------------------- | +| `npm run dev` | Start development server with hot reload | +| `npm run build` | Build production-ready application | +| `npm run preview` | Preview production build locally | +| `npm run lint` | Run ESLint for code quality checks | +| `npm run test` | Run test suite with Vitest | +| `npm run test:coverage` | Run tests with coverage report | --- @@ -209,30 +212,35 @@ src/ ## 🌟 Features ### 🎯 Dashboard & Analytics + - **Real-time Statistics** - Live metrics for repositories, PRs, and issues - **Activity Timeline** - Chronological view of all repository activities - **Performance Insights** - Detailed analytics on workflow efficiency - **Custom Widgets** - Configurable dashboard components ### 🔐 Security & Authentication + - **GitHub OAuth Integration** - Secure authentication via GitHub - **Multi-Factor Authentication** - FIDO2, TOTP, and recovery codes support - **Session Management** - Secure session handling and automatic expiration - **Permission Controls** - Granular access control for repositories ### 🔧 Repository Management + - **Branch Protection** - Configure and manage branch protection rules - **Workflow Automation** - Set up and monitor GitHub Actions workflows - **Integration Hub** - Connect with SonarCloud, Codacy, Snyk, and more - **Release Management** - Track and manage repository releases ### 📊 Pull Request & Issue Tracking + - **Advanced Filtering** - Filter by status, labels, assignees, and more - **Bulk Operations** - Perform actions on multiple items simultaneously - **Timeline Visualization** - Visual representation of PR/issue lifecycle - **Automated Workflows** - Set up automated responses and actions ### 🔔 Notifications & Alerts + - **Real-time Notifications** - Instant updates on important events - **Custom Filters** - Configure notification preferences - **Priority Levels** - Categorize notifications by importance @@ -253,6 +261,7 @@ src/ ### 🎭 Dark Mode Support The application features a comprehensive dark mode implementation with: + - **System Preference Detection** - Automatically adapts to user's system theme - **Manual Toggle** - Users can override system preference - **Persistent Settings** - Theme preference saved in localStorage @@ -297,7 +306,7 @@ The project uses Vite with SWC for optimal performance: export default defineConfig({ plugins: [react()], optimizeDeps: { - exclude: ['lucide-react'], + exclude: ["lucide-react"], }, }); ``` @@ -322,6 +331,7 @@ npm run test:coverage ``` Coverage reports are generated in the `coverage/` directory with: + - **HTML Reports** - Interactive coverage visualization - **JSON Reports** - Machine-readable coverage data - **LCOV Reports** - Integration with external tools @@ -360,6 +370,7 @@ CMD ["nginx", "-g", "daemon off;"] ### ☁️ Deployment Platforms The application is optimized for deployment on: + - **Vercel** - Zero-configuration deployment - **Netlify** - Continuous deployment from Git - **AWS S3 + CloudFront** - Scalable static hosting @@ -420,4 +431,4 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) [⭐ Star this repository](https://github.com/guibranco/gstraccini-bot-portal) if you find it helpful! - \ No newline at end of file + diff --git a/eslint.config.js b/eslint.config.js index 82c2e20..79a552e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,28 +1,28 @@ -import js from '@eslint/js'; -import globals from 'globals'; -import reactHooks from 'eslint-plugin-react-hooks'; -import reactRefresh from 'eslint-plugin-react-refresh'; -import tseslint from 'typescript-eslint'; +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; export default tseslint.config( - { ignores: ['dist'] }, + { ignores: ["dist"] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, }, rules: { ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], }, - } + }, ); diff --git a/src/App.tsx b/src/App.tsx index 802b809..1323177 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,24 +1,29 @@ -import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; -import { AuthProvider } from './context/AuthContext'; -import { PublicLayout } from './layouts/PublicLayout'; -import { AuthenticatedLayout } from './layouts/AuthenticatedLayout'; -import { Landing } from './pages/Landing'; -import Auth from './pages/Auth'; -import ForgotPassword from './pages/ForgotPassword'; -import Privacy from './pages/Privacy'; -import Security from './pages/Security'; -import Status from './pages/Status'; -import Terms from './pages/Terms'; -import { Dashboard } from './pages/Dashboard'; -import { Repositories } from './pages/Repositories'; -import { RepositoryDetail } from './pages/RepositoryDetail'; -import { Issues } from './pages/Issues'; -import { PullRequests } from './pages/PullRequests'; -import { PullRequestDetail } from './pages/PullRequestDetail'; -import { Notifications } from './pages/Notifications'; -import { Account } from './pages/Account'; -import { Settings } from './pages/Settings'; -import { Integrations } from './pages/Integrations'; +import { + BrowserRouter as Router, + Routes, + Route, + Navigate, +} from "react-router-dom"; +import { AuthProvider } from "./context/AuthContext"; +import { PublicLayout } from "./layouts/PublicLayout"; +import { AuthenticatedLayout } from "./layouts/AuthenticatedLayout"; +import { Landing } from "./pages/Landing"; +import Auth from "./pages/Auth"; +import ForgotPassword from "./pages/ForgotPassword"; +import Privacy from "./pages/Privacy"; +import Security from "./pages/Security"; +import Status from "./pages/Status"; +import Terms from "./pages/Terms"; +import { Dashboard } from "./pages/Dashboard"; +import { Repositories } from "./pages/Repositories"; +import { RepositoryDetail } from "./pages/RepositoryDetail"; +import { Issues } from "./pages/Issues"; +import { PullRequests } from "./pages/PullRequests"; +import { PullRequestDetail } from "./pages/PullRequestDetail"; +import { Notifications } from "./pages/Notifications"; +import { Account } from "./pages/Account"; +import { Settings } from "./pages/Settings"; +import { Integrations } from "./pages/Integrations"; function App() { return ( @@ -35,12 +40,15 @@ function App() { } /> } /> - + {/* Authenticated Routes - Dashboard Area */} }> } /> } /> - } /> + } + /> } /> } /> } /> @@ -49,7 +57,7 @@ function App() { } /> } /> - + {/* Catch all route - redirect to landing */} } /> diff --git a/src/components/ActivityList.tsx b/src/components/ActivityList.tsx index 3e5823c..771085d 100644 --- a/src/components/ActivityList.tsx +++ b/src/components/ActivityList.tsx @@ -1,22 +1,28 @@ -import { Activity } from '../types'; -import { GitPullRequest, GitMerge, XCircle, GitCommit, GitPullRequestDraft } from 'lucide-react'; -import { Link } from 'react-router-dom'; +import { Activity } from "../types"; +import { + GitPullRequest, + GitMerge, + XCircle, + GitCommit, + GitPullRequestDraft, +} from "lucide-react"; +import { Link } from "react-router-dom"; interface ActivityListProps { activities: Activity[]; } -const getActivityIcon = (type: Activity['type']) => { +const getActivityIcon = (type: Activity["type"]) => { switch (type) { - case 'pr_created': + case "pr_created": return ; - case 'pr_merged': + case "pr_merged": return ; - case 'issue_closed': + case "issue_closed": return ; - case 'commits_analyzed': + case "commits_analyzed": return ; - case 'pr_opened': + case "pr_opened": return ; } }; @@ -24,10 +30,15 @@ const getActivityIcon = (type: Activity['type']) => { export function ActivityList({ activities }: ActivityListProps) { return (
-

Recent Activities

+

+ Recent Activities +

{activities.map((activity) => ( -
+
{getActivityIcon(activity.type)}
@@ -54,11 +65,11 @@ export function ActivityList({ activities }: ActivityListProps) { {activity.repository} - {new Date(activity.timestamp).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' + {new Date(activity.timestamp).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", })}

@@ -67,4 +78,4 @@ export function ActivityList({ activities }: ActivityListProps) {
); -} \ No newline at end of file +} diff --git a/src/components/BulletDiagram.tsx b/src/components/BulletDiagram.tsx index b0e846e..f665058 100644 --- a/src/components/BulletDiagram.tsx +++ b/src/components/BulletDiagram.tsx @@ -1,30 +1,34 @@ -import React from 'react'; -import { Circle, User, Code, Globe } from 'lucide-react'; -import { Event } from '../types'; -import { getAppAvatarUrl } from '../utils/avatar'; -import { groupEventsByPayloadId } from '../utils/events'; +import React from "react"; +import { Circle, User, Code, Globe } from "lucide-react"; +import { Event } from "../types"; +import { getAppAvatarUrl } from "../utils/avatar"; +import { groupEventsByPayloadId } from "../utils/events"; interface BulletDiagramProps { readonly events: readonly Event[]; readonly onViewPayload: (payload: Record) => void; } -export function BulletDiagram({ events, onViewPayload }: Readonly) { +export function BulletDiagram({ + events, + onViewPayload, +}: Readonly) { const formatDateTime = (date: string) => { return new Date(date).toLocaleString(undefined, { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", }); }; const getEventLabel = (event: Event): string => { - const title = event.payload.check_run?.name || - event.payload.workflow_run?.name || - event.payload.workflow_job?.name; + const title = + event.payload.check_run?.name || + event.payload.workflow_run?.name || + event.payload.workflow_job?.name; if (title) { return `${title} (${event.action})`; @@ -49,12 +53,12 @@ export function BulletDiagram({ events, onViewPayload }: Readonly { // Check for conclusion in various payload types - const conclusion = + const conclusion = event.payload.check_run?.conclusion || event.payload.check_suite?.conclusion || event.payload.status?.state || @@ -64,92 +68,98 @@ export function BulletDiagram({ events, onViewPayload }: Readonly { - const type = event.payload.check_run?.name || - event.payload.workflow_run?.name || - event.payload.workflow_job?.name || - event.type; - if (!acc[type]) { - acc[type] = []; - } - acc[type].push(event); - return acc; - }, {} as Record); + const groupedByType = events.reduce( + (acc, event) => { + const type = + event.payload.check_run?.name || + event.payload.workflow_run?.name || + event.payload.workflow_job?.name || + event.type; + if (!acc[type]) { + acc[type] = []; + } + acc[type].push(event); + return acc; + }, + {} as Record, + ); // Sort events within each group by timestamp - Object.values(groupedByType).forEach(group => { - group.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + Object.values(groupedByType).forEach((group) => { + group.sort( + (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), + ); }); return ( @@ -157,13 +167,17 @@ export function BulletDiagram({ events, onViewPayload }: Readonly {Object.entries(groupedByType).map(([type, typeEvents]) => (
-

{type}

+

+ {type} +

{typeEvents.map((event, index) => { const appData = event.payload[event.type]?.app; - + return ( - + {index > 0 && (
@@ -215,7 +229,9 @@ export function BulletDiagram({ events, onViewPayload }: Readonly -
+
@@ -238,4 +254,4 @@ export function BulletDiagram({ events, onViewPayload }: Readonly
); -} \ No newline at end of file +} diff --git a/src/components/CommandsSection.tsx b/src/components/CommandsSection.tsx index 3de63eb..0b49cb4 100644 --- a/src/components/CommandsSection.tsx +++ b/src/components/CommandsSection.tsx @@ -1,151 +1,160 @@ -import React from 'react'; -import { Terminal, ExternalLink } from 'lucide-react'; +import React from "react"; +import { Terminal, ExternalLink } from "lucide-react"; const commands = [ { command: "@gstraccini help", description: "Shows the help message with available commands.", docs: "https://docs.gstraccini.bot/commands/help", - places: "All" + places: "All", }, { command: "@gstraccini add project ", - description: "Adds a project to the solution file (only for .NET projects).", + description: + "Adds a project to the solution file (only for .NET projects).", docs: "https://docs.gstraccini.bot/commands/add-project", - places: "Portal" + places: "Portal", }, { command: "@gstraccini appveyor build ", - description: "Runs the AppVeyor build for the target commit and/or pull request.", + description: + "Runs the AppVeyor build for the target commit and/or pull request.", docs: "https://docs.gstraccini.bot/commands/appveyor-build", - places: "Pull Requests" + places: "Pull Requests", }, { command: "@gstraccini appveyor bump version ", description: "Bumps the CI version in AppVeyor.", docs: "https://docs.gstraccini.bot/commands/appveyor-bump-version", - places: "Issues + Pull Requests" + places: "Issues + Pull Requests", }, { command: "@gstraccini appveyor register", description: "Registers the repository in AppVeyor.", docs: "https://docs.gstraccini.bot/commands/appveyor-register", - places: "Portal" + places: "Portal", }, { command: "@gstraccini appveyor reset", description: "Resets the AppVeyor build number for the target repository.", docs: "https://docs.gstraccini.bot/commands/appveyor-reset", - places: "Pull Requests" + places: "Pull Requests", }, { command: "@gstraccini bump version ", description: "Bumps the .NET version in .csproj files.", docs: "https://docs.gstraccini.bot/commands/bump-version", - places: "Portal" + places: "Portal", }, { command: "@gstraccini cargo clippy", - description: "Formats the Rust code using Cargo Clippy (only for Rust projects).", + description: + "Formats the Rust code using Cargo Clippy (only for Rust projects).", docs: "https://docs.gstraccini.bot/commands/cargo-clippy", - places: "All" + places: "All", }, { command: "@gstraccini change runner ", description: "Changes the GitHub action runner in a workflow file (.yml).", docs: "https://docs.gstraccini.bot/commands/change-runner", - places: "Issues + Pull Requests" + places: "Issues + Pull Requests", }, { command: "@gstraccini codacy bypass", - description: "Bypasses the Codacy analysis for the target commit and/or pull request.", + description: + "Bypasses the Codacy analysis for the target commit and/or pull request.", docs: "https://docs.gstraccini.bot/commands/codacy-bypass", - places: "Pull Requests" + places: "Pull Requests", }, { command: "@gstraccini codacy reanalyze commit", description: "Reanalyzes the Codacy last commit in a pull request.", docs: "https://docs.gstraccini.bot/commands/codacy-reanalyze-commit", - places: "Pull Requests" + places: "Pull Requests", }, { command: "@gstraccini codeclimate bypass", - description: "Bypasses the CodeClimate analysis for the target commit and/or pull request.", + description: + "Bypasses the CodeClimate analysis for the target commit and/or pull request.", docs: "https://docs.gstraccini.bot/commands/codeclimate-bypass", - places: "Pull Requests" + places: "Pull Requests", }, { command: "@gstraccini copy labels ", description: "Copies the labels from another repository.", docs: "https://docs.gstraccini.bot/commands/copy-labels", - places: "Issues" + places: "Issues", }, { command: "@gstraccini copy issue ", description: "Copies an issue from one repository to another.", docs: "https://docs.gstraccini.bot/commands/copy-issue", - places: "Issues" + places: "Issues", }, { command: "@gstraccini create labels