Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 60 additions & 4 deletions apps/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,14 +248,30 @@ func Preview() string {
limit = len(public)
}
for _, a := range public[:limit] {
sb.WriteString(fmt.Sprintf(`<p style="display:flex;align-items:center;gap:8px;"><img src="/apps/%s/icon.svg" width="20" height="20"><a href="/apps/%s">%s</a> — %s</p>`,
priceTag := `<span style="color:#090;font-size:12px;">Free</span>`
if a.Price > 0 {
priceTag = fmt.Sprintf(`<span style="color:#c60;font-size:12px;">%d credits</span>`, a.Price)
}
sb.WriteString(fmt.Sprintf(`<p style="display:flex;align-items:center;gap:8px;"><img src="/apps/%s/icon.svg" width="20" height="20"><a href="/apps/%s">%s</a> — %s %s</p>`,
htmlpkg.EscapeString(a.Slug),
htmlpkg.EscapeString(a.Slug),
htmlpkg.EscapeString(a.Name),
htmlpkg.EscapeString(truncate(a.Description, 60)),
htmlpkg.EscapeString(truncate(a.Description, 50)),
priceTag,
))
}
sb.WriteString(fmt.Sprintf(`<p class="card-desc">%d apps available · <a href="/apps/build">Build new</a></p>`, len(public)))
paidCount := 0
for _, a := range public {
if a.Price > 0 {
paidCount++
}
}
sb.WriteString(fmt.Sprintf(`<p class="card-desc">%d apps available`, len(public)))
if paidCount > 0 {
sb.WriteString(fmt.Sprintf(` · %d paid`, paidCount))
}
sb.WriteString(` · <a href="/apps/build">Build new</a></p>`)
sb.WriteString(`<p style="font-size:12px;color:#999;">Build apps, set your price, earn 90%% of every sale.</p>`)
return sb.String()
}

Expand Down Expand Up @@ -381,7 +397,10 @@ func handleList(w http.ResponseWriter, r *http.Request) {

// HTML
var sb strings.Builder
sb.WriteString(`<p class="card-desc">Small, useful apps that do one thing well. No ads, no tracking, no bloat.</p>`)
sb.WriteString(`<p class="card-desc">Small, useful apps that do one thing well. Build apps, set your price, earn 90% of every sale.</p>`)

// Pricing filter
pricing := r.URL.Query().Get("pricing")

// Tag filter
tag := r.URL.Query().Get("tag")
Expand All @@ -408,6 +427,24 @@ func handleList(w http.ResponseWriter, r *http.Request) {
sb.WriteString(`</div>`)
}

// Pricing filter pills
hasPaid := false
hasFree := false
for _, a := range list {
if a.Price > 0 {
hasPaid = true
} else {
hasFree = true
}
}
if hasPaid && hasFree {
sb.WriteString(`<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:16px;">`)
sb.WriteString(fmt.Sprintf(`<a href="/apps" style="padding:4px 12px;border-radius:12px;font-size:12px;text-decoration:none;%s">All</a>`, pillStyle(pricing == "")))
sb.WriteString(fmt.Sprintf(`<a href="/apps?pricing=free" style="padding:4px 12px;border-radius:12px;font-size:12px;text-decoration:none;%s">Free</a>`, pillStyle(pricing == "free")))
sb.WriteString(fmt.Sprintf(`<a href="/apps?pricing=paid" style="padding:4px 12px;border-radius:12px;font-size:12px;text-decoration:none;%s">Paid</a>`, pillStyle(pricing == "paid")))
sb.WriteString(`</div>`)
}

// Filter by tag
if tag != "" {
var filtered []*App
Expand All @@ -419,6 +456,25 @@ func handleList(w http.ResponseWriter, r *http.Request) {
list = filtered
}

// Filter by pricing
if pricing == "free" {
var filtered []*App
for _, a := range list {
if a.Price == 0 {
filtered = append(filtered, a)
}
}
list = filtered
} else if pricing == "paid" {
var filtered []*App
for _, a := range list {
if a.Price > 0 {
filtered = append(filtered, a)
}
}
list = filtered
}

if len(list) == 0 {
sb.WriteString(`<p>No apps yet. <a href="/apps/new">Create the first one</a>.</p>`)
} else {
Expand Down
46 changes: 44 additions & 2 deletions wallet/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ func WalletPage(userID string) string {
sb.WriteString(`<p><a href="/wallet/topup">Add Credits →</a> · <a href="/wallet/transfer">Transfer →</a></p>`)
sb.WriteString(`</div>`)

// App earnings summary
var totalEarnings int
for _, tx := range transactions {
if tx.Operation == OpAppRevenue {
totalEarnings += tx.Amount
}
}
if totalEarnings > 0 {
sb.WriteString(`<div class="card">`)
sb.WriteString(`<h3>App Earnings</h3>`)
sb.WriteString(fmt.Sprintf(`<p>%d credits earned from your apps (recent)</p>`, totalEarnings))
sb.WriteString(`<p class="text-sm text-muted">You keep 90%% of every sale. <a href="/apps">Manage your apps →</a></p>`)
sb.WriteString(`</div>`)
}

if !isAdmin {
// Self-hosting note
sb.WriteString(`<div class="card">`)
Expand Down Expand Up @@ -80,7 +95,19 @@ func WalletPage(userID string) string {

for _, tx := range transactions {
typeLabel := tx.Operation
if tx.Type == TxTopup {
if tx.Operation == OpAppUse {
if appSlug, ok := tx.Metadata["app"].(string); ok {
typeLabel = "App: " + appSlug
} else {
typeLabel = "App usage"
}
} else if tx.Operation == OpAppRevenue {
if appSlug, ok := tx.Metadata["app"].(string); ok {
typeLabel = "Earned: " + appSlug
} else {
typeLabel = "App revenue"
}
} else if tx.Type == TxTopup {
typeLabel = "Deposit"
} else if tx.Type == TxTransfer {
if tx.Amount > 0 {
Expand Down Expand Up @@ -626,8 +653,23 @@ func handlePricing(w http.ResponseWriter, r *http.Request) {
sb.WriteString(fmt.Sprintf(`<tr><td>%s</td><td>%d</td></tr>`, item.Description, item.Cost))
}
sb.WriteString(`</table>`)
sb.WriteString(`</div>`)

// App marketplace pricing
sb.WriteString(`<div class="card">`)
sb.WriteString(`<h3>App Marketplace</h3>`)
sb.WriteString(`<p class="info">Build apps and charge per use. You set the price, you earn the revenue.</p>`)
sb.WriteString(`<table class="stats-table">`)
sb.WriteString(`<tr><td>Price range</td><td>0–1000 credits per use</td></tr>`)
sb.WriteString(`<tr><td>Creator share</td><td>90%</td></tr>`)
sb.WriteString(`<tr><td>Platform fee</td><td>10%</td></tr>`)
sb.WriteString(`<tr><td>Free apps</td><td>No charge</td></tr>`)
sb.WriteString(`</table>`)
sb.WriteString(`<p class="info mt-3"><a href="/apps/build">Build an app →</a></p>`)
sb.WriteString(`</div>`)

sb.WriteString(`<p class="info mt-3">JSON: <code>curl -H "Accept: application/json" /wallet/pricing</code></p>`)
sb.WriteString(`</div></div>`)
sb.WriteString(`</div>`)

html := app.RenderHTMLForRequest("Pricing", "Platform pricing and costs", sb.String(), r)
w.Write([]byte(html))
Expand Down
Loading