Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/.env.example β†’ backend/.env.sample
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
PORT=5000
MONGO_URI=mongodb://localhost:27017/githubTracker
SESSION_SECRET=your-secret-key
SESSION_SECRET=your-secret-key
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "1.0.0",
"main": "server.js",
"scripts": {
"start": "nodemon server.js",
"start": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
Expand Down
43 changes: 24 additions & 19 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
const express = require('express');
const mongoose = require('mongoose');
const session = require('express-session');
const passport = require('passport');
const bodyParser = require('body-parser');
require('dotenv').config();
const cors = require('cors');
const express = require("express");
const mongoose = require("mongoose");
const session = require("express-session");
const passport = require("passport");
const bodyParser = require("body-parser");
require("dotenv").config();
const cors = require("cors");

// Passport configuration
require('./config/passportConfig');
require("./config/passportConfig");

const app = express();

// CORS configuration
app.use(cors('*'));
app.use(cors("*"));

// Middleware
app.use(bodyParser.json());
app.use(session({
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
}));
})
);
app.use(passport.initialize());
app.use(passport.session());

// Routes
const authRoutes = require('./routes/auth');
app.use('/api/auth', authRoutes);
const authRoutes = require("./routes/auth");
app.use("/api/auth", authRoutes);

// Connect to MongoDB
mongoose.connect(process.env.MONGO_URI, {}).then(() => {
console.log('Connected to MongoDB');
mongoose
.connect(process.env.MONGO_URI, {})
.then(() => {
console.log("Connected to MongoDB");
app.listen(process.env.PORT, () => {
console.log(`Server running on port ${process.env.PORT}`);
console.log(`Server running on port ${process.env.PORT}`);
});
}).catch((err) => {
console.log('MongoDB connection error:', err);
});
})
.catch((err) => {
console.log("MongoDB connection error:", err);
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@
"tailwindcss": "^3.4.14",
"vite": "^5.4.10"
}

}
21 changes: 12 additions & 9 deletions src/Routes/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState, ChangeEvent, FormEvent } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom"; // Import the hook for navigation
import toast from "react-hot-toast";

const backendUrl = import.meta.env.VITE_BACKEND_URL;
interface LoginFormData {
Expand All @@ -9,7 +10,10 @@ interface LoginFormData {
}

const Login: React.FC = () => {
const [formData, setFormData] = useState<LoginFormData>({ email: "", password: "" });
const [formData, setFormData] = useState<LoginFormData>({
email: "",
password: "",
});
const [message, setMessage] = useState<string>("");

const navigate = useNavigate(); // Initialize the navigate hook
Expand All @@ -22,18 +26,18 @@ const Login: React.FC = () => {
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
try {
const response = await axios.post(`${backendUrl}/api/auth/login`,
formData,

const response = await axios.post(
`${backendUrl}/api/auth/login`,
formData
);
setMessage(response.data.message); // Show success message from backend
toast.success(response.data.message); // Show success message from backend

// Navigate to /home if login is successful
if (response.data.message === 'Login successful') {
if (response.data.message === "Login successful") {
navigate("/home");
}
} catch (error: any) {
setMessage(error.response?.data?.message || "Something went wrong");
toast.error(error.response?.data?.message || "Something went wrong");
}
};

Expand Down Expand Up @@ -70,10 +74,9 @@ const Login: React.FC = () => {
Login
</button>
</form>
{message && <p className="text-center text-red-500 mt-4">{message}</p>}
{/* {message && <p className="text-center text-red-500 mt-4">{message}</p>} */}
</div>
);
};

export default Login;

18 changes: 13 additions & 5 deletions src/Routes/Signup/Signup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState, ChangeEvent, FormEvent } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import toast from "react-hot-toast";

const backendUrl = import.meta.env.VITE_BACKEND_URL;
interface SignUpFormData {
Expand All @@ -10,7 +11,11 @@ interface SignUpFormData {
}

const SignUp: React.FC = () => {
const [formData, setFormData] = useState<SignUpFormData>({ username: "", email: "", password: "" });
const [formData, setFormData] = useState<SignUpFormData>({
username: "",
email: "",
password: "",
});
const [message, setMessage] = useState<string>("");

const navigate = useNavigate();
Expand All @@ -27,14 +32,17 @@ const SignUp: React.FC = () => {
`${backendUrl}/api/auth/signup`,
formData // Include cookies for session
);
setMessage(response.data.message); // Show success message from backend
toast.success(response.data.message); // Show success message from backend

// Navigate to login page after successful signup
if (response.data.message === 'User created successfully') {
if (response.data.message === "User created successfully") {
navigate("/login");
}
} catch (error: any) {
setMessage(error.response?.data?.message || "Something went wrong");
if (error.status === 404) {
toast.error("User not found");
} else
toast.error(error.response?.data?.message || "Something went wrong");
}
};

Expand Down Expand Up @@ -82,7 +90,7 @@ const SignUp: React.FC = () => {
Sign Up
</button>
</form>
{message && <p className="text-center text-red-500 mt-4">{message}</p>}
{/* {message && <p className="text-center text-red-500 mt-4">{message}</p>} */}
</div>
);
};
Expand Down
31 changes: 28 additions & 3 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Link } from 'react-router-dom';
import { useState } from 'react';
import { Link } from "react-router-dom";
import { useState } from "react";

const Navbar: React.FC = () => {
const [isOpen, setIsOpen] = useState<boolean>(false);
Expand Down Expand Up @@ -42,7 +42,17 @@ const Navbar: React.FC = () => {
<Link
to="/login"
className="text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
>Login</Link>
>
Login
</Link>

{/* signup button add */}
<Link
to="/signup"
className="text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
>
Signup
</Link>
</div>

{/* Mobile Menu Button */}
Expand Down Expand Up @@ -102,6 +112,21 @@ const Navbar: React.FC = () => {
>
Contributors
</Link>
{/* signup and login button add on mobile screen */}
<Link
to="/login"
className="block text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
onClick={() => setIsOpen(false)}
>
Login
</Link>
<Link
to="/signup"
className="block text-lg font-medium hover:text-gray-300 transition-all px-2 py-1 border border-transparent hover:border-gray-400 rounded"
onClick={() => setIsOpen(false)}
>
Signup
</Link>
</div>
</div>
)}
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/useGitHubAuth.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useState } from 'react';
import { Octokit } from '@octokit/core';
import toast from 'react-hot-toast';

export const useGitHubAuth = () => {
const [username, setUsername] = useState('');
const [token, setToken] = useState('');
const [error, setError] = useState('');

const getOctokit = () => {
if (!username || !token) return null;

if (!username || !token) return;
return new Octokit({ auth: token });
};

Expand Down
29 changes: 23 additions & 6 deletions src/hooks/useGitHubData.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useCallback } from 'react';
import toast from 'react-hot-toast';

export const useGitHubData = (octokit) => {
const [issues, setIssues] = useState([]);
Expand All @@ -22,8 +23,9 @@ export const useGitHubData = (octokit) => {
};

const fetchData = useCallback(async (username) => {
if (!octokit || !username) return;

if (!octokit || !username) {
toast.error("PAT not found"); return
}
setLoading(true);
setError('');

Expand All @@ -42,11 +44,26 @@ export const useGitHubData = (octokit) => {
per_page: 100,
}),
]);

setIssues(issuesResponse);
setPrs(prsResponse);
} catch (err) {
setError(err.message);

if (issuesResponse?.length || prsResponse?.length) toast.success(
`Fetched ${issuesResponse.length} issues and ${prsResponse.length} PRs successfully.`
);

else toast("⚠️ No issues or PRs found", { icon: "πŸ•΅οΈβ€β™‚οΈ" });



} catch (err: any) {
if (err.status === 401 || err.status === 403) {
toast.error("Please provide a valid GitHub token ");
}
else if (err.status === 422) toast.error("User not found")
else {
toast.error("something went wrong while fetching data")
}
// setError("Failed to fetched github date")
} finally {
setLoading(false);
}
Expand All @@ -56,7 +73,7 @@ export const useGitHubData = (octokit) => {
issues,
prs,
loading,
error,
// error,
fetchData,
};
};
34 changes: 24 additions & 10 deletions src/pages/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ const Home: React.FC = () => {
// Handle data submission to fetch GitHub data
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
fetchData(username);
const datafetch = fetchData(username);
console.log("fetch user detail: ", datafetch);
};

