chore: auto-commit uncommitted changes

This commit is contained in:
James 2026-02-23 00:01:23 -05:00
parent 135d23468a
commit d99b455282
5 changed files with 212 additions and 3 deletions

View File

@ -120,3 +120,77 @@ func generateToken() string {
rand.Read(b)
return hex.EncodeToString(b)
}
func (h *Handler) handleSignupPage(w http.ResponseWriter, r *http.Request) {
// If already logged in, redirect to dashboard
cookie, err := r.Cookie("session")
if err == nil && cookie.Value != "" {
var count int
h.db.QueryRow("SELECT COUNT(*) FROM sessions WHERE token = ? AND expires_at > datetime('now')", cookie.Value).Scan(&count)
if count > 0 {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
}
templates.Signup().Render(r.Context(), w)
}
func (h *Handler) handleSignup(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Redirect(w, r, "/signup", http.StatusSeeOther)
return
}
// Parse form
err := r.ParseForm()
if err != nil {
http.Redirect(w, r, "/signup", http.StatusSeeOther)
return
}
orgName := r.FormValue("org_name")
name := r.FormValue("name")
email := r.FormValue("email")
password := r.FormValue("password")
// Create organization
orgID := generateID()
_, err = h.db.Exec(`INSERT INTO organizations (id, name, slug) VALUES (?, ?, ?)`, orgID, orgName, orgName)
if err != nil {
http.Redirect(w, r, "/signup", http.StatusSeeOther)
return
}
// Create user
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
userID := generateID()
_, err = h.db.Exec(`INSERT INTO profiles (id, email, full_name, role, organization_id, hashed_password) VALUES (?, ?, ?, ?, ?, ?)`,
userID, email, name, "owner", orgID, hashedPassword)
if err != nil {
http.Redirect(w, r, "/signup", http.StatusSeeOther)
return
}
// Auto login
token := make([]byte, 32)
rand.Read(token)
tokenHex := hex.EncodeToString(token)
_, err = h.db.Exec(`INSERT INTO sessions (id, user_id, token, expires_at) VALUES (?, ?, ?, datetime('now', '+24 hours'))`,
generateID(), userID, tokenHex)
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: tokenHex,
Path: "/",
Expires: time.Now().Add(24 * time.Hour),
HttpOnly: true,
})
http.Redirect(w, r, "/", http.StatusSeeOther)
}

View File

@ -35,7 +35,9 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
// Auth
mux.HandleFunc("/login", h.handleLoginPage)
mux.HandleFunc("/signup", h.handleSignupPage)
mux.HandleFunc("/auth/login", h.handleLogin)
mux.HandleFunc("/auth/signup", h.handleSignup)
mux.HandleFunc("/auth/logout", h.handleLogout)
mux.HandleFunc("/auth/view-toggle", h.requireAuth(h.handleViewToggle))

View File

