diff --git a/controllers/shopItemController.go b/controllers/shopItemController.go index e8317b2..fb75b37 100644 --- a/controllers/shopItemController.go +++ b/controllers/shopItemController.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "strconv" + "path/filepath" "github.com/gin-gonic/gin" @@ -27,6 +28,10 @@ type ShopItemController interface { AddItemHandler(*gin.Context) CreateTag(*gin.Context) GetAllTags(*gin.Context) + EditItemView(*gin.Context) + EditItemHandler(*gin.Context) + DeleteItemView(*gin.Context) + DeleteItemHandler(*gin.Context) } type shopItemController struct {} @@ -63,6 +68,15 @@ func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.Shop 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 { + dst = filepath.Join("static/uploads", image.Filename) + if err := ctx.SaveUploadedFile(image, dst); err != nil { + return models.ShopItem{}, fmt.Errorf("Could not save image") + } + } if name == "" || description == "" { return models.ShopItem{}, fmt.Errorf("Name or description empty") @@ -80,6 +94,7 @@ func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.Shop Description: description, Price: price, IsPublic: true, + Image: dst, } for _, tagId := range tagIds { @@ -179,7 +194,6 @@ func (rc *shopItemController) ShopItemView(c *gin.Context) { c.HTML(http.StatusOK, "shopitem.html", data) } - func (rc *shopItemController) AddItemView(c *gin.Context) { tags, err := repositories.Tags.GetAll() @@ -201,6 +215,7 @@ func (rc *shopItemController) AddItemHandler(c *gin.Context) { shopItem, err := rc.NewShopItemFromForm(c) if err != nil { + fmt.Println(err) c.HTML(http.StatusBadRequest, "additem.html", gin.H{ "error": err }) return } @@ -208,6 +223,7 @@ func (rc *shopItemController) AddItemHandler(c *gin.Context) { tags, err := repositories.Tags.GetAll() if err != nil { + fmt.Println(err) c.HTML(http.StatusBadRequest, "additem.html", gin.H{ "error": err }) return } @@ -233,6 +249,123 @@ func (rc *shopItemController) AddItemHandler(c *gin.Context) { 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) diff --git a/main.go b/main.go index 96d3c6c..f02d1af 100644 --- a/main.go +++ b/main.go @@ -77,6 +77,10 @@ func main() { { viewRoutes.GET("/", userController.MainView) 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 viewRoutes.GET("/login", userController.LoginView) viewRoutes.GET("/logout", userController.Logout) diff --git a/models/shopItem.go b/models/shopItem.go index 935a5d0..6f8e616 100644 --- a/models/shopItem.go +++ b/models/shopItem.go @@ -12,6 +12,6 @@ type ShopItem struct { Price float64 `json:"price" binding:"required"` IsPublic bool `json:"isPublic" gorm:"default:true"` Tags []Tag `gorm:"many2many:item_tags;"` - //Images gin.multipart.FileHeader `` + Image string } diff --git a/static/img/zine.jpg b/static/img/zine.jpg new file mode 100644 index 0000000..befa05c Binary files /dev/null and b/static/img/zine.jpg differ diff --git a/static/output.css b/static/output.css index efbaaa7..cf9b64f 100644 --- a/static/output.css +++ b/static/output.css @@ -596,18 +596,6 @@ video { margin-right: auto; } -.mt-10 { - margin-top: 2.5rem; -} - -.mt-2 { - margin-top: 0.5rem; -} - -.mt-1 { - margin-top: 0.25rem; -} - .mb-4 { margin-bottom: 1rem; } @@ -616,6 +604,18 @@ video { margin-left: 0.5rem; } +.mt-1 { + margin-top: 0.25rem; +} + +.mt-10 { + margin-top: 2.5rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + .block { display: block; } @@ -636,34 +636,38 @@ video { height: 2.5rem; } -.h-16 { - height: 4rem; -} - -.h-8 { - height: 2rem; -} - .h-12 { height: 3rem; } -.h-5 { - height: 1.25rem; -} - -.h-3 { - height: 0.75rem; +.h-16 { + height: 4rem; } .h-4 { height: 1rem; } +.h-48 { + height: 12rem; +} + +.h-8 { + height: 2rem; +} + .min-h-full { min-height: 100%; } +.w-12 { + width: 3rem; +} + +.w-4 { + width: 1rem; +} + .w-auto { width: auto; } @@ -672,30 +676,10 @@ video { width: 100%; } -.w-12 { - width: 3rem; -} - -.w-5 { - width: 1.25rem; -} - -.w-3 { - width: 0.75rem; -} - -.w-4 { - width: 1rem; -} - .max-w-7xl { max-width: 80rem; } -.max-w-md { - max-width: 28rem; -} - .flex-1 { flex: 1 1 0%; } @@ -740,36 +724,30 @@ video { margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); } -.space-y-6 > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(1.5rem * 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-4 > :not([hidden]) ~ :not([hidden]) { +.space-y-6 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; - margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(1rem * 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)); } .rounded { border-radius: 0.25rem; } -.rounded-md { - border-radius: 0.375rem; -} - .rounded-lg { border-radius: 0.5rem; } +.rounded-md { + border-radius: 0.375rem; +} + .border { border-width: 1px; } @@ -812,18 +790,19 @@ video { background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); } -.p-4 { - padding: 1rem; -} - -.p-6 { - padding: 1.5rem; +.object-cover { + -o-object-fit: cover; + object-fit: cover; } .p-2 { padding: 0.5rem; } +.p-4 { + padding: 1rem; +} + .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -863,14 +842,6 @@ video { padding-bottom: 0.75rem; } -.pr-2 { - padding-right: 0.5rem; -} - -.pt-2 { - padding-top: 0.5rem; -} - .pb-6 { padding-bottom: 1.5rem; } @@ -879,6 +850,14 @@ video { padding-left: 0.25rem; } +.pr-2 { + padding-right: 0.5rem; +} + +.pt-2 { + padding-top: 0.5rem; +} + .pt-5 { padding-top: 1.25rem; } @@ -943,6 +922,16 @@ video { 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 { --tw-text-opacity: 1; color: rgb(17 24 39 / var(--tw-text-opacity, 1)); @@ -973,33 +962,12 @@ video { color: rgb(255 255 255 / var(--tw-text-opacity, 1)); } -.text-gray-700 { - --tw-text-opacity: 1; - color: rgb(55 65 81 / var(--tw-text-opacity, 1)); -} - -.text-gray-600 { - --tw-text-opacity: 1; - color: rgb(75 85 99 / var(--tw-text-opacity, 1)); -} - -.text-blue-600 { - --tw-text-opacity: 1; - color: rgb(37 99 235 / var(--tw-text-opacity, 1)); -} - .shadow-sm { --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.shadow-md { - --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - .outline { outline-style: solid; } @@ -1169,6 +1137,14 @@ video { } @media (min-width: 768px) { + .md\:h-full { + height: 100%; + } + + .md\:w-48 { + width: 12rem; + } + .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -1202,16 +1178,16 @@ video { background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1)); } - .dark\:text-gray-200 { - --tw-text-opacity: 1; - color: rgb(229 231 235 / var(--tw-text-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)); diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..a04b678 --- /dev/null +++ b/tailwind.config.js @@ -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: [], +} diff --git a/views/additem.html b/views/additem.html index 697da16..d6abfb2 100644 --- a/views/additem.html +++ b/views/additem.html @@ -8,7 +8,7 @@