Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Readme #36

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
83 changes: 80 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,82 @@
# Freelance-Job-Board
# JOB MTAANI
A Comprehensive Freelance Job Board Platform
August 12, 2024

[SQL Models]( https://drawsql.app/teams/raddames/diagrams/job-apllication )
# Developers
-Raddames Tonui
-Rehema Somo
-Shadrack Ngeno
-Regina Ngunga
-James Nyakundi

[Figma Design]( https://www.figma.com/design/80T0mxXVzQ7b3E4LaQBY1j/Freelance-Job-Board?node-id=0-1&t=M5LDMzvvintLLmJz-0 )
# Project Description
JOB MTAANI is a robust freelance job board platform designed to bridge the gap between freelancers and clients. It provides a seamless experience for job posting, application, and project management.

# Important Links
Deployed Frontend:
Deployed Backend:
UI Design: https://www.figma.com/design/80T0mxXVzQ7b3E4LaQBY1j/Freelance-Job-Board?node-id=0-1&t=M5LDMzvvintLLmJz-0
SQL Tables Diagram: https://drawsql.app/teams/raddames/diagrams/job-apllication

# Technologies Used
React (Vite)
Tailwind CSS
Python
Flask
SQLAlchemy
Payment Integration:
MPESA
# Setup/Installation Requirements
-Clone the Repository:
git clone https://github.com/your-username/job-mtaani.git
cd job-mtaani
-Backend Setup:
Navigate to the backend directory:
cd backend
-Install dependencies:
pip install -r requirements.txt
-Set up the database:
flask db upgrade
-Run the Flask application:
flask run
-Frontend Setup:
Navigate to the frontend directory:
cd frontend
-Install dependencies:
npm install
-Start the React application:
npm start
# User capabilities
# Freelancer
1.Profile Creation
2.Job Browsing
3.Proposal Submission
4.Proposal Tracking
5.Messaging
6.Secure Payments
7.Milestones Tracking
8.Reviews
# Client Capabilities:
1.Profile Creation
2.Job Posting
3.Proposal Management
4.Messaging
5.Progress Tracking
6.Secure Payments
7.Reviews
8.Freelancer Search
# Administrator Capabilities:
1.User Management
2.Activity Monitoring
3.Technical Support
4.Reporting for performance analysis

# Known Bugs
[List any known bugs or issues here.]

# Support and Contact Details
- [Raddames Tonui]([email protected])
- [Shadrack Ngeno]( @gmail.com)
- [Rehema Somo]([email protected])
- [Regina Ngunga]( @gmail.com)
- [James Nyakundi]([email protected])
11 changes: 11 additions & 0 deletions backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,17 @@ def delete_project(project_id):
db.session.commit()
return jsonify({"message": "Project deleted"}), 200

@app.route('/freelancer/projects', methods=['GET'])
@jwt_required()
def get_freelancer_projects():
print("Entering /freelancer/projects route") # Debug log
current_user_id = get_jwt_identity()
print(f"Current User ID: {current_user_id}") # Debug log
projects = Project.query.filter_by(freelancer_id=current_user_id).all()
print(f"Projects Found: {projects}") # Debug log
return jsonify([project.to_dict() for project in projects]), 200



# ================================ MILESTONES ================================

Expand Down
Binary file modified backend/instance/app.db
Binary file not shown.
7 changes: 7 additions & 0 deletions backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,13 @@ def to_dict(self):
"lastname": self.freelancer.lastname,
"username": self.freelancer.username
} if self.freelancer else None, # Including freelancer details if available

"client": {
"firstname": self.client.firstname,
"lastname": self.client.lastname,
"username": self.client.username
} if self.client else None, # Including client details if available

"created_at": self.created_at,
"updated_at": self.updated_at
}
Expand Down
Binary file added backend/uploads/resume/resume-sample.pdf
Binary file not shown.
103 changes: 91 additions & 12 deletions frontend/src/context/ProjectContext.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { createContext, useState, useEffect } from 'react';
import toast from 'react-hot-toast';
import { server_url } from '../../config.json';
import { server_url } from '../../config.json';

export const ProjectContext = createContext();