@ -93,13 +93,27 @@ templ DealRoomDetail(profile *model.Profile, deal *model.Deal, folders []*model.
</div>
}
}
if profile.Role != "buyer" {
<div class="mt-4 pt-3 border-t border-gray-800">
<button class="w-full flex items-center justify-center gap-1.5 py-1.5 px-2 rounded text-sm text-gray-400 hover:text-teal-400 hover:bg-gray-800 transition-colors">
<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"></path></svg>
New Folder
</button>
</div>
}
</div>
<!-- Files Table -->
<div class="col-span-3 bg-gray-900 rounded-lg border border-gray-800">
<!-- Search -->
<div class="p-3 border-b border-gray-800">
<input type="text" id="fileSearch" placeholder="Search files..." oninput="filterFiles()" class="w-full px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-100 focus:border-teal-500 focus:outline-none"/>
<!-- Toolbar -->
<div class="p-3 border-b border-gray-800 flex gap-3">
<input type="text" id="fileSearch" placeholder="Search files..." oninput="filterFiles()" class="flex-1 px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-100 focus:border-teal-500 focus:outline-none"/>
if profile.Role != "buyer" {
<button class="px-3 py-1.5 bg-teal-600 hover:bg-teal-500 text-white text-sm font-medium rounded-lg transition-colors flex items-center gap-1.5">
<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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg>
Upload Document
</button>
}
</div>
<table class="w-full">
<thead>
@ -108,6 +122,7 @@ templ DealRoomDetail(profile *model.Profile, deal *model.Deal, folders []*model.
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-24">Size</th>
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-24">Date</th>
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-28">Status</th>
<th class="text-right px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-20">Actions</th>
</tr>
</thead>
<tbody id="fileTableBody" class="divide-y divide-gray-800/50">
@ -156,6 +171,7 @@ templ DealRoomDetail(profile *model.Profile, deal *model.Deal, folders []*model.
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider">Request Item</th>
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-16">Priority</th>
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-20">Atlas</th>
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider">Atlas Notes</th>
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider w-16">Conf.</th>
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider">Buyer</th>
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider">Seller</th>
@ -169,6 +185,13 @@ templ DealRoomDetail(profile *model.Profile, deal *model.Deal, folders []*model.
<td class="px-4 py-2.5 text-sm">{ req.Description }</td>
<td class="px-4 py-2.5">@PriorityBadge(req.Priority)</td>
<td class="px-4 py-2.5">@StatusIcon(req.AtlasStatus)</td>
<td class="px-4 py-2.5 text-xs text-gray-400">
if req.AtlasStatus == "Complete" {
<span class="text-teal-400/80">Found in Q3 Financials.pdf</span>
} else {
<span class="text-gray-600 italic">No notes</span>
}
</td>
<td class="px-4 py-2.5 text-xs text-gray-500">
if req.Confidence > 0 {
{ fmt.Sprintf("%d%%", req.Confidence) }

View File

@ -55,6 +55,10 @@ templ Login() {
Sign In
<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="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
</button>
<div class="text-center pt-4">
<span class="text-sm text-gray-500">Don't have an account? </span>
<a href="/signup" class="text-sm font-medium text-teal-600 hover:text-teal-500 transition-colors">Sign up your organization</a>
</div>
</form>
</div>
</div>

106
templates/signup.templ Normal file
View File

@ -0,0 +1,106 @@
package templates
templ Signup() {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Dealspace AI - Organization Sign Up</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen flex">
<!-- Left: Form -->
<div class="flex-1 flex items-center justify-center p-8">
<div class="w-full max-w-md">
<!-- Brand -->
<div class="flex items-center gap-2 mb-8">
<div class="w-10 h-10 rounded-xl bg-teal-500 flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path></svg>
</div>
<span class="text-xl font-bold text-gray-900">Dealspace AI</span>
</div>
<h1 class="text-3xl font-bold text-gray-900 mb-2">Create an Organization</h1>
<p class="text-gray-500 mb-8">Set up your workspace to manage deals, invite your team, and collaborate with buyers securely.</p>
<!-- Signup Form -->
<form action="/auth/signup" method="POST" class="space-y-4">
<div>
<label class="block text-xs font-medium text-gray-600 uppercase tracking-wider mb-1.5">Organization Name</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path></svg>
<input type="text" name="org_name" placeholder="e.g. Apex Capital Partners" class="w-full h-11 pl-10 pr-4 rounded-lg border border-gray-200 text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/50 focus:border-teal-500"/>
</div>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 uppercase tracking-wider mb-1.5">Your Name</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
<input type="text" name="name" placeholder="John Doe" class="w-full h-11 pl-10 pr-4 rounded-lg border border-gray-200 text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/50 focus:border-teal-500"/>
</div>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 uppercase tracking-wider mb-1.5">Work Email</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg>
<input type="email" name="email" placeholder="you@company.com" class="w-full h-11 pl-10 pr-4 rounded-lg border border-gray-200 text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/50 focus:border-teal-500"/>
</div>
</div>
<div>
<label class="block text-xs font-medium text-gray-600 uppercase tracking-wider mb-1.5">Password</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
<input type="password" name="password" placeholder="••••••••" class="w-full h-11 pl-10 pr-4 rounded-lg border border-gray-200 text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/50 focus:border-teal-500"/>
</div>
</div>
<div class="pt-2">
<button type="submit" class="w-full h-11 rounded-lg bg-teal-500 text-white font-medium text-sm hover:bg-teal-600 transition flex items-center justify-center gap-2">
Create Organization
<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="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
</button>
</div>
<div class="text-center pt-4">
<span class="text-sm text-gray-500">Already have an account? </span>
<a href="/login" class="text-sm font-medium text-teal-600 hover:text-teal-500 transition-colors">Sign in</a>
</div>
</form>
</div>
</div>
<!-- Right: Features Panel -->
<div class="flex-1 bg-gray-50 flex items-center justify-center p-8 hidden lg:flex">
<div class="max-w-md space-y-6">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Everything you need to close</h2>
<div class="flex gap-4">
<div class="w-10 h-10 shrink-0 rounded-full bg-teal-100 flex items-center justify-center">
<svg class="w-5 h-5 text-teal-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
</div>
<div>
<h3 class="font-medium text-gray-900">Bank-grade Security</h3>
<p class="text-sm text-gray-500 mt-1">AES-256-GCM encryption, granular role-based access, and comprehensive audit logs.</p>
</div>
</div>
<div class="flex gap-4">
<div class="w-10 h-10 shrink-0 rounded-full bg-teal-100 flex items-center justify-center">
<svg class="w-5 h-5 text-teal-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
</div>
<div>
<h3 class="font-medium text-gray-900">Multi-tenant Architecture</h3>
<p class="text-sm text-gray-500 mt-1">Manage multiple deals simultaneously. Keep buyers isolated and analytics centralized.</p>
</div>
</div>
<div class="flex gap-4">
<div class="w-10 h-10 shrink-0 rounded-full bg-teal-100 flex items-center justify-center">
<svg class="w-5 h-5 text-teal-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
</div>
<div>
<h3 class="font-medium text-gray-900">Atlas AI Assistant</h3>
<p class="text-sm text-gray-500 mt-1">Automated diligence tracking, instant document search, and intelligent summaries.</p>
</div>
</div>
</div>
</div>
</body>
</html>
}