// Format date strings into a readable format
Expand All @@ -83,13 +84,14 @@ const Home: React.FC = () => {
};

// Filter data based on selected criteria
const filterData = (
data: GitHubItem[],
filterType: string
): GitHubItem[] => {
const filterData = (data: GitHubItem[], filterType: string): GitHubItem[] => {
let filteredData = [...data];

if (filterType === "open" || filterType === "closed" || filterType === "merged") {
if (
filterType === "open" ||
filterType === "closed" ||
filterType === "merged"
) {
filteredData = filteredData.filter((item) =>
filterType === "merged"
? item.pull_request?.merged_at
Expand Down Expand Up @@ -160,7 +162,11 @@ const Home: React.FC = () => {
required
sx={{ flex: 1 }}
/>
<Button type="submit" variant="contained" sx={{ minWidth: "120px" }}>
<Button
type="submit"
variant="contained"
sx={{ minWidth: "120px" }}
>
Fetch Data
</Button>
</Box>
Expand Down Expand Up @@ -210,12 +216,18 @@ const Home: React.FC = () => {
gap: 2,
}}
>
<Tabs value={tab} onChange={(e, newValue) => setTab(newValue)} sx={{ flex: 1 }}>
<Tabs
value={tab}
onChange={(e, newValue) => setTab(newValue)}
sx={{ flex: 1 }}
>
<Tab label={`Issues (${filterData(issues, issueFilter).length})`} />
<Tab label={`Pull Requests (${filterData(prs, prFilter).length})`} />
</Tabs>
<FormControl sx={{ minWidth: 150 }}>
<InputLabel sx={{ fontSize: "14px", color: "#555" }}>State</InputLabel>
<InputLabel sx={{ fontSize: "14px", color: "#555" }}>
State
</InputLabel>
<Select
value={tab === 0 ? issueFilter : prFilter}
onChange={(e) =>
Expand Down Expand Up @@ -261,7 +273,9 @@ const Home: React.FC = () => {
<TableHead>
<TableRow>
<TableCell sx={{ textAlign: "left" }}>Title</TableCell>
<TableCell sx={{ textAlign: "center" }}>Repository</TableCell>
<TableCell sx={{ textAlign: "center" }}>
Repository
</TableCell>
<TableCell sx={{ textAlign: "center" }}>State</TableCell>
<TableCell sx={{ textAlign: "left" }}>Created</TableCell>
</TableRow>
Expand Down
Loading