diff --git a/main.go b/main.go index 25446f24..901769b0 100644 --- a/main.go +++ b/main.go @@ -129,6 +129,7 @@ func configure(app *aero.Application) *aero.Application { app.Ajax("/shop", shop.Get) app.Ajax("/inventory", inventory.Get) app.Ajax("/charge", charge.Get) + app.Ajax("/shop/history", shop.PurchaseHistory) app.Post("/api/shop/buy/:item/:quantity", shop.BuyItem) // Admin diff --git a/pages/inventory/inventory.pixy b/pages/inventory/inventory.pixy index 24dc3406..d50cec70 100644 --- a/pages/inventory/inventory.pixy +++ b/pages/inventory/inventory.pixy @@ -9,7 +9,8 @@ component Inventory(inventory *arn.Inventory, viewUser *arn.User, user *arn.User .inventory-slot.mountable(draggable="false", data-index=index) else .inventory-slot.mountable(title=slot.Item().Name, draggable="true", data-index=index, data-item-id=slot.ItemID, data-consumable=slot.Item().Consumable) - Icon(slot.Item().Icon) + .item-icon + Icon(slot.Item().Icon) if slot.Quantity > 1 .inventory-slot-quantity= slot.Quantity diff --git a/pages/inventory/inventory.scarlet b/pages/inventory/inventory.scarlet index b4829920..fa8e858a 100644 --- a/pages/inventory/inventory.scarlet +++ b/pages/inventory/inventory.scarlet @@ -16,12 +16,45 @@ inventory-slot-size = 64px display flex align-items 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 margin 0 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 position absolute bottom 0.25rem diff --git a/pages/shop/buyitem.go b/pages/shop/buyitem.go new file mode 100644 index 00000000..669c4ebf --- /dev/null +++ b/pages/shop/buyitem.go @@ -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" +} diff --git a/pages/shop/history.go b/pages/shop/history.go new file mode 100644 index 00000000..d390bb69 --- /dev/null +++ b/pages/shop/history.go @@ -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)) +} diff --git a/pages/shop/history.pixy b/pages/shop/history.pixy new file mode 100644 index 00000000..9bd2b2f3 --- /dev/null +++ b/pages/shop/history.pixy @@ -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) \ No newline at end of file diff --git a/pages/shop/history.scarlet b/pages/shop/history.scarlet new file mode 100644 index 00000000..da4725b5 --- /dev/null +++ b/pages/shop/history.scarlet @@ -0,0 +1,2 @@ +.history-price, .history-date, .history-quantity + text-align right \ No newline at end of file diff --git a/pages/shop/shop.go b/pages/shop/shop.go index 928fe12c..16ec90a1 100644 --- a/pages/shop/shop.go +++ b/pages/shop/shop.go @@ -3,7 +3,6 @@ package shop import ( "net/http" "sort" - "sync" "github.com/animenotifier/arn" @@ -12,8 +11,6 @@ import ( "github.com/animenotifier/notify.moe/utils" ) -var itemBuyMutex sync.Mutex - // Get shop page. func Get(ctx *aero.Context) string { user := utils.GetUser(ctx) @@ -34,56 +31,3 @@ func Get(ctx *aero.Context) string { 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" -} diff --git a/pages/shop/shop.pixy b/pages/shop/shop.pixy index 8556163e..81239903 100644 --- a/pages/shop/shop.pixy +++ b/pages/shop/shop.pixy @@ -11,12 +11,14 @@ component ShopTabs(user *arn.User) .tabs Tab("Shop", "shopping-cart", "/shop") Tab("Inventory", "briefcase", "/inventory") + Tab("History", "history", "/shop/history") Tab(strconv.Itoa(user.Balance), "diamond", "/charge") component ShopItem(item *arn.Item) - .widget.shop-item.mountable + .widget.shop-item.mountable(data-item-id=item.ID) h3.widget-title.shop-item-name - Icon(item.Icon) + .item-icon + Icon(item.Icon) span= item.Name //- span.shop-item-duration= " " + duration .shop-item-description!= aero.Markdown(item.Description) diff --git a/pages/shop/shop.scarlet b/pages/shop/shop.scarlet index b2374edc..a99e56e2 100644 --- a/pages/shop/shop.scarlet +++ b/pages/shop/shop.scarlet @@ -1,11 +1,33 @@ +item-color-pro-account = hsl(0, 100%, 71%) +item-color-anime-support-ticket = hsl(217, 64%, 50%) + .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 // ... diff --git a/patches/add-shop-items/add-shop-items.go b/patches/add-shop-items/add-shop-items.go index 1e08a94e..ba9f4784 100644 --- a/patches/add-shop-items/add-shop-items.go +++ b/patches/add-shop-items/add-shop-items.go @@ -85,7 +85,9 @@ Includes: Price: 100, 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 -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", Rarity: arn.ItemRarityRare, Order: 5, diff --git a/patches/delete-pro/delete-pro.go b/patches/delete-pro/delete-pro.go new file mode 100644 index 00000000..05b7b975 --- /dev/null +++ b/patches/delete-pro/delete-pro.go @@ -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.") +} diff --git a/patches/add-inventories/add-inventories.go b/patches/reset-inventories/reset-inventories.go similarity index 59% rename from patches/add-inventories/add-inventories.go rename to patches/reset-inventories/reset-inventories.go index 28966b6d..6ab25c83 100644 --- a/patches/add-inventories/add-inventories.go +++ b/patches/reset-inventories/reset-inventories.go @@ -1,14 +1,29 @@ package main import ( + "flag" "fmt" "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() { - 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 allUsers, err := arn.StreamUsers() @@ -16,20 +31,9 @@ func main() { // Iterate over the stream for user := range allUsers { - exists, err := arn.DB.Exists("Inventory", user.ID) - - if err != nil || exists { - continue - } - fmt.Println(user.Nick) 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) if err != nil { diff --git a/tests.go b/tests.go index 0529e2b5..75b135a6 100644 --- a/tests.go +++ b/tests.go @@ -247,6 +247,7 @@ var routeTests = map[string][]string{ "/user": nil, "/settings": nil, "/shop": nil, + "/shop/history": nil, "/charge": nil, "/inventory": nil, "/extension/embed": nil,