export const ProjectProvider = ({ children }) => {
const [projects, setProjects] = useState([]);
const [freelancerProjects, setFreelancerProjects] = useState([]);
const [clientProjects, setClientProjects] = useState([]);
const [acceptedFreelancers, setAcceptedFreelancers] = useState([]);
const [authToken, setAuthToken] = useState(localStorage.getItem('access_token'));

Expand All @@ -27,13 +29,12 @@ export const ProjectProvider = ({ children }) => {
.then(data => setProjects(data))
.catch(error => {
console.error('Failed to fetch projects:', error);

});
};

// Fetch freelancers accepted by the current client
const fetchAcceptedFreelancers = () => {
return fetch(`${server_url}/freelancers/accepted`, {
// Fetch projects assigned to the freelancer
const fetchFreelancerProjects = () => {
fetch(`${server_url}/freelancer/projects`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${authToken}`,
Expand All @@ -46,14 +47,60 @@ export const ProjectProvider = ({ children }) => {
}
return response.json();
})
.then(data => {
setAcceptedFreelancers(data);
return data;
.then(data => setFreelancerProjects(data))
.catch(error => {
console.error('Failed to fetch freelancer projects:', error);
});
};

// Fetch client projects
const fetchClientProjects = () => {
fetch(`${server_url}/client/projects`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`Error: ${response.status} ${response.statusText}`);
}
return response.json();
})
.then(data => setClientProjects(data))
.catch(error => {
console.error('Failed to fetch accepted freelancers:', error);

return [];
console.error('Failed to fetch client projects:', error);
});
};

// Update milestone status
const updateMilestoneStatus = (milestoneId, status) => {
fetch(`${server_url}/milestones/${milestoneId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ completed: status === 'completed' })
})
.then(response => {
if (!response.ok) {
return response.text().then(text => {
throw new Error(`Error: ${response.status} ${response.statusText}, Details: ${text}`);
});
}
return response.json();
})
.then(() => {

fetchFreelancerProjects();
fetchClientProjects();
toast.success('Milestone status updated successfully!');
})
.catch(error => {
console.error('Failed to update milestone status:', error);
toast.error(`Failed to update milestone status: ${error.message}`);
});
};

Expand Down Expand Up @@ -83,19 +130,51 @@ export const ProjectProvider = ({ children }) => {
});
};

// Fetch freelancers accepted by the current client
const fetchAcceptedFreelancers = () => {
return fetch(`${server_url}/freelancers/accepted`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`Error: ${response.status} ${response.statusText}`);
}
return response.json();
})
.then(data => {
setAcceptedFreelancers(data);
return data;
})
.catch(error => {
console.error('Failed to fetch accepted freelancers:', error);
return [];
});
};

const contextData = {
projects,
freelancerProjects,
clientProjects,
acceptedFreelancers,
fetchProjects,
fetchFreelancerProjects,
fetchClientProjects,
createProject,
updateMilestoneStatus,
setAuthToken,
fetchAcceptedFreelancers
fetchAcceptedFreelancers
};

useEffect(() => {
if (authToken) {
fetchProjects();
fetchAcceptedFreelancers();
fetchFreelancerProjects();
fetchClientProjects();
}
}, [authToken]);

Expand Down
80 changes: 73 additions & 7 deletions frontend/src/freelancers/FreelancerProjects.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,75 @@
import React from 'react'
import React, { useContext, useEffect } from 'react';
import { ProjectContext } from '../context/ProjectContext';
import { NavLink } from 'react-router-dom';

function FreelancerProjects() {
return (
<div>FreelancerProjects</div>
)
}
const FreelancerProjects = () => {
const { freelancerProjects, fetchFreelancerProjects, updateMilestoneStatus } = useContext(ProjectContext);

export default FreelancerProjects
useEffect(() => {
if (fetchFreelancerProjects) {
fetchFreelancerProjects();
}
}, [fetchFreelancerProjects]);

const handleStatusChange = (milestoneId, status) => {
const completed = status === 'completed';
updateMilestoneStatus(milestoneId, { completed });
};

return (
<div className="p-6 bg-gray-100 text-black shadow-lg h-[90vh] flex flex-col">
<h2 className="text-2xl font-bold mb-4">My Projects</h2>
<div className="flex-grow overflow-auto">
<table className="min-w-full bg-white">
<thead className="sticky top-0 bg-gray-200 text-black uppercase text-sm leading-normal">
<tr>
<th className="py-3 px-6 text-left">Title</th>
<th className="py-3 px-6 text-left">Client</th>
<th className="py-3 px-6 text-left">Milestone</th>
<th className="py-3 px-6 text-left">Deadline</th>
</tr>
</thead>
<tbody className="text-black text-sm font-light">
{freelancerProjects && freelancerProjects.length > 0 ? (
freelancerProjects.map((project) => (
<tr key={project.id} className="border-b border-gray-300 hover:bg-gray-200">
<td className="py-3 px-6 text-left">{project.title}</td>
<td className="py-3 px-6 text-left">
{project.client
? `${project.client.firstname} ${project.client.lastname}`
: 'Unknown Client'}
</td>
<td className="py-3 px-6 text-left">
{/* Always show the dropdown */}
<select
defaultValue={project.milestones && project.milestones.length > 0 ? project.milestones[0].status : 'started'}
onChange={(e) => {
if (project.milestones && project.milestones.length > 0) {
project.milestones.forEach(milestone => {
handleStatusChange(milestone.id, e.target.value);
});
}
}}
className="block w-full rounded-md border-0 py-2 pl-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6"
>
<option value="started">Started</option>
<option value="ongoing">Ongoing</option>
<option value="completed">Completed</option>
</select>
</td>
<td className="py-3 px-6 text-left">{project.deadline}</td>
</tr>
))
) : (
<tr>
<td colSpan="4" className="py-3 px-6 text-center">No projects available</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
};

export default FreelancerProjects;