Compare commits
5 Commits
d9bd3aa719
...
803535daba
| Author | SHA1 | Date | |
|---|---|---|---|
| 803535daba | |||
| cdfd77bc21 | |||
| ec4a3b047f | |||
| c7b10d24be | |||
| 016b54f8dd |
@@ -4,10 +4,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"example.com/gin/test/models"
|
"example.com/gin/test/models"
|
||||||
|
//"example.com/gin/test/services"
|
||||||
"example.com/gin/test/repositories"
|
"example.com/gin/test/repositories"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,6 +26,12 @@ type ShopItemController interface {
|
|||||||
ShopItemView(*gin.Context)
|
ShopItemView(*gin.Context)
|
||||||
AddItemView(*gin.Context)
|
AddItemView(*gin.Context)
|
||||||
AddItemHandler(*gin.Context)
|
AddItemHandler(*gin.Context)
|
||||||
|
CreateTag(*gin.Context)
|
||||||
|
GetAllTags(*gin.Context)
|
||||||
|
EditItemView(*gin.Context)
|
||||||
|
EditItemHandler(*gin.Context)
|
||||||
|
DeleteItemView(*gin.Context)
|
||||||
|
DeleteItemHandler(*gin.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
type shopItemController struct {}
|
type shopItemController struct {}
|
||||||
@@ -54,26 +62,70 @@ func (rc *shopItemController) GetById(c *gin.Context) {
|
|||||||
ReplyOK(c, shopItem)
|
ReplyOK(c, shopItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *shopItemController) Create(c *gin.Context) {
|
func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.ShopItem, error) {
|
||||||
shopItem, err := models.NewShopItem(c)
|
name := ctx.PostForm("name")
|
||||||
|
abstract := ctx.PostForm("abstract")
|
||||||
|
description := ctx.PostForm("description")
|
||||||
|
priceStr := ctx.PostForm("price")
|
||||||
|
tagIds := ctx.PostFormArray("tags[]")
|
||||||
|
image, err := ctx.FormFile("image")
|
||||||
|
dst := "static/img/zine.jpg"
|
||||||
|
|
||||||
if err != nil {
|
if err == nil {
|
||||||
ReplyError(c, err)
|
dst = filepath.Join("static/uploads", image.Filename)
|
||||||
|
if err := ctx.SaveUploadedFile(image, dst); err != nil {
|
||||||
|
return models.ShopItem{}, fmt.Errorf("Could not save image")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = repositories.ShopItems.Create(shopItem)
|
if name == "" || description == "" {
|
||||||
|
return models.ShopItem{}, fmt.Errorf("Name or description empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the price string to float64
|
||||||
|
price, err := strconv.ParseFloat(priceStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
return models.ShopItem{}, fmt.Errorf("Could not parse price")
|
||||||
|
}
|
||||||
|
|
||||||
|
shopItem := models.ShopItem{
|
||||||
|
Name: name,
|
||||||
|
Abstract: abstract,
|
||||||
|
Description: description,
|
||||||
|
Price: price,
|
||||||
|
IsPublic: true,
|
||||||
|
Image: dst,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tagId := range tagIds {
|
||||||
|
tag, err := repositories.Tags.GetById(tagId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return models.ShopItem{}, fmt.Errorf("Could not get tag by id")
|
||||||
|
}
|
||||||
|
|
||||||
|
shopItem.Tags = append(shopItem.Tags, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return shopItem, nil
|
||||||
|
//return services.ShopItems.NewShopItem(name, abstract, description, price, tagIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (rc *shopItemController) Create(c *gin.Context) {
|
||||||
|
shopItem, err := rc.NewShopItemFromForm(c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err))
|
ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//userID := user.(models.User).ID
|
_, err = repositories.ShopItems.Create(shopItem)
|
||||||
//rc.DB.Model(&models.shopItem{}).Where("id = ?"), room.ID).Association("Admins").Append(&models.User{ID: userID})
|
|
||||||
|
|
||||||
//if result.Error != nil {
|
if err != nil {
|
||||||
// ReplyError(c, fmt.Errorf("shopItem creation failed: %s", result.Error))
|
ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err))
|
||||||
// return
|
return
|
||||||
//}
|
}
|
||||||
|
|
||||||
ReplyOK(c, "shopItem was created")
|
ReplyOK(c, "shopItem was created")
|
||||||
}
|
}
|
||||||
@@ -87,7 +139,7 @@ func (rc *shopItemController) Update(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
shopItem, err := models.NewShopItem(c)
|
shopItem, err := rc.NewShopItemFromForm(c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ReplyError(c, err)
|
ReplyError(c, err)
|
||||||
@@ -119,8 +171,20 @@ func (rc *shopItemController) Delete(c *gin.Context) {
|
|||||||
func (rc *shopItemController) ShopItemView(c *gin.Context) {
|
func (rc *shopItemController) ShopItemView(c *gin.Context) {
|
||||||
shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "shopitem.html", gin.H{ "data": gin.H{ "error": err } })
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: get tags by item
|
||||||
|
tags, err := repositories.Tags.GetAll()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "shopitem.html", gin.H{ "data": gin.H{ "error": err } })
|
||||||
|
}
|
||||||
|
|
||||||
data := CreateSessionData(c, gin.H{
|
data := CreateSessionData(c, gin.H{
|
||||||
"shopItem": shopItem,
|
"shopItem": shopItem,
|
||||||
|
"tags": tags,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -130,11 +194,17 @@ func (rc *shopItemController) ShopItemView(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, "shopitem.html", data)
|
c.HTML(http.StatusOK, "shopitem.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (rc *shopItemController) AddItemView(c *gin.Context) {
|
func (rc *shopItemController) AddItemView(c *gin.Context) {
|
||||||
|
tags, err := repositories.Tags.GetAll()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "additem.html", gin.H{ "error": err })
|
||||||
|
}
|
||||||
|
|
||||||
data := CreateSessionData(c, gin.H{
|
data := CreateSessionData(c, gin.H{
|
||||||
"error": "",
|
"error": "",
|
||||||
"success": "",
|
"success": "",
|
||||||
|
"tags": tags,
|
||||||
})
|
})
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "additem.html", data)
|
c.HTML(http.StatusOK, "additem.html", data)
|
||||||
@@ -142,10 +212,20 @@ func (rc *shopItemController) AddItemView(c *gin.Context) {
|
|||||||
|
|
||||||
|
|
||||||
func (rc *shopItemController) AddItemHandler(c *gin.Context) {
|
func (rc *shopItemController) AddItemHandler(c *gin.Context) {
|
||||||
shopItem, err := models.NewShopItem(c)
|
shopItem, err := rc.NewShopItemFromForm(c)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ReplyError(c, err)
|
fmt.Println(err)
|
||||||
|
c.HTML(http.StatusBadRequest, "additem.html", gin.H{ "error": err })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err := repositories.Tags.GetAll()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
c.HTML(http.StatusBadRequest, "additem.html", gin.H{ "error": err })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = repositories.ShopItems.Create(shopItem)
|
_, err = repositories.ShopItems.Create(shopItem)
|
||||||
@@ -153,6 +233,7 @@ func (rc *shopItemController) AddItemHandler(c *gin.Context) {
|
|||||||
data := CreateSessionData(c, gin.H{
|
data := CreateSessionData(c, gin.H{
|
||||||
"error": err,
|
"error": err,
|
||||||
"success": "",
|
"success": "",
|
||||||
|
"tags": tags,
|
||||||
})
|
})
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "additem.html", data)
|
c.HTML(http.StatusOK, "additem.html", data)
|
||||||
@@ -162,11 +243,165 @@ func (rc *shopItemController) AddItemHandler(c *gin.Context) {
|
|||||||
data := CreateSessionData(c, gin.H{
|
data := CreateSessionData(c, gin.H{
|
||||||
"error": "",
|
"error": "",
|
||||||
"success": fmt.Sprintf("Item '%s' Registered", shopItem.Name),
|
"success": fmt.Sprintf("Item '%s' Registered", shopItem.Name),
|
||||||
|
"tags": tags,
|
||||||
})
|
})
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "additem.html", data)
|
c.HTML(http.StatusOK, "additem.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (rc *shopItemController) EditItemView(c *gin.Context) {
|
||||||
|
shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
||||||
|
tags, err := repositories.Tags.GetAll()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{ "error": err })
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(shopItem)
|
||||||
|
|
||||||
|
data := CreateSessionData(c, gin.H{
|
||||||
|
"error": "",
|
||||||
|
"success": "",
|
||||||
|
"shopItem": shopItem,
|
||||||
|
"tags": tags,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "edititem.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (rc *shopItemController) EditItemHandler(c *gin.Context) {
|
||||||
|
shopItem, err := rc.NewShopItemFromForm(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{ "error": err })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newShopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{ "error": err })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newShopItem.Name = shopItem.Name
|
||||||
|
newShopItem.Abstract = shopItem.Abstract
|
||||||
|
newShopItem.Description = shopItem.Description
|
||||||
|
newShopItem.Price = shopItem.Price
|
||||||
|
newShopItem.IsPublic = shopItem.IsPublic
|
||||||
|
newShopItem.Tags = shopItem.Tags
|
||||||
|
|
||||||
|
tags, err := repositories.Tags.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "edititem.html", gin.H{ "error": err })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = repositories.ShopItems.Update(newShopItem)
|
||||||
|
if err != nil {
|
||||||
|
data := CreateSessionData(c, gin.H{
|
||||||
|
"error": err,
|
||||||
|
"success": "",
|
||||||
|
"tags": tags,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "edititem.html", data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := CreateSessionData(c, gin.H{
|
||||||
|
"error": "",
|
||||||
|
"success": fmt.Sprintf("Item '%s' Updated", newShopItem.Name),
|
||||||
|
"tags": tags,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "edititem.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *shopItemController) DeleteItemView(c *gin.Context) {
|
||||||
|
shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
|
||||||
|
tags, err := repositories.Tags.GetAll()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "deleteitem.html", gin.H{ "error": err })
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(shopItem)
|
||||||
|
|
||||||
|
data := CreateSessionData(c, gin.H{
|
||||||
|
"error": "",
|
||||||
|
"success": "",
|
||||||
|
"shopItem": shopItem,
|
||||||
|
"tags": tags,
|
||||||
|
})
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "deleteitem.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (rc *shopItemController) DeleteItemHandler(c *gin.Context) {
|
||||||
|
err := repositories.ShopItems.DeleteById(c.Param("id"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
data := CreateSessionData(c, gin.H{
|
||||||
|
"error": err,
|
||||||
|
"success": "",
|
||||||
|
})
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "deleteitem.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
shopItems, _ := repositories.ShopItems.GetAll()
|
||||||
|
fmt.Println(len(shopItems))
|
||||||
|
|
||||||
|
data := CreateSessionData(c, gin.H{
|
||||||
|
"title": "shopItem Page",
|
||||||
|
"shopItems": shopItems,
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println(data)
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "index.html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *shopItemController) CreateTag(c *gin.Context) {
|
||||||
|
tag, err := models.NewTagByJson(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ReplyError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = repositories.Tags.Create(tag)
|
||||||
|
if err != nil {
|
||||||
|
ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//userID := user.(models.User).ID
|
||||||
|
//rc.DB.Model(&models.shopItem{}).Where("id = ?"), room.ID).Association("Admins").Append(&models.User{ID: userID})
|
||||||
|
|
||||||
|
//if result.Error != nil {
|
||||||
|
// ReplyError(c, fmt.Errorf("shopItem creation failed: %s", result.Error))
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
ReplyOK(c, fmt.Sprintf("tag '%s' was created", tag.Name))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *shopItemController) GetAllTags(c *gin.Context) {
|
||||||
|
tags, err := repositories.Tags.GetAll()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ReplyError(c, fmt.Errorf("Could not query Tags"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, tags)
|
||||||
|
}
|
||||||
|
|
||||||
func ReplyError(ctx *gin.Context, err error) {
|
func ReplyError(ctx *gin.Context, err error) {
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{ "error": err.Error() })
|
ctx.JSON(http.StatusBadRequest, gin.H{ "error": err.Error() })
|
||||||
}
|
}
|
||||||
|
|||||||
6
main.go
6
main.go
@@ -57,6 +57,8 @@ func main() {
|
|||||||
apiRoutes := server.Group("/api")
|
apiRoutes := server.Group("/api")
|
||||||
//apiRoutes.Use(middlewares.BasicAuth())
|
//apiRoutes.Use(middlewares.BasicAuth())
|
||||||
{
|
{
|
||||||
|
apiRoutes.POST("/tags", authValidator.RequireAuth, shopItemController.CreateTag)
|
||||||
|
apiRoutes.GET("/tags", authValidator.OptionalAuth, shopItemController.GetAllTags)
|
||||||
apiRoutes.POST("/shopitems", authValidator.RequireAuth, shopItemController.Create)
|
apiRoutes.POST("/shopitems", authValidator.RequireAuth, shopItemController.Create)
|
||||||
apiRoutes.GET("/shopitems", authValidator.OptionalAuth, shopItemController.GetAll)
|
apiRoutes.GET("/shopitems", authValidator.OptionalAuth, shopItemController.GetAll)
|
||||||
apiRoutes.GET("/shopitems/:id", authValidator.OptionalAuth, shopItemController.GetById)
|
apiRoutes.GET("/shopitems/:id", authValidator.OptionalAuth, shopItemController.GetById)
|
||||||
@@ -75,6 +77,10 @@ func main() {
|
|||||||
{
|
{
|
||||||
viewRoutes.GET("/", userController.MainView)
|
viewRoutes.GET("/", userController.MainView)
|
||||||
viewRoutes.GET("/shopitems/:id", shopItemController.ShopItemView)
|
viewRoutes.GET("/shopitems/:id", shopItemController.ShopItemView)
|
||||||
|
viewRoutes.GET("/shopitems/:id/edit", authValidator.RequireAuth, shopItemController.EditItemView)
|
||||||
|
viewRoutes.GET("/shopitems/:id/delete", authValidator.RequireAuth, shopItemController.DeleteItemView)
|
||||||
|
viewRoutes.POST("/shopitems/:id/edit", authValidator.RequireAuth, shopItemController.EditItemHandler)
|
||||||
|
viewRoutes.POST("/shopitems/:id/delete", authValidator.RequireAuth, shopItemController.DeleteItemHandler)
|
||||||
//write middleware that redirects to homescreen on register/login/reset for logged in users
|
//write middleware that redirects to homescreen on register/login/reset for logged in users
|
||||||
viewRoutes.GET("/login", userController.LoginView)
|
viewRoutes.GET("/login", userController.LoginView)
|
||||||
viewRoutes.GET("/logout", userController.Logout)
|
viewRoutes.GET("/logout", userController.Logout)
|
||||||
|
|||||||
@@ -1,42 +1,17 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShopItem struct {
|
type ShopItem struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Name string `json:"name" binding:"required" gorm:"unique;not null"`
|
Name string `json:"name" binding:"required" gorm:"unique;not null"`
|
||||||
|
Abstract string `json:"Abstract" binding:"required"`
|
||||||
Description string `json:"description" binding:"required"`
|
Description string `json:"description" binding:"required"`
|
||||||
Price float64 `json:"price" binding:"required"`
|
Price float64 `json:"price" binding:"required"`
|
||||||
IsPublic bool `json:"isPublic" gorm:"default:true"`
|
IsPublic bool `json:"isPublic" gorm:"default:true"`
|
||||||
//Images gin.multipart.FileHeader ``
|
Tags []Tag `gorm:"many2many:item_tags;"`
|
||||||
|
Image string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShopItem(ctx *gin.Context) (ShopItem, error) {
|
|
||||||
name := ctx.PostForm("name")
|
|
||||||
description := ctx.PostForm("description")
|
|
||||||
priceStr := ctx.PostForm("price")
|
|
||||||
|
|
||||||
// Convert the price string to float64
|
|
||||||
price, err := strconv.ParseFloat(priceStr, 64)
|
|
||||||
if err != nil {
|
|
||||||
return ShopItem{}, fmt.Errorf("Could not parse price")
|
|
||||||
}
|
|
||||||
|
|
||||||
shopItem := ShopItem{
|
|
||||||
Name: name,
|
|
||||||
Description: description,
|
|
||||||
Price: price,
|
|
||||||
IsPublic: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == "" || description == "" {
|
|
||||||
return ShopItem{}, fmt.Errorf("Name or description empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return shopItem, nil
|
|
||||||
}
|
|
||||||
|
|||||||
40
models/tag.go
Normal file
40
models/tag.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tag struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string `json:"name" binding:"required" gorm:"unique;not null"`
|
||||||
|
ShopItems []ShopItem `gorm:"many2many:item_tags;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTag(ctx *gin.Context) (Tag, error) {
|
||||||
|
name := ctx.PostForm("name")
|
||||||
|
|
||||||
|
// Convert the price string to float64
|
||||||
|
tag := Tag{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return Tag{}, fmt.Errorf("Name or description empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func NewTagByJson(ctx *gin.Context) (Tag, error) {
|
||||||
|
var tag Tag
|
||||||
|
err := ctx.ShouldBindJSON(&tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return Tag{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import(
|
|||||||
var(
|
var(
|
||||||
ShopItems ShopItemRepository
|
ShopItems ShopItemRepository
|
||||||
Users UserRepository
|
Users UserRepository
|
||||||
|
Tags TagRepository
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitRepositories() {
|
func InitRepositories() {
|
||||||
@@ -19,11 +20,12 @@ func InitRepositories() {
|
|||||||
panic("failed to connect to database")
|
panic("failed to connect to database")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.AutoMigrate(&models.ShopItem{}, &models.User{}, &models.Booking{})
|
err = db.AutoMigrate(&models.ShopItem{}, &models.User{}, &models.Tag{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to migrate database")
|
panic("failed to migrate database")
|
||||||
}
|
}
|
||||||
|
|
||||||
ShopItems = NewGORMShopItemRepository(db)
|
ShopItems = NewGORMShopItemRepository(db)
|
||||||
Users = NewGORMUserRepository(db)
|
Users = NewGORMUserRepository(db)
|
||||||
|
Tags = NewGORMTagRepository(db)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type ShopItemRepository interface {
|
|||||||
GetAll() ([]models.ShopItem, error)
|
GetAll() ([]models.ShopItem, error)
|
||||||
GetAllPublic() ([]models.ShopItem, error)
|
GetAllPublic() ([]models.ShopItem, error)
|
||||||
GetById(string) (models.ShopItem, error)
|
GetById(string) (models.ShopItem, error)
|
||||||
|
//GetByTagId(string) ([]models.ShopItem, error)
|
||||||
Update(models.ShopItem) (models.ShopItem, error)
|
Update(models.ShopItem) (models.ShopItem, error)
|
||||||
DeleteById(string) error
|
DeleteById(string) error
|
||||||
}
|
}
|
||||||
@@ -37,14 +38,14 @@ func (r *GORMShopItemRepository) Create(shopItem models.ShopItem) (models.ShopIt
|
|||||||
|
|
||||||
func (r *GORMShopItemRepository) GetAll() ([]models.ShopItem, error) {
|
func (r *GORMShopItemRepository) GetAll() ([]models.ShopItem, error) {
|
||||||
var shopItems []models.ShopItem
|
var shopItems []models.ShopItem
|
||||||
result := r.DB.Find(&shopItems)
|
result := r.DB.Preload("Tags").Find(&shopItems)
|
||||||
|
|
||||||
return shopItems, result.Error
|
return shopItems, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GORMShopItemRepository) GetAllPublic() ([]models.ShopItem, error) {
|
func (r *GORMShopItemRepository) GetAllPublic() ([]models.ShopItem, error) {
|
||||||
var shopItems []models.ShopItem
|
var shopItems []models.ShopItem
|
||||||
result := r.DB.Where("is_public = 1").Find(&shopItems)
|
result := r.DB.Preload("Tags").Where("is_public = 1").Find(&shopItems)
|
||||||
|
|
||||||
return shopItems, result.Error
|
return shopItems, result.Error
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ func (r *GORMShopItemRepository) GetById(id string) (models.ShopItem, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var shopItem models.ShopItem
|
var shopItem models.ShopItem
|
||||||
result := r.DB.First(&shopItem, uint(shopItemId))
|
result := r.DB.Preload("Tags").First(&shopItem, uint(shopItemId))
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return models.ShopItem{}, result.Error
|
return models.ShopItem{}, result.Error
|
||||||
|
|||||||
83
repositories/tagRepository.go
Normal file
83
repositories/tagRepository.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import(
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"example.com/gin/test/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TagRepository interface {
|
||||||
|
Create(models.Tag) (models.Tag, error)
|
||||||
|
GetAll() ([]models.Tag, error)
|
||||||
|
GetById(string) (models.Tag, error)
|
||||||
|
//GetByShopItemId(string) (models.Tag, error)
|
||||||
|
Update(models.Tag) (models.Tag, error)
|
||||||
|
DeleteById(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type GORMTagRepository struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGORMTagRepository(db *gorm.DB) TagRepository {
|
||||||
|
return &GORMTagRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *GORMTagRepository) Create(tag models.Tag) (models.Tag, error) {
|
||||||
|
result := t.DB.Create(&tag)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return models.Tag{}, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (t *GORMTagRepository) GetAll() ([]models.Tag, error) {
|
||||||
|
var tags []models.Tag
|
||||||
|
result := t.DB.Find(&tags)
|
||||||
|
|
||||||
|
return tags, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *GORMTagRepository) GetById(id string) (models.Tag, error) {
|
||||||
|
tagId, err := strconv.Atoi(id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return models.Tag{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var tag models.Tag
|
||||||
|
result := t.DB.First(&tag, uint(tagId))
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return models.Tag{}, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *GORMTagRepository) Update(tag models.Tag) (models.Tag, error) {
|
||||||
|
result := t.DB.Save(&tag)
|
||||||
|
if result.Error != nil {
|
||||||
|
return models.Tag{}, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *GORMTagRepository) DeleteById(id string) error {
|
||||||
|
tagId, err := strconv.Atoi(id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := t.DB.Delete(&models.Tag{}, tagId)
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import(
|
import(
|
||||||
"golang.org/x/crypto/bcrypt"
|
"fmt"
|
||||||
|
|
||||||
"example.com/gin/test/models"
|
"example.com/gin/test/models"
|
||||||
"example.com/gin/test/repositories"
|
"example.com/gin/test/repositories"
|
||||||
@@ -13,20 +13,24 @@ var(
|
|||||||
|
|
||||||
type ShopItemService struct {}
|
type ShopItemService struct {}
|
||||||
|
|
||||||
func (u *ShopItemService) Create(name string, email string, password string) (models.User, error) {
|
func (u *ShopItemService) NewShopItem(name string, abstract string, description string, price float64, tagIds []string) (models.ShopItem, error) {
|
||||||
//hash pw
|
shopItem := models.ShopItem{
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
Name: name,
|
||||||
|
Abstract: abstract,
|
||||||
|
Description: description,
|
||||||
|
Price: price,
|
||||||
|
IsPublic: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tagId := range tagIds {
|
||||||
|
tag, err := repositories.Tags.GetById(tagId)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return models.User{}, err
|
return models.ShopItem{}, fmt.Errorf("Could not get tag by id")
|
||||||
}
|
}
|
||||||
|
|
||||||
user := models.User{Name: name, Email: email, Password: string(hash)}
|
shopItem.Tags = append(shopItem.Tags, tag)
|
||||||
_, err = repositories.Users.Create(user)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return models.User{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, nil
|
return repositories.ShopItems.Create(shopItem)
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
static/img/zine.jpg
Normal file
BIN
static/img/zine.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 308 KiB |
@@ -554,38 +554,16 @@ video {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.sr-only {
|
||||||
width: 100%;
|
position: absolute;
|
||||||
}
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
@media (min-width: 640px) {
|
padding: 0;
|
||||||
.container {
|
margin: -1px;
|
||||||
max-width: 640px;
|
overflow: hidden;
|
||||||
}
|
clip: rect(0, 0, 0, 0);
|
||||||
}
|
white-space: nowrap;
|
||||||
|
border-width: 0;
|
||||||
@media (min-width: 768px) {
|
|
||||||
.container {
|
|
||||||
max-width: 768px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1024px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1280px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1536px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1536px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.absolute {
|
.absolute {
|
||||||
@@ -618,6 +596,18 @@ video {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-2 {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-1 {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-10 {
|
.mt-10 {
|
||||||
margin-top: 2.5rem;
|
margin-top: 2.5rem;
|
||||||
}
|
}
|
||||||
@@ -646,10 +636,22 @@ video {
|
|||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-12 {
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
.h-16 {
|
.h-16 {
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-4 {
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-48 {
|
||||||
|
height: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
.h-8 {
|
.h-8 {
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
@@ -658,6 +660,14 @@ video {
|
|||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-12 {
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-4 {
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.w-auto {
|
.w-auto {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
@@ -678,6 +688,10 @@ video {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.grid-cols-1 {
|
.grid-cols-1 {
|
||||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -686,10 +700,6 @@ video {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-center {
|
|
||||||
align-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-center {
|
.items-center {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -702,10 +712,6 @@ video {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gap-4 {
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-x-reverse: 0;
|
--tw-space-x-reverse: 0;
|
||||||
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
||||||
@@ -718,20 +724,18 @@ video {
|
|||||||
margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
|
margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
||||||
|
}
|
||||||
|
|
||||||
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
|
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
|
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
.overflow-auto {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overflow-hidden {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rounded {
|
.rounded {
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
@@ -744,25 +748,21 @@ video {
|
|||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rounded-xl {
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border {
|
.border {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-b {
|
.border-2 {
|
||||||
border-bottom-width: 1px;
|
border-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-solid {
|
.border-dashed {
|
||||||
border-style: solid;
|
border-style: dashed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-black {
|
.border-gray-300 {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(0 0 0 / var(--tw-border-opacity, 1));
|
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-gray-200 {
|
.bg-gray-200 {
|
||||||
@@ -780,31 +780,21 @@ video {
|
|||||||
background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1));
|
background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-green-200 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(187 247 208 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-indigo-600 {
|
.bg-indigo-600 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(79 70 229 / var(--tw-bg-opacity, 1));
|
background-color: rgb(79 70 229 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-red-200 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(254 202 202 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-slate-50 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(248 250 252 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-white {
|
.bg-white {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.object-cover {
|
||||||
|
-o-object-fit: cover;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
.p-2 {
|
.p-2 {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -852,6 +842,14 @@ video {
|
|||||||
padding-bottom: 0.75rem;
|
padding-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pb-6 {
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-1 {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.pr-2 {
|
.pr-2 {
|
||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -860,6 +858,10 @@ video {
|
|||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pt-5 {
|
||||||
|
padding-top: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -920,6 +922,16 @@ video {
|
|||||||
color: rgb(107 114 128 / var(--tw-text-opacity, 1));
|
color: rgb(107 114 128 / var(--tw-text-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-gray-600 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(75 85 99 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray-700 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(55 65 81 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.text-gray-900 {
|
.text-gray-900 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
|
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
|
||||||
@@ -982,26 +994,36 @@ video {
|
|||||||
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
|
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.focus-within\:outline-none:focus-within {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-within\:ring-2:focus-within {
|
||||||
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-within\:ring-indigo-500:focus-within {
|
||||||
|
--tw-ring-opacity: 1;
|
||||||
|
--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-within\:ring-offset-2:focus-within {
|
||||||
|
--tw-ring-offset-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:bg-gray-700:hover {
|
.hover\:bg-gray-700:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
|
background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:bg-green-400:hover {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(74 222 128 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:bg-indigo-500:hover {
|
.hover\:bg-indigo-500:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(99 102 241 / var(--tw-bg-opacity, 1));
|
background-color: rgb(99 102 241 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:bg-red-400:hover {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(248 113 113 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:text-indigo-500:hover {
|
.hover\:text-indigo-500:hover {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(99 102 241 / var(--tw-text-opacity, 1));
|
color: rgb(99 102 241 / var(--tw-text-opacity, 1));
|
||||||
@@ -1012,6 +1034,16 @@ video {
|
|||||||
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.focus\:border-blue-500:focus {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus\:outline-none:focus {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.focus\:outline:focus {
|
.focus\:outline:focus {
|
||||||
outline-style: solid;
|
outline-style: solid;
|
||||||
}
|
}
|
||||||
@@ -1028,6 +1060,17 @@ video {
|
|||||||
outline-color: #4f46e5;
|
outline-color: #4f46e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.focus\:ring:focus {
|
||||||
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus\:ring-blue-500:focus {
|
||||||
|
--tw-ring-opacity: 1;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.focus-visible\:outline:focus-visible {
|
.focus-visible\:outline:focus-visible {
|
||||||
outline-style: solid;
|
outline-style: solid;
|
||||||
}
|
}
|
||||||
@@ -1094,6 +1137,14 @@ video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
.md\:h-full {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md\:w-48 {
|
||||||
|
width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
.md\:grid-cols-2 {
|
.md\:grid-cols-2 {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -1117,7 +1168,28 @@ video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.dark\:bg-slate-800\/25 {
|
.dark\:border-gray-600 {
|
||||||
background-color: rgb(30 41 59 / 0.25);
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(75 85 99 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark\:bg-gray-800 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark\:text-gray-300 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(209 213 219 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark\:text-gray-900 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark\:focus\:border-blue-500:focus {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
tailwind.config.js
Normal file
16
tailwind.config.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ["./views/**/*.{html,js}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
gridTemplateRows: {
|
||||||
|
// Simple 16 row grid
|
||||||
|
'19': 'repeat(19, minmax(0, 1fr))',
|
||||||
|
|
||||||
|
// Complex site-specific row configuration
|
||||||
|
'layout': '200px minmax(900px, 1fr) 100px',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
||||||
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
|
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
<img class="mx-auto h-10 w-auto" src="static/img/circlea.png" alt="Your Company">
|
<img class="mx-auto h-10 w-auto" src="/static/img/circlea.png" alt="Your Company">
|
||||||
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Add an Item</h2>
|
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Add an Item</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
<form class="space-y-6" action="#" method="POST">
|
<form class="space-y-6" action="#" method="POST" enctype="multipart/form-data">
|
||||||
<div>
|
<div>
|
||||||
<label for="name" class="block text-sm/6 font-medium text-gray-900">Name</label>
|
<label for="name" class="block text-sm/6 font-medium text-gray-900">Name</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
@@ -16,12 +17,43 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="description" class="block text-sm/6 font-medium text-gray-900">Description</label>
|
<label for="abstract" class="block text-sm/6 font-medium text-gray-900">Abstract</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input type="text" name="description" id="description" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
<input type="text" name="abstract" id="abstract" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="description" class="block text-sm/6 font-medium text-gray-900" for="passwordConfirmation">Description</label>
|
||||||
|
<textarea id="description" name="description" type="textarea" class="block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-300 rounded-md dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 focus:border-blue-500 dark:focus:border-blue-500 focus:outline-none focus:ring"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm/6 text-gray-900">Select Categories</label>
|
||||||
|
<div class="space-y-2">
|
||||||
|
{{ range .data.tags }}
|
||||||
|
<label class="flex text-sm/6 items-center">
|
||||||
|
<input type="checkbox" class="form-checkbox h-4 w-4 text-gray-900" value="{{ .ID }}" name="tags[]">
|
||||||
|
<span class="ml-2 text-sm/6 text-gray-900">{{ .Name }}</span>
|
||||||
|
</label>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="tags" class="block text-gray-700">Select Tags</label>
|
||||||
|
<select id="tags" name="tags" multiple class="mt-1 block w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
|
||||||
|
<option value="tag1">Tag 1</option>
|
||||||
|
<option value="tag2">Tag 2</option>
|
||||||
|
<option value="tag3">Tag 3</option>
|
||||||
|
<option value="tag4">Tag 4</option>
|
||||||
|
<option value="tag5">Tag 5</option>
|
||||||
|
</select>
|
||||||
|
<p class="mt-1 text-gray-500 text-sm">Hold down the Ctrl (Windows) or Command (Mac) button to select multiple options.</p>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<label for="price" class="block text-sm/6 font-medium text-gray-900">Price</label>
|
<label for="price" class="block text-sm/6 font-medium text-gray-900">Price</label>
|
||||||
@@ -31,6 +63,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-900">
|
||||||
|
Image
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
|
||||||
|
<div class="space-y-1 text-center">
|
||||||
|
<svg class="mx-auto h-12 w-12 text-gray-900" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
|
||||||
|
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
<div class="flex text-sm text-gray-600">
|
||||||
|
<label for="image-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
||||||
|
<span class="">Upload a file</span>
|
||||||
|
<input id="image-upload" name="image" type="file" accept="image/*" class="sr-only">
|
||||||
|
</label>
|
||||||
|
<p class="pl-1 text-gray-900">or drag and drop</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-900">
|
||||||
|
PNG, JPG, GIF up to 10MB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="mt-10 text-center text-sm/6 text-red-500">
|
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||||
{{ .data.error }}
|
{{ .data.error }}
|
||||||
</p>
|
</p>
|
||||||
@@ -39,7 +94,6 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Add Item</button>
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Add Item</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
38
views/deleteitem.html
Normal file
38
views/deleteitem.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
|
<div class="bg-gray-200 rounded m-4 p-4">
|
||||||
|
<h3 class="text-lg">{{ .data.shopItem.Name }}</h3>
|
||||||
|
<i class="text-xs">{{ .data.shopItem.Description }}</i>
|
||||||
|
<p class="">Price: {{ .data.shopItem.Price }}</p>
|
||||||
|
{{ if .loggedIn }}
|
||||||
|
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||||
|
Do you really want to delete this item??
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||||
|
{{ .data.error }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-green-500">
|
||||||
|
{{ .data.success }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form class="space-y-6" action="#" method="POST">
|
||||||
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Delete</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
<a href="/shopitems/{{ .data.shopItem.ID }}" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-red-300 hover:bg-gray-700 hover:text-white">Cancel</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ template "footer.html" . }}
|
||||||
105
views/edititem.html
Normal file
105
views/edititem.html
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
|
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
||||||
|
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
<img class="mx-auto h-10 w-auto" src="/static/img/circlea.png" alt="Your Company">
|
||||||
|
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Edit Item</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
<form class="space-y-6" action="#" method="POST">
|
||||||
|
<div>
|
||||||
|
<label for="name" class="block text-sm/6 font-medium text-gray-900">Name</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="text" name="name" id="name" value="{{ .data.shopItem.Name }}" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="abstract" class="block text-sm/6 font-medium text-gray-900">Abstract</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="text" name="abstract" id="abstract" value="{{ .data.shopItem.Abstract }}" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="description" class="block text-sm/6 font-medium text-gray-900">Description</label>
|
||||||
|
<textarea id="description" name="description" type="textarea" class="block w-full px-4 py-2 mt-2 text-gray-900 bg-white border border-gray-300 rounded-md dark:bg-gray-800 dark:text-gray-900 dark:border-gray-600 focus:border-blue-500 dark:focus:border-blue-500 focus:outline-none focus:ring">{{ .data.shopItem.Description}}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm/6 text-gray-900">Select Categories</label>
|
||||||
|
<div class="space-y-2">
|
||||||
|
{{ range .data.tags }}
|
||||||
|
<label class="flex text-sm/6 items-center">
|
||||||
|
<input type="checkbox" class="form-checkbox h-4 w-4 text-gray-900" value="{{ .ID }}" name="tags[]">
|
||||||
|
<span class="ml-2 text-sm/6 text-gray-900">{{ .Name }}</span>
|
||||||
|
</label>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="tags" class="block text-gray-700">Select Tags</label>
|
||||||
|
<select id="tags" name="tags" multiple class="mt-1 block w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
|
||||||
|
<option value="tag1">Tag 1</option>
|
||||||
|
<option value="tag2">Tag 2</option>
|
||||||
|
<option value="tag3">Tag 3</option>
|
||||||
|
<option value="tag4">Tag 4</option>
|
||||||
|
<option value="tag5">Tag 5</option>
|
||||||
|
</select>
|
||||||
|
<p class="mt-1 text-gray-500 text-sm">Hold down the Ctrl (Windows) or Command (Mac) button to select multiple options.</p>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<label for="price" class="block text-sm/6 font-medium text-gray-900">Price</label>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input type="number" name="price" id="price" value="{{ .data.shopItem.Price }}" step="0.01" min="0.00" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-900">
|
||||||
|
Image
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
|
||||||
|
<div class="space-y-1 text-center">
|
||||||
|
<svg class="mx-auto h-12 w-12 text-gray-900" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
|
||||||
|
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
<div class="flex text-sm text-gray-600">
|
||||||
|
<label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
||||||
|
<span class="">Upload a file</span>
|
||||||
|
<input id="file-upload" name="file-upload" type="file" class="sr-only">
|
||||||
|
</label>
|
||||||
|
<p class="pl-1 text-gray-900">or drag and drop</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-900">
|
||||||
|
PNG, JPG, GIF up to 10MB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-red-500">
|
||||||
|
{{ .data.error }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-10 text-center text-sm/6 text-green-500">
|
||||||
|
{{ .data.success }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Update Item</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{{ template "footer.html" . }}
|
||||||
@@ -12,9 +12,9 @@
|
|||||||
<div class="mx-auto max-w-7xl px-4 sm:px-8 lg:px-8">
|
<div class="mx-auto max-w-7xl px-4 sm:px-8 lg:px-8">
|
||||||
<div class="relative flex h-16 items-center justify-between">
|
<div class="relative flex h-16 items-center justify-between">
|
||||||
<div class="flex flex-1 items-center">
|
<div class="flex flex-1 items-center">
|
||||||
<div class="flex shrink-0">
|
<a href="/"><div class="flex shrink-0">
|
||||||
<img class="h-8 w-auto" src="static/img/circlea.png" alt="Your Company">
|
<img class="h-8 w-auto" src="/static/img/circlea.png" alt="Your Company">
|
||||||
</div>
|
</div></a>
|
||||||
<!--
|
<!--
|
||||||
{{ if .loggedIn }}
|
{{ if .loggedIn }}
|
||||||
<div class="hidden sm:ml-6 sm:block">
|
<div class="hidden sm:ml-6 sm:block">
|
||||||
@@ -27,13 +27,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{{ if .loggedIn }}
|
{{ if .loggedIn }}
|
||||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
||||||
<a href="additem" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Add Item</a>
|
<a href="/additem" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Add Item</a>
|
||||||
<a href="logout" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-red-300 hover:bg-gray-700 hover:text-white">Logout</a>
|
<a href="/logout" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-red-300 hover:bg-gray-700 hover:text-white">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
||||||
<a href="login" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Login</a>
|
<a href="/login" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Login</a>
|
||||||
<a href="register" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Register</a>
|
<a href="/register" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Register</a>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,10 +2,22 @@
|
|||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
<div class="bg-gray-200 rounded m-4 p-4">
|
<div class="bg-gray-200 rounded m-4 p-4">
|
||||||
<h3 class="text-lg">{{ .data.shopItem.Name }}</h3>
|
<div class="relative w-full md:w-48 mb-4 flex rounded-lg justify-center items-center">
|
||||||
<i class="text-xs">{{ .data.shopItem.Description }}</i>
|
<img src="/{{ .data.shopItem.Image }}" alt="shopping image"
|
||||||
<p class="">Price: {{ .data.shopItem.Price }}</p>
|
class="object-cover w-full h-48 md:h-full rounded">
|
||||||
</div>
|
</div>
|
||||||
|
<h3 class="text-lg">{{ .data.shopItem.Name }}</h3>
|
||||||
|
<i class="text-xs">{{ .data.shopItem.Abstract }}</i>
|
||||||
|
<p class="text-xs">{{ .data.shopItem.Description }}</p>
|
||||||
|
<p class="">Price: {{ .data.shopItem.Price }}</p>
|
||||||
|
{{ if .loggedIn }}
|
||||||
|
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 px-4 sm:px-8">
|
||||||
|
<a href="{{ .data.shopItem.ID }}/edit" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white">Edit</a>
|
||||||
|
<a href="{{ .data.shopItem.ID }}/delete" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-red-300 hover:bg-gray-700 hover:text-white">Delete</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ template "footer.html" . }}
|
{{ template "footer.html" . }}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
{{ range .data.shopItems }}
|
{{ range .data.shopItems }}
|
||||||
<a href="shopitems/{{ .ID }}">
|
<a href="/shopitems/{{ .ID }}">
|
||||||
<div class="bg-gray-200 rounded m-4 p-4">
|
<div class="bg-gray-200 rounded m-4 p-4">
|
||||||
|
|
||||||
|
<div class="relative w-full md:w-48 mb-4 flex rounded-lg justify-center items-center">
|
||||||
|
<img src="/{{ .Image }}" alt="shopping image"
|
||||||
|
class="object-cover w-full h-48 md:h-full rounded">
|
||||||
|
</div>
|
||||||
<h3 class="text-lg">{{ .Name }}</h3>
|
<h3 class="text-lg">{{ .Name }}</h3>
|
||||||
<i class="text-xs">{{ .Description }}</i>
|
<i class="text-xs">{{ .Abstract }}</i>
|
||||||
<p class="">Price: {{ .Price }}</p>
|
<p class="">Price: {{ .Price }}</pÖ>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user