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