feat: seller/buyer view toggle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
James 2026-02-22 01:08:21 -05:00
parent 6ab568ba4f
commit 135d23468a
6 changed files with 54 additions and 2 deletions

View File

@ -77,6 +77,44 @@ func (h *Handler) createSession(w http.ResponseWriter, userID string) {
})
}
func (h *Handler) handleViewToggle(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
profile := getProfile(r.Context())
if profile.Role != "owner" && profile.Role != "admin" {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// Toggle the cookie
current := false
if c, err := r.Cookie("view_as_buyer"); err == nil && c.Value == "1" {
current = true
}
value := "1"
if current {
value = ""
}
http.SetCookie(w, &http.Cookie{
Name: "view_as_buyer",
Value: value,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
// Redirect back to referrer or dashboard
ref := r.Header.Get("Referer")
if ref == "" {
ref = "/"
}
http.Redirect(w, r, ref, http.StatusSeeOther)
}
func generateToken() string {
b := make([]byte, 32)
rand.Read(b)

View File

@ -234,7 +234,7 @@ func (h *Handler) getRequests(dealID string, profile *model.Profile) []*model.Di
query := "SELECT id, deal_id, item_number, section, description, priority, atlas_status, atlas_note, confidence, buyer_comment, seller_comment, buyer_group FROM diligence_requests WHERE deal_id = ?"
args := []interface{}{dealID}
if rbac.IsBuyer(profile.Role) {
if rbac.EffectiveIsBuyer(profile) {
groups := rbac.BuyerGroups(profile)
if len(groups) > 0 {
placeholders := make([]string, len(groups))

View File

@ -37,6 +37,7 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/login", h.handleLoginPage)
mux.HandleFunc("/auth/login", h.handleLogin)
mux.HandleFunc("/auth/logout", h.handleLogout)
mux.HandleFunc("/auth/view-toggle", h.requireAuth(h.handleViewToggle))
// Pages (auth required)
mux.HandleFunc("/", h.requireAuth(h.handleDashboard))
@ -94,6 +95,13 @@ func (h *Handler) requireAuth(next http.HandlerFunc) http.HandlerFunc {
return
}
// Check view-as-buyer toggle (only applies to sellers)
if profile.Role == "owner" || profile.Role == "admin" {
if c, err := r.Cookie("view_as_buyer"); err == nil && c.Value == "1" {
profile.ViewAsBuyer = true
}
}
ctx := setProfile(r.Context(), &profile)
next.ServeHTTP(w, r.WithContext(ctx))
}

View File

@ -56,7 +56,7 @@ func (h *Handler) handleUpdateComment(w http.ResponseWriter, r *http.Request) {
value := r.FormValue("value")
field := "seller_comment"
if rbac.IsBuyer(profile.Role) {
if rbac.EffectiveIsBuyer(profile) {
field = "buyer_comment"
}

View File

@ -31,6 +31,7 @@ type Profile struct {
PasswordHash string
CreatedAt time.Time
LastLogin *time.Time
ViewAsBuyer bool // Not persisted, set per-request from cookie
}
type Deal struct {

View File

@ -12,6 +12,11 @@ func IsBuyer(role string) bool {
return role == "viewer" || role == "member"
}
// EffectiveIsBuyer returns true if ViewAsBuyer is set OR real role is buyer
func EffectiveIsBuyer(profile *model.Profile) bool {
return profile.ViewAsBuyer || IsBuyer(profile.Role)
}
// BuyerGroups returns the buyer groups for the demo buyer
func BuyerGroups(profile *model.Profile) []string {
if IsBuyer(profile.Role) {