Add GOTH stack CRM platform skeleton

- Go module with chi router, bcrypt
- SQLite schema for 9 tables
- Auth, Clients, Customers, Services, Scheduling, Payments, Q&A handlers
- HTMX template layouts
This commit is contained in:
2026-04-21 15:56:40 +00:00
parent 31039ea4af
commit 5b8107c169
14 changed files with 1529 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
{{ define "content" }}
<h1>Welcome to CRM</h1>
<p>Manage your clients, customers, and scheduling.</p>
{{ end }}

View File

@@ -0,0 +1,76 @@
package templates
import (
"html/template"
"sync"
)
var (
templates *template.Template
mu sync.RWMutex
)
func Init() {
templates = template.Must(template.New("").ParseGlob("internal/templates/*.html"))
}
func Layout(title, content string) *template.Template {
mu.RLock()
defer mu.RUnlock()
tmpl := template.Must(template.New("layout.html").Parse(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .Title }}</title>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
nav { background: white; border-bottom: 1px solid #e0e0e0; padding: 1rem 0; margin-bottom: 2rem; }
nav ul { list-style: none; display: flex; gap: 1.5rem; max-width: 1200px; margin: 0 auto; padding: 0 20px; }
nav a { color: #333; text-decoration: none; }
nav a:hover { color: #007bff; }
.card { background: white; border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.btn { display: inline-block; padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; }
.btn:hover { background: #0056b3; }
.btn-secondary { background: #6c757d; }
.btn-danger { background: #dc3545; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #e0e0e0; }
th { background: #f8f9fa; font-weight: 600; }
form { display: flex; flex-direction: column; gap: 1rem; max-width: 500px; }
input, textarea, select { padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; width: 100%; }
textarea { min-height: 100px; }
.error { color: #dc3545; }
.success { color: #28a745; }
</style>
</head>
<body>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/clients">Clients</a></li>
<li><a href="/customers">Customers</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/scheduling">Scheduling</a></li>
<li><a href="/payments">Payments</a></li>
<li><a href="/questions">Questions</a></li>
</ul>
</nav>
<div class="container">
{{ template "content" . }}
</div>
</body>
</html>`))
return tmpl
}
func Get(name string) *template.Template {
mu.RLock()
defer mu.RUnlock()
return templates.Lookup(name)
}