Skip to content

Commit

Permalink
Merge branch 'main' into about-badges
Browse files Browse the repository at this point in the history
  • Loading branch information
asmit27rai authored Dec 30, 2024
2 parents d665ee6 + e1f5034 commit 682e1c5
Show file tree
Hide file tree
Showing 3 changed files with 683 additions and 2 deletions.
2 changes: 2 additions & 0 deletions blt/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
ProjectListView,
ProjectView,
blt_tomato,
create_project,
distribute_bacon,
select_contribution,
)
Expand Down Expand Up @@ -797,6 +798,7 @@
TemplateView.as_view(template_name="similarity.html"),
name="similarity_scan",
),
path("projects/create/", create_project, name="create_project"),
]

if settings.DEBUG:
Expand Down
331 changes: 331 additions & 0 deletions website/templates/projects/project_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
Project List
{% endblock title %}
{% block content %}
<!-- Toast Container -->
<div id="toastContainer" class="fixed top-4 right-4 z-50 space-y-4"></div>
<!-- Loading Overlay -->
<div id="loadingOverlay"
class="fixed inset-0 bg-gray-900 bg-opacity-50 hidden z-[60] flex items-center justify-center">
<div class="bg-white p-6 rounded-lg shadow-xl flex items-center gap-4">
<div class="animate-spin rounded-full h-8 w-8 border-4 border-red-500 border-t-transparent"></div>
<span class="text-gray-700 text-lg font-medium">Processing...</span>
</div>
</div>
{% include "includes/sidenav.html" %}
<div class="container py-6 mx-auto px-14 max-w-[1300px]">
<!-- Stats Overview - Made more compact -->
Expand All @@ -24,6 +34,28 @@ <h3 class="text-sm font-medium text-gray-500">Filtered Results</h3>
<p class="text-2xl font-bold text-gray-900">{{ filtered_count }}</p>
</div>
{% endif %}
{% if user.is_authenticated %}
<button type="button"
onclick="openAddProjectModal()"
class="px-6 py-3 bg-red-500 text-white rounded-lg hover:bg-red-600 transform hover:scale-105 transition-all duration-200 font-medium flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Add New Project
</button>
{% else %}
<button type="button"
disabled
class="px-6 py-3 bg-gray-300 text-gray-600 rounded-lg cursor-not-allowed flex items-center gap-2 group relative">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Add New Project
<div class="absolute -bottom-12 left-1/2 transform -translate-x-1/2 w-48 bg-gray-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
Please login to add a new project
</div>
</button>
{% endif %}
</div>
</div>
<!-- Filter Section - More compact -->
Expand Down Expand Up @@ -242,4 +274,303 @@ <h3 class="text-xl font-bold text-gray-800">{{ repo.name }}</h3>
</div>
{% endif %}
</div>
<!-- Add Project Modal -->
<div id="addProjectModal"
class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden z-50">
<div class="fixed inset-0 overflow-y-auto">
<div class="flex min-h-full items-center justify-center p-4">
<div class="relative bg-white rounded-lg shadow-xl w-full max-w-4xl">
<!-- Modal Header -->
<div class="flex items-center justify-between p-4 border-b">
<h3 class="text-xl font-bold text-gray-900">Add New Project</h3>
<button type="button"
onclick="closeAddProjectModal()"
class="text-gray-400 hover:text-gray-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Modal Body with Scrollable Content -->
<div class="max-h-[calc(100vh-200px)] overflow-y-auto p-6">
<form id="addProjectForm" onsubmit="submitProjectForm(event)">
{% csrf_token %}
<div class="space-y-6">
<!-- Project Basic Details -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700">Project Name *</label>
<input type="text"
name="name"
required
class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500" />
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Project URL</label>
<input type="url"
name="url"
class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500" />
</div>
{% if user.is_authenticated %}
<div>
<label class="block text-sm font-medium text-gray-700">Organization</label>
<select name="organization"
class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500">
<option value="">No Organization (Independent Project)</option>
{% for org in user_organizations %}<option value="{{ org.id }}">{{ org.name }}</option>{% endfor %}
</select>
</div>
{% endif %}
<div>
<label class="block text-sm font-medium text-gray-700">Project Logo</label>
<input type="file"
name="logo"
accept="image/*"
class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500" />
</div>
</div>
<!-- Project Description -->
<div>
<label class="block text-sm font-medium text-gray-700">Description *</label>
<textarea name="description"
required
rows="3"
class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500"></textarea>
</div>
<!-- Social Links -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700">Twitter URL</label>
<input type="url"
name="twitter"
class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500"
placeholder="https://twitter.com/..." />
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Facebook URL</label>
<input type="url"
name="facebook"
class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500"
placeholder="https://facebook.com/..." />
</div>
</div>
<!-- Repositories Section -->
<div>
<h4 class="text-lg font-medium text-gray-900 mb-4">Repositories</h4>
<div id="reposList" class="space-y-4">
<!-- Initial repo input -->
<div class="repo-entry bg-gray-50 p-4 rounded-lg">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700">Repository URL *</label>
<input type="url"
name="repo_urls[]"
required
class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500" />
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Repository Type</label>
<select name="repo_types[]"
class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500">
<option value="normal">Normal</option>
<option value="main">Main</option>
<option value="wiki">Wiki</option>
</select>
</div>
</div>
</div>
</div>
<!-- Add Repository Button -->
<button type="button"
onclick="addRepoField()"
class="mt-4 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 flex items-center gap-2 text-sm">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Add Another Repository
</button>
</div>
</div>
<!-- Form Actions -->
<div class="mt-6 flex justify-end gap-4">
<button type="button"
onclick="closeAddProjectModal()"
class="px-6 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">
Cancel
</button>
<button type="submit"
class="px-6 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600">
Create Project
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Add this to your existing scripts section or create one -->
<script>
// Toast notification system
function showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `transform transition-all duration-300 ease-out scale-95 opacity-0
px-6 py-4 rounded-lg shadow-lg max-w-sm w-full
${type === 'success' ? 'bg-green-500' : 'bg-red-500'} text-white`;

toast.innerHTML = `
<div class="flex items-center justify-between">
<span class="font-medium">${message}</span>
<button onclick="this.parentElement.parentElement.remove()" class="ml-4 hover:opacity-75">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
`;

document.getElementById('toastContainer').appendChild(toast);

// Animate in
setTimeout(() => {
toast.classList.remove('scale-95', 'opacity-0');
toast.classList.add('scale-100', 'opacity-100');
}, 10);

// Auto remove after 5 seconds
setTimeout(() => {
toast.classList.add('scale-95', 'opacity-0');
setTimeout(() => toast.remove(), 300);
}, 5000);
}

// Loading state management
function setLoading(isLoading) {
const overlay = document.getElementById('loadingOverlay');
const submitBtn = document.querySelector('#addProjectForm button[type="submit"]');

if (isLoading) {
overlay.classList.remove('hidden');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = `
<div class="flex items-center gap-2">
<div class="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></div>
<span>Creating Project...</span>
</div>
`;
}
} else {
overlay.classList.add('hidden');
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.innerHTML = 'Create Project';
}
}
}

function openAddProjectModal() {
document.getElementById('addProjectModal').classList.remove('hidden');
document.body.style.overflow = 'hidden';
}

function closeAddProjectModal() {
document.getElementById('addProjectModal').classList.add('hidden');
document.body.style.overflow = 'auto';
}

function addRepoField() {
const reposList = document.getElementById('reposList');
const newRepo = document.createElement('div');
newRepo.className = 'repo-entry bg-gray-50 p-4 rounded-lg relative';
newRepo.innerHTML = `
<button type="button" onclick="this.parentElement.remove()" class="absolute top-2 right-2 text-gray-400 hover:text-gray-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700">Repository URL *</label>
<input type="url" name="repo_urls[]" required class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500" />
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Repository Type</label>
<select name="repo_types[]" class="mt-1 w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-red-500">
<option value="normal">Normal</option>
<option value="main">Main</option>
<option value="wiki">Wiki</option>
</select>
</div>
</div>
`;
reposList.appendChild(newRepo);
}

async function submitProjectForm(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);

setLoading(true);

try {
const response = await fetch('{% url "create_project" %}', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
}
});

const data = await response.json();

if (!response.ok) {
// Handle specific error codes
let errorMessage = data.error || 'Failed to create project';

switch(data.code) {
case 'INVALID_PROJECT_URL':
errorMessage = 'The project URL is not accessible. Please verify the URL and try again.';
break;
case 'INVALID_TWITTER_URL':
errorMessage = 'The Twitter URL is not accessible. Please verify the URL and try again.';
break;
case 'INVALID_FACEBOOK_URL':
errorMessage = 'The Facebook URL is invalid or not accessible. Please verify the URL and try again.';
break;
case 'NON_GITHUB_URL':
case 'INVALID_GITHUB_URL_FORMAT':
case 'INACCESSIBLE_REPO':
errorMessage = data.error;
break;
case 'NAME_EXISTS':
errorMessage = 'A project with this name already exists.';
break;
case 'URL_EXISTS':
errorMessage = 'A project with this URL already exists.';
break;
case 'REPOS_EXIST':
errorMessage = 'One or more repositories are already linked to other projects.';
break;
case 'PERMISSION_DENIED':
errorMessage = 'You do not have permission to add projects to this organization.';
break;
}

throw new Error(errorMessage);
}

showToast('Project created successfully!', 'success');
closeAddProjectModal();
window.location.reload();

} catch (error) {
console.error('Error:', error);
showToast(error.message || 'Failed to create project. Please try again.', 'error');
} finally {
setLoading(false);
}
}
</script>
{% endblock content %}
Loading

0 comments on commit 682e1c5

Please sign in to comment.