Changeset 0.26.0 (#165)
This commit is contained in:
@@ -182,6 +182,11 @@ func (e *Engine) registerCoreSurfaces() {
|
||||
DataRequires: []string{"workflow"},
|
||||
Layout: "single", Source: "core",
|
||||
},
|
||||
{
|
||||
ID: "workflow-landing", Route: "/w/:id/:slug",
|
||||
Title: "Workflow Landing", Template: "workflow-landing", Auth: "public",
|
||||
Layout: "single", Source: "core",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +382,7 @@ func (e *Engine) RegisterPageRoutes(base *gin.RouterGroup, mw PageRouteMiddlewar
|
||||
registerRoutes(base, s, e.disabledRedirect())
|
||||
continue
|
||||
}
|
||||
handler := e.RenderSurface(s.ID)
|
||||
handler := e.surfaceHandler(s)
|
||||
registerRoutes(base, s, handler)
|
||||
}
|
||||
}
|
||||
@@ -390,6 +395,9 @@ func (e *Engine) surfaceHandler(s SurfaceManifest) gin.HandlerFunc {
|
||||
if s.ID == "workflow" {
|
||||
return e.RenderWorkflow()
|
||||
}
|
||||
if s.ID == "workflow-landing" {
|
||||
return e.RenderWorkflowLanding()
|
||||
}
|
||||
return e.RenderSurface(s.ID)
|
||||
}
|
||||
|
||||
@@ -469,6 +477,24 @@ type WorkflowPageData struct {
|
||||
SessionName string
|
||||
}
|
||||
|
||||
// WorkflowLandingPageData is passed to workflow-landing.html.
|
||||
type WorkflowLandingPageData struct {
|
||||
WorkflowID string
|
||||
Scope string
|
||||
Slug string
|
||||
Name string
|
||||
Description string
|
||||
Branding struct {
|
||||
AccentColor string `json:"accent_color"`
|
||||
LogoURL string `json:"logo_url"`
|
||||
Tagline string `json:"tagline"`
|
||||
}
|
||||
PersonaName string
|
||||
PersonaIcon string
|
||||
StageCount int
|
||||
ResumeURL string // non-empty if visitor has an active session
|
||||
}
|
||||
|
||||
// RenderWorkflow renders the workflow entry page for anonymous session visitors.
|
||||
// The middleware (AuthOrSession) has already created/resumed the session.
|
||||
func (e *Engine) RenderWorkflow() gin.HandlerFunc {
|
||||
@@ -514,6 +540,80 @@ func (e *Engine) RenderWorkflow() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// RenderWorkflowLanding renders the public landing page for a workflow.
|
||||
// Visitors see the workflow name, description, persona info, and a Start button.
|
||||
func (e *Engine) RenderWorkflowLanding() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
// Route: /w/:id/:slug — :id is scope (team-id or "global"),
|
||||
// :slug is the workflow slug. Same :id param name as /w/:id
|
||||
// (chat surface) to avoid Gin wildcard conflicts.
|
||||
scope := c.Param("id")
|
||||
slug := c.Param("slug")
|
||||
|
||||
// Resolve scope to team_id
|
||||
var teamID *string
|
||||
if scope != "global" {
|
||||
teamID = &scope
|
||||
}
|
||||
|
||||
if e.stores.Workflows == nil {
|
||||
c.String(http.StatusNotFound, "Workflows not available")
|
||||
return
|
||||
}
|
||||
|
||||
wf, err := e.stores.Workflows.GetBySlug(ctx, teamID, slug)
|
||||
if err != nil || wf == nil {
|
||||
c.String(http.StatusNotFound, "Workflow not found")
|
||||
return
|
||||
}
|
||||
if !wf.IsActive {
|
||||
c.String(http.StatusNotFound, "Workflow not available")
|
||||
return
|
||||
}
|
||||
|
||||
data := WorkflowLandingPageData{
|
||||
WorkflowID: wf.ID,
|
||||
Scope: scope,
|
||||
Slug: slug,
|
||||
Name: wf.Name,
|
||||
Description: wf.Description,
|
||||
}
|
||||
|
||||
// Parse branding
|
||||
if len(wf.Branding) > 0 {
|
||||
_ = json.Unmarshal(wf.Branding, &data.Branding)
|
||||
}
|
||||
|
||||
// Load stages for count + first persona info
|
||||
stages, _ := e.stores.Workflows.ListStages(ctx, wf.ID)
|
||||
data.StageCount = len(stages)
|
||||
if len(stages) > 0 && stages[0].PersonaID != nil {
|
||||
if p, err := e.stores.Personas.GetByID(ctx, *stages[0].PersonaID); err == nil {
|
||||
data.PersonaName = p.Name
|
||||
data.PersonaIcon = p.Icon
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing active session
|
||||
sbSession, err := c.Cookie("sb_session")
|
||||
if err == nil && sbSession != "" && e.stores.Sessions != nil {
|
||||
sess, err := e.stores.Sessions.GetByToken(ctx, sbSession)
|
||||
if err == nil && sess != nil {
|
||||
data.ResumeURL = "/w/" + sess.ChannelID
|
||||
}
|
||||
}
|
||||
|
||||
instanceName, _, _ := e.loadBranding()
|
||||
|
||||
e.Render(c, "workflow-landing.html", PageData{
|
||||
Surface: "workflow-landing",
|
||||
InstanceName: instanceName,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// getUserContext extracts user info from gin context (set by auth middleware)
|
||||
// and enriches it with display name, username, and email from the database.
|
||||
func (e *Engine) getUserContext(c *gin.Context) *UserContext {
|
||||
|
||||
Reference in New Issue
Block a user