Improved shop

This commit is contained in:
Eduard Urbach 2017-10-06 12:44:55 +02:00
parent 7e5acf5a97
commit bb39234f2d
14 changed files with 253 additions and 77 deletions

View File

@ -129,6 +129,7 @@ func configure(app *aero.Application) *aero.Application {
app.Ajax("/shop", shop.Get) app.Ajax("/shop", shop.Get)
app.Ajax("/inventory", inventory.Get) app.Ajax("/inventory", inventory.Get)
app.Ajax("/charge", charge.Get) app.Ajax("/charge", charge.Get)
app.Ajax("/shop/history", shop.PurchaseHistory)
app.Post("/api/shop/buy/:item/:quantity", shop.BuyItem) app.Post("/api/shop/buy/:item/:quantity", shop.BuyItem)
// Admin // Admin

View File

@ -9,6 +9,7 @@ component Inventory(inventory *arn.Inventory, viewUser *arn.User, user *arn.User
.inventory-slot.mountable(draggable="false", data-index=index) .inventory-slot.mountable(draggable="false", data-index=index)
else else
.inventory-slot.mountable(title=slot.Item().Name, draggable="true", data-index=index, data-item-id=slot.ItemID, data-consumable=slot.Item().Consumable) .inventory-slot.mountable(title=slot.Item().Name, draggable="true", data-index=index, data-item-id=slot.ItemID, data-consumable=slot.Item().Consumable)
.item-icon
Icon(slot.Item().Icon) Icon(slot.Item().Icon)
if slot.Quantity > 1 if slot.Quantity > 1
.inventory-slot-quantity= slot.Quantity .inventory-slot-quantity= slot.Quantity

View File

@ -16,12 +16,45 @@ inventory-slot-size = 64px
display flex display flex
align-items center align-items center
justify-content center justify-content center
font-size 2rem font-size 2.5rem
[draggable="true"]
:hover
cursor pointer
.item-icon
animation hover-item 1s infinite ease-in-out
.icon .icon
margin 0 margin 0
pointer-events none pointer-events none
// [data-item-id="pro-account-3"]
// .item-icon
// opacity 0.7
// [data-item-id="pro-account-6"]
// .item-icon
// opacity 0.8
// [data-item-id="pro-account-12"]
// .item-icon
// opacity 0.9
// [data-item-id="pro-account-24"]
// .item-icon
// opacity 1.0
animation hover-item
0%
transform rotateZ(0)
20%
transform rotateZ(5deg)
80%
transform rotateZ(-5deg)
100%
transform rotateZ(0)
.inventory-slot-quantity .inventory-slot-quantity
position absolute position absolute
bottom 0.25rem bottom 0.25rem

72
pages/shop/buyitem.go Normal file
View File

@ -0,0 +1,72 @@
package shop
import (
"net/http"
"sync"
"github.com/aerogo/aero"
"github.com/animenotifier/arn"
"github.com/animenotifier/notify.moe/utils"
)
var itemBuyMutex sync.Mutex
// BuyItem ...
func BuyItem(ctx *aero.Context) string {
// Lock via mutex to prevent race conditions
itemBuyMutex.Lock()
defer itemBuyMutex.Unlock()
// Logged in user
user := utils.GetUser(ctx)
if user == nil {
return ctx.Error(http.StatusUnauthorized, "Not logged in", nil)
}
// Item ID and quantity
itemID := ctx.Get("item")
quantity, err := ctx.GetInt("quantity")
if err != nil || quantity == 0 {
return ctx.Error(http.StatusBadRequest, "Invalid item quantity", err)
}
item, err := arn.GetItem(itemID)
if err != nil {
return ctx.Error(http.StatusInternalServerError, "Error fetching item data", err)
}
// Calculate total price and subtract balance
totalPrice := int(item.Price) * quantity
if user.Balance < totalPrice {
return ctx.Error(http.StatusBadRequest, "Not enough gems", nil)
}
user.Balance -= totalPrice
err = user.Save()
if err != nil {
return ctx.Error(http.StatusInternalServerError, "Error saving user data", err)
}
// Add item to user inventory
inventory := user.Inventory()
inventory.AddItem(itemID, uint(quantity))
err = inventory.Save()
if err != nil {
return ctx.Error(http.StatusInternalServerError, "Error saving inventory", err)
}
// Save purchase
err = arn.NewPurchase(user.ID, itemID, quantity, int(item.Price), "gem").Save()
if err != nil {
return ctx.Error(http.StatusInternalServerError, "Error saving purchase", err)
}
return "ok"
}

32
pages/shop/history.go Normal file
View File

@ -0,0 +1,32 @@
package shop
import (
"net/http"
"sort"
"github.com/aerogo/aero"
"github.com/animenotifier/arn"
"github.com/animenotifier/notify.moe/components"
"github.com/animenotifier/notify.moe/utils"
)
// PurchaseHistory ...
func PurchaseHistory(ctx *aero.Context) string {
user := utils.GetUser(ctx)
if user == nil {
return ctx.Error(http.StatusUnauthorized, "Not logged in", nil)
}
purchases, err := arn.AllPurchases()
if err != nil {
return ctx.Error(http.StatusInternalServerError, "Error fetching shop item data", err)
}
sort.Slice(purchases, func(i, j int) bool {
return purchases[i].Date > purchases[j].Date
})
return ctx.HTML(components.PurchaseHistory(purchases, user))
}

22
pages/shop/history.pixy Normal file
View File

@ -0,0 +1,22 @@
component PurchaseHistory(purchases []*arn.Purchase, user *arn.User)
ShopTabs(user)
h1.page-title Purchase History
table
thead
tr.mountable
th Icon
th Item
th.history-quantity Quantity
th.history-price Price
th.history-date Date
tbody
each purchase in purchases
tr.shop-item.mountable(data-item-id=purchase.ItemID)
td.item-icon
Icon(purchase.Item().Icon)
td= purchase.Item().Name
td.history-quantity= purchase.Quantity
td.history-price= purchase.Price
td.history-date.utc-date(data-date=purchase.Date)

View File

@ -0,0 +1,2 @@
.history-price, .history-date, .history-quantity
text-align right

View File

@ -3,7 +3,6 @@ package shop
import ( import (
"net/http" "net/http"
"sort" "sort"
"sync"
"github.com/animenotifier/arn" "github.com/animenotifier/arn"
@ -12,8 +11,6 @@ import (
"github.com/animenotifier/notify.moe/utils" "github.com/animenotifier/notify.moe/utils"
) )
var itemBuyMutex sync.Mutex
// Get shop page. // Get shop page.
func Get(ctx *aero.Context) string { func Get(ctx *aero.Context) string {
user := utils.GetUser(ctx) user := utils.GetUser(ctx)
@ -34,56 +31,3 @@ func Get(ctx *aero.Context) string {
return ctx.HTML(components.Shop(user, items)) return ctx.HTML(components.Shop(user, items))
} }
// BuyItem ...
func BuyItem(ctx *aero.Context) string {
// Lock via mutex to prevent race conditions
itemBuyMutex.Lock()
defer itemBuyMutex.Unlock()
// Logged in user
user := utils.GetUser(ctx)
if user == nil {
return ctx.Error(http.StatusUnauthorized, "Not logged in", nil)
}
// Item ID and quantity
itemID := ctx.Get("item")
quantity, err := ctx.GetInt("quantity")
if err != nil || quantity == 0 {
return ctx.Error(http.StatusBadRequest, "Invalid item quantity", err)
}
item, err := arn.GetItem(itemID)
if err != nil {
return ctx.Error(http.StatusInternalServerError, "Error fetching item data", err)
}
// Calculate total price and subtract balance
totalPrice := int(item.Price) * quantity
if user.Balance < totalPrice {
return ctx.Error(http.StatusBadRequest, "Not enough gems", nil)
}
user.Balance -= totalPrice
err = user.Save()
if err != nil {
return ctx.Error(http.StatusInternalServerError, "Error saving user data", err)
}
// Add item to user inventory
inventory := user.Inventory()
inventory.AddItem(itemID, uint(quantity))
err = inventory.Save()
if err != nil {
return ctx.Error(http.StatusInternalServerError, "Error saving inventory", err)
}
return "ok"
}

View File

@ -11,11 +11,13 @@ component ShopTabs(user *arn.User)
.tabs .tabs
Tab("Shop", "shopping-cart", "/shop") Tab("Shop", "shopping-cart", "/shop")
Tab("Inventory", "briefcase", "/inventory") Tab("Inventory", "briefcase", "/inventory")
Tab("History", "history", "/shop/history")
Tab(strconv.Itoa(user.Balance), "diamond", "/charge") Tab(strconv.Itoa(user.Balance), "diamond", "/charge")
component ShopItem(item *arn.Item) component ShopItem(item *arn.Item)
.widget.shop-item.mountable .widget.shop-item.mountable(data-item-id=item.ID)
h3.widget-title.shop-item-name h3.widget-title.shop-item-name
.item-icon
Icon(item.Icon) Icon(item.Icon)
span= item.Name span= item.Name
//- span.shop-item-duration= " " + duration //- span.shop-item-duration= " " + duration

View File

@ -1,11 +1,33 @@
item-color-pro-account = hsl(0, 100%, 71%)
item-color-anime-support-ticket = hsl(217, 64%, 50%)
.shop-items .shop-items
// ... // ...
.shop-item .item-icon
// ... display inline-block
.shop-item-name // Colors
// ... .shop-item, .inventory-slot
[data-item-id="pro-account-3"]
.item-icon
color item-color-pro-account
[data-item-id="pro-account-6"]
.item-icon
color item-color-pro-account
[data-item-id="pro-account-12"]
.item-icon
color item-color-pro-account
[data-item-id="pro-account-24"]
.item-icon
color item-color-pro-account
[data-item-id="anime-support-ticket"]
.item-icon
color item-color-anime-support-ticket
.shop-item-price .shop-item-price
// ... // ...

View File

@ -85,7 +85,9 @@ Includes:
Price: 100, Price: 100,
Description: `Support the makers of your favourite anime by using an anime support ticket. Description: `Support the makers of your favourite anime by using an anime support ticket.
Anime Notifier uses 10% of the money to handle the transaction fees while the remaining 90% go directly Anime Notifier uses 10% of the money to handle the transaction fees while the remaining 90% go directly
to the studios involved in the creation of your favourite anime.`, to the studios involved in the creation of your favourite anime.
*This feature is work in progress.*`,
Icon: "ticket", Icon: "ticket",
Rarity: arn.ItemRarityRare, Rarity: arn.ItemRarityRare,
Order: 5, Order: 5,

View File

@ -0,0 +1,38 @@
package main
import (
"flag"
"github.com/animenotifier/arn"
"github.com/fatih/color"
)
// Shell parameters
var confirmed bool
// Shell flags
func init() {
flag.BoolVar(&confirmed, "confirm", false, "Confirm that you really want to execute this.")
flag.Parse()
}
func main() {
if !confirmed {
color.Green("Please run this command with -confirm option if you really want to delete all pro subscriptions.")
return
}
color.Yellow("Deleting all pro subscriptions")
// Get a stream of all users
allUsers, err := arn.StreamUsers()
arn.PanicOnError(err)
// Iterate over the stream
for user := range allUsers {
user.Balance = 0
arn.PanicOnError(user.Save())
}
color.Green("Finished.")
}

View File

@ -1,14 +1,29 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"github.com/animenotifier/arn" "github.com/animenotifier/arn"
"github.com/fatih/color" "github.com/fatih/color"
) )
// Shell parameters
var confirmed bool
// Shell flags
func init() {
flag.BoolVar(&confirmed, "confirm", false, "Confirm that you really want to execute this.")
flag.Parse()
}
func main() { func main() {
color.Yellow("Adding inventories to users who don't have one") if !confirmed {
color.Green("Please run this command with -confirm option.")
return
}
color.Yellow("Resetting all inventories")
// Get a stream of all users // Get a stream of all users
allUsers, err := arn.StreamUsers() allUsers, err := arn.StreamUsers()
@ -16,20 +31,9 @@ func main() {
// Iterate over the stream // Iterate over the stream
for user := range allUsers { for user := range allUsers {
exists, err := arn.DB.Exists("Inventory", user.ID)
if err != nil || exists {
continue
}
fmt.Println(user.Nick) fmt.Println(user.Nick)
inventory := arn.NewInventory(user.ID) inventory := arn.NewInventory(user.ID)
// // TEST
// inventory.AddItem("anime-support-ticket", 50)
// inventory.AddItem("pro-account-24", 30)
err = arn.DB.Set("Inventory", inventory.UserID, inventory) err = arn.DB.Set("Inventory", inventory.UserID, inventory)
if err != nil { if err != nil {

View File

@ -247,6 +247,7 @@ var routeTests = map[string][]string{
"/user": nil, "/user": nil,
"/settings": nil, "/settings": nil,
"/shop": nil, "/shop": nil,
"/shop/history": nil,
"/charge": nil, "/charge": nil,
"/inventory": nil, "/inventory": nil,
"/extension/embed": nil, "/extension/embed": nil,