chore: auto-commit uncommitted changes
This commit is contained in:
parent
135d23468a
commit
d99b455282
|
|
@ -120,3 +120,77 @@ func generateToken() string {
|
||||||
rand.Read(b)
|
rand.Read(b)
|
||||||
return hex.EncodeToString(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,9 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
mux.HandleFunc("/login", h.handleLoginPage)
|
mux.HandleFunc("/login", h.handleLoginPage)
|
||||||
|
mux.HandleFunc("/signup", h.handleSignupPage)
|
||||||
mux.HandleFunc("/auth/login", h.handleLogin)
|
mux.HandleFunc("/auth/login", h.handleLogin)
|
||||||
|
mux.HandleFunc("/auth/signup", h.handleSignup)
|
||||||
mux.HandleFunc("/auth/logout", h.handleLogout)
|
mux.HandleFunc("/auth/logout", h.handleLogout)
|
||||||
mux.HandleFunc("/auth/view-toggle", h.requireAuth(h.handleViewToggle))
|
mux.HandleFunc("/auth/view-toggle", h.requireAuth(h.handleViewToggle))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,27 @@ templ DealRoomDetail(profile *model.Profile, deal *model.Deal, folders []*model.
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Files Table -->
|
<!-- Files Table -->
|
||||||
<div class="col-span-3 bg-gray-900 rounded-lg border border-gray-800">
|
<div class="col-span-3 bg-gray-900 rounded-lg border border-gray-800">
|
||||||
<!-- Search -->
|
<!-- Toolbar -->
|
||||||
<div class="p-3 border-b border-gray-800">
|
<div class="p-3 border-b border-gray-800 flex gap-3">
|
||||||
<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"/>
|
<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>
|
</div>
|
||||||
<table class="w-full">
|
<table class="w-full">
|
||||||
<thead>
|
<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">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-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-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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="fileTableBody" class="divide-y divide-gray-800/50">
|
<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">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-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 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 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">Buyer</th>
|
||||||
<th class="text-left px-4 py-2.5 text-xs font-medium text-gray-500 uppercase tracking-wider">Seller</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 text-sm">{ req.Description }</td>
|
||||||
<td class="px-4 py-2.5">@PriorityBadge(req.Priority)</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">@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">
|
<td class="px-4 py-2.5 text-xs text-gray-500">
|
||||||
if req.Confidence > 0 {
|
if req.Confidence > 0 {
|
||||||
{ fmt.Sprintf("%d%%", req.Confidence) }
|
{ fmt.Sprintf("%d%%", req.Confidence) }
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,10 @@ templ Login() {
|
||||||
Sign In
|
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>
|
<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>
|
</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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue