---
import Layout from '../layouts/Layout.astro';
// Server-side data fetching
let projects = [];
let error = null;
let currentProject = null;
// Get auth context from middleware
const authContext = Astro.locals.auth;
const currentProjectId = Astro.locals.currentProjectId;
// Only fetch data if user is authenticated
if (authContext.isAuthenticated) {
try {
// Always use container service name for internal communication
const baseUrl = 'http://vultr-backend:8000';
const response = await fetch(`${baseUrl}/api/projects/`, {
headers: {
'Authorization': `Bearer ${Astro.cookies.get('auth_token')?.value}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
projects = await response.json();
// Find current project if set
if (currentProjectId) {
currentProject = projects.find(p => p.id === currentProjectId);
}
} else {
console.error('Failed to fetch projects:', response.status, response.statusText);
error = 'Failed to load projects. Please try again.';
}
} catch (err) {
console.error('Error fetching projects:', err);
error = 'Network error while loading projects.';
}
}
---
<Layout title="Projects - Service Collection Management">
<div class="py-6"
data-projects={JSON.stringify(projects)}
x-data="projectManager(JSON.parse($el.dataset.projects || '[]'))"
x-init="init()">
<!-- Page Header -->
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="md:flex md:items-center md:justify-between">
<div class="min-w-0 flex-1">
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
Projects
</h2>
<p class="mt-1 text-sm text-gray-500">
Manage your project workspaces and team access
</p>
</div>
<div class="mt-4 flex md:ml-4 md:mt-0">
<button @click="openCreateModal()"
class="btn-primary">
<svg class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
New Project
</button>
</div>
</div>
</div>
{error && (
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 mt-8">
<div class="rounded-md bg-red-50 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800">Error Loading Projects</h3>
<div class="mt-2 text-sm text-red-700">{error}</div>
</div>
</div>
</div>
</div>
)}
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 mt-8">
{projects.length === 0 && !error ? (
<!-- Empty State -->
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" />
</svg>
<h3 class="mt-2 text-sm font-semibold text-gray-900">No projects</h3>
<p class="mt-1 text-sm text-gray-500">Get started by creating your first project workspace.</p>
<div class="mt-6">
<button @click="openCreateModal()" class="btn-primary">
<svg class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
New Project
</button>
</div>
</div>
) : (
<!-- Project Cards -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{projects.map((project) => (
<div class="group relative rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm hover:shadow-md transition-shadow"
@click.away={`isDropdownOpen('${project.id}') && closeAllDropdowns()`}>
<!-- Project Header -->
<div class="flex items-center justify-between">
<div class="flex items-center">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg"
style={`background-color: ${project.color || '#6366f1'}`}>
<span class="text-sm font-medium text-white">
{project.name.charAt(0).toUpperCase()}
</span>
</div>
<div class="ml-4">
<h3 class="text-base font-semibold leading-6 text-gray-900">{project.name}</h3>
<p class="text-sm text-gray-500">{project.slug}</p>
</div>
</div>
<!-- Project Menu -->
<div class="relative">
<button
@click.stop={`toggleDropdown('${project.id}')`}
class="opacity-0 group-hover:opacity-100 transition-opacity rounded-full bg-gray-100 p-1 text-gray-400 hover:text-gray-600">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 12.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 18.75a.75.75 0 110-1.5.75.75 0 010 1.5z" />
</svg>
</button>
<div
x-show={`isDropdownOpen('${project.id}')`}
x-transition
class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5">
<div class="py-1">
<button @click.stop={`selectProject(${JSON.stringify(project)})`} class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100">
Switch to Project
</button>
<button @click.stop={`editProject(${JSON.stringify(project)})`} class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100">
Edit Project
</button>
<button @click.stop={`openTeamModal(${JSON.stringify(project)})`} class="block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100">
Manage Team
</button>
<hr class="my-1" />
<button @click.stop={`deleteProject(${JSON.stringify(project)})`} class="block w-full px-4 py-2 text-left text-sm text-red-600 hover:bg-red-50">
Delete Project
</button>
</div>
</div>
</div>
</div>
<!-- Project Description -->
<div class="mt-4">
<p class="text-sm text-gray-600 line-clamp-2">{project.description || 'No description'}</p>
</div>
<!-- Project Stats -->
<div class="mt-4 flex items-center justify-between">
<div class="flex space-x-4 text-sm text-gray-500">
<span>{project.collection_count} collections</span>
<span>{project.member_count} members</span>
</div>
<div class="flex items-center space-x-1">
<div class={`h-2 w-2 rounded-full ${
project.status === 'active' ? 'bg-green-400' :
project.status === 'suspended' ? 'bg-yellow-400' :
'bg-gray-400'
}`}></div>
<span class="text-sm text-gray-500 capitalize">{project.status}</span>
</div>
</div>
<!-- Click to view details -->
<a
href={`/projects/${project.id}`}
class="absolute inset-0 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded-lg">
<span class="sr-only">View project details for {project.name}</span>
</a>
</div>
))}
</div>
)}
</div>
<!-- Create/Edit Project Modal -->
<div x-show="projectModal"
x-transition
class="fixed inset-0 z-50 overflow-y-auto"
@click.self="closeProjectModal()">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
<div class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6"
@click.stop>
<form @submit.prevent="submitForm()">
<div>
<h3 x-text="modalTitle" class="text-lg font-semibold leading-6 text-gray-900"></h3>
<div class="mt-6 space-y-4">
<!-- Project Name -->
<div>
<label for="project-name" class="block text-sm font-medium text-gray-700">Project Name</label>
<input type="text"
id="project-name"
x-model="form.name"
required
:class="validationErrors.name ? 'form-input mt-1 block w-full border-red-300 focus:ring-red-500 focus:border-red-500' : 'form-input mt-1 block w-full'"
placeholder="Enter project name">
<p x-show="validationErrors.name" x-text="validationErrors.name" class="mt-1 text-sm text-red-600"></p>
</div>
<!-- Project Slug -->
<div>
<label for="project-slug" class="block text-sm font-medium text-gray-700">URL Slug</label>
<input type="text"
id="project-slug"
x-model="form.slug"
required
:class="validationErrors.slug ? 'form-input mt-1 block w-full border-red-300 focus:ring-red-500 focus:border-red-500' : 'form-input mt-1 block w-full'"
placeholder="project-slug">
<p x-show="validationErrors.slug" x-text="validationErrors.slug" class="mt-1 text-sm text-red-600"></p>
<p x-show="!validationErrors.slug" class="mt-1 text-xs text-gray-500">Lowercase letters, numbers, and hyphens only</p>
</div>
<!-- Description -->
<div>
<label for="project-description" class="block text-sm font-medium text-gray-700">Description</label>
<textarea id="project-description"
x-model="form.description"
rows="3"
class="form-input mt-1 block w-full"
placeholder="Describe your project..."></textarea>
</div>
<!-- Color -->
<div>
<label for="project-color" class="block text-sm font-medium text-gray-700">Theme Color</label>
<div class="mt-1 flex items-center space-x-3">
<input type="color"
id="project-color"
x-model="form.color"
class="h-10 w-16 border border-gray-300 rounded-md">
<input type="text"
id="project-color-text"
x-model="form.color"
class="form-input flex-1"
placeholder="#6366f1">
</div>
</div>
</div>
</div>
<div class="mt-6 flex items-center justify-end space-x-3">
<button type="button" @click="closeProjectModal()" class="btn-secondary">
Cancel
</button>
<button type="submit"
:disabled="!canSubmit"
class="btn-primary disabled:opacity-50 disabled:cursor-not-allowed">
<span x-text="submitButtonText"></span>
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Team Management Modal -->
<div x-show="teamModal"
x-transition
class="fixed inset-0 z-50 overflow-y-auto"
@click.self="closeTeamModal()">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
<div class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6"
@click.stop>
<div>
<h3 class="text-lg font-semibold leading-6 text-gray-900">
Manage Team
</h3>
<div class="mt-6">
<!-- Team member list would go here -->
<p class="text-gray-500 text-center py-8">Team management interface coming soon...</p>
</div>
</div>
<div class="mt-6 flex items-center justify-end">
<button @click="closeTeamModal()" class="btn-secondary">
Close
</button>
</div>
</div>
</div>
</div>
</div>
</Layout>