25 Commits

Author SHA1 Message Date
faf07704d2 add fittopage checkbox + custom print options
All checks were successful
Go / build (push) Successful in 16m26s
2025-12-12 19:51:05 +01:00
7fa932e378 [nixpkgs] update 2025-12-11 19:34:22 +01:00
f56b7eb688 [nixpkgs] rename poppler_utils -> poppler-utils
All checks were successful
Go / build (push) Successful in 14m0s
2025-12-05 14:44:03 +01:00
f505fb17bf remove darkmode overwrites
All checks were successful
Go / build (push) Successful in 13m48s
2025-07-02 15:13:25 +02:00
9b67dd7955 responsive print dialog
Some checks failed
Go / build (push) Has been cancelled
2025-07-02 15:02:17 +02:00
a6e7baf087 fix printjob execution
All checks were successful
Go / build (push) Successful in 13m33s
two bugs here:
- accidently comparing with == instead != against error
- using return instead of continue during for loop
2025-07-02 14:14:36 +02:00
144e14ee62 update vendor hash
All checks were successful
Go / build (push) Successful in 13m47s
2025-07-02 13:28:57 +02:00
275eaf7b52 Merge pull request 'Automatic Price Calculation' (#33) from priceCalculation into master
All checks were successful
Go / build (push) Successful in 13m37s
Reviewed-on: #33
2025-07-02 11:26:52 +02:00
249cccd240 show newest invoice first, show datetime
All checks were successful
Go / build (push) Successful in 13m34s
2025-07-02 11:09:33 +02:00
db3dc9ecd1 better invoice view 2025-07-02 11:09:10 +02:00
b75c46ec2f use pdfcpu for pagecount
All checks were successful
Go / build (push) Successful in 13m36s
2025-07-02 00:50:04 +02:00
16c68cd0f8 set WasPrinted after printjob execution
All checks were successful
Go / build (push) Successful in 12m19s
2025-07-02 00:33:11 +02:00
ad5573ee2c add CoverPage hint to <select> 2025-07-02 00:28:27 +02:00
6c0440f06d add coverpage to price calculation 2025-07-02 00:28:13 +02:00
9e638dcfc2 add tested/not tested indicator on print view
All checks were successful
Go / build (push) Successful in 12m19s
2025-07-02 00:10:37 +02:00
6c59d1233f show invoice after print
All checks were successful
Go / build (push) Successful in 12m16s
2025-07-01 23:45:27 +02:00
7025f526c1 add invoice view
All checks were successful
Go / build (push) Successful in 12m14s
2025-07-01 15:30:06 +02:00
992b9c17c3 store invoices and printjobs
All checks were successful
Go / build (push) Successful in 12m19s
2025-07-01 13:38:28 +02:00
4b0649439c update PrintJob model and add Invoice 2025-07-01 13:15:02 +02:00
8ce01417e7 select papertype on print
All checks were successful
Go / build (push) Successful in 12m15s
2025-06-29 16:58:07 +02:00
8e1df934b3 WIP cost calculation 2025-06-29 16:57:51 +02:00
17a1ef0123 fix paperview
All checks were successful
Go / build (push) Successful in 12m14s
2025-06-29 15:37:10 +02:00
6330a990f5 add paper weight 2025-06-29 15:37:02 +02:00
f4faeb351d add basic paper model/view/controller
All checks were successful
Go / build (push) Successful in 12m50s
paper weight is missing
2025-06-27 17:02:57 +02:00
861b18651b add ui configurable config options 2025-06-27 16:31:37 +02:00
36 changed files with 1644 additions and 425 deletions

View File

@@ -0,0 +1,350 @@
package controllers
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"git.dynamicdiscord.de/kalipso/zineshop/models"
//"git.dynamicdiscord.de/kalipso/zineshop/services"
"git.dynamicdiscord.de/kalipso/zineshop/repositories"
)
type ConfigController interface {
AddConfigHandler(*gin.Context)
ConfigHandler(*gin.Context)
ConfigView(*gin.Context)
GetAllPaper(*gin.Context)
PaperView(*gin.Context)
PaperHandler(*gin.Context)
AddPaperHandler(*gin.Context)
InvoiceView(*gin.Context)
InvoiceHandler(*gin.Context)
CreateTag(*gin.Context)
GetAllTags(*gin.Context)
TagView(*gin.Context)
TagHandler(*gin.Context)
AddTagHandler(*gin.Context)
}
type configController struct{}
func NewConfigController() ConfigController {
return &configController{}
}
func (rc *configController) AddConfigHandler(c *gin.Context) {
key := c.PostForm("key")
value := c.PostForm("value")
if key == "" || value == "" {
err := "Key or Value empty during config creation"
fmt.Println(err)
c.HTML(http.StatusBadRequest, "configview.html", gin.H{"error": err})
return
}
config := models.Config{
Key: key,
Value: value,
}
_, err := repositories.ConfigOptions.Create(config)
if err != nil {
data := CreateSessionData(c, gin.H{
"error": err,
"success": "",
})
c.HTML(http.StatusOK, "configview.html", data)
return
}
rc.ConfigView(c)
}
func (rc *configController) ConfigView(c *gin.Context) {
configOptions, err := repositories.ConfigOptions.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "configview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"configOptions": configOptions,
})
if err != nil {
c.HTML(http.StatusBadRequest, "configview.html", data)
}
c.HTML(http.StatusOK, "configview.html", data)
}
func (rc *configController) ConfigHandler(ctx *gin.Context) {
key := ctx.PostForm("key")
value := ctx.PostForm("value")
action := ctx.PostForm("action")
config, err := repositories.ConfigOptions.GetById(ctx.Param("id"))
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "configview.html", gin.H{"error": err})
return
}
if action == "update" {
config.Key = key
config.Value = value
config, err = repositories.ConfigOptions.Update(config)
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "configview.html", gin.H{"error": err})
return
}
}
if action == "delete" {
repositories.ConfigOptions.DeleteById(ctx.Param("id"))
}
rc.ConfigView(ctx)
}
func (rc *configController) PaperHandler(ctx *gin.Context) {
newPaper, err := models.NewPaper(ctx)
action := ctx.PostForm("action")
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err})
return
}
paper, err := repositories.Papers.GetById(ctx.Param("id"))
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err})
return
}
if action == "update" {
paper.Name = newPaper.Name
paper.Brand = newPaper.Brand
paper.Size = newPaper.Size
paper.Weight = newPaper.Weight
paper.Price = newPaper.Price
paper, err = repositories.Papers.Update(paper)
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err})
return
}
}
if action == "delete" {
repositories.Papers.DeleteById(ctx.Param("id"))
}
rc.PaperView(ctx)
}
func (rc *configController) AddPaperHandler(c *gin.Context) {
paper, err := models.NewPaper(c)
if err != nil {
fmt.Println(err)
c.HTML(http.StatusBadRequest, "paperview.html", gin.H{"error": err})
return
}
_, err = repositories.Papers.Create(paper)
if err != nil {
data := CreateSessionData(c, gin.H{
"error": err,
"success": "",
})
c.HTML(http.StatusOK, "paperview.html", data)
return
}
rc.PaperView(c)
}
func (rc *configController) PaperView(c *gin.Context) {
papers, err := repositories.Papers.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "paperview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"paper": papers,
})
if err != nil {
c.HTML(http.StatusBadRequest, "paperview.html", data)
}
c.HTML(http.StatusOK, "paperview.html", data)
}
func (rc *configController) GetAllPaper(c *gin.Context) {
papers, err := repositories.Papers.GetAll()
if err != nil {
ReplyError(c, fmt.Errorf("Could not query Papers"))
return
}
c.JSON(http.StatusOK, papers)
}
//////////////////////////////////////////////////////////////////////
func (rc *configController) InvoiceView(c *gin.Context) {
invoices, err := repositories.Invoices.GetAllNewestFirst()
if err != nil {
c.HTML(http.StatusBadRequest, "invoiceview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"invoices": invoices,
})
if err != nil {
c.HTML(http.StatusBadRequest, "invoiceview.html", data)
}
c.HTML(http.StatusOK, "invoiceview.html", data)
}
func (rc *configController) InvoiceHandler(ctx *gin.Context) {
action := ctx.PostForm("action")
if action == "delete" {
repositories.Invoices.DeleteById(ctx.Param("id"))
}
rc.InvoiceView(ctx)
}
//////////////////////////////////////////////////////////////////////
func (rc *configController) TagHandler(ctx *gin.Context) {
name := ctx.PostForm("name")
color := ctx.PostForm("color")
action := ctx.PostForm("action")
tag, err := repositories.Tags.GetById(ctx.Param("id"))
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
if action == "update" {
tag.Name = name
tag.Color = color
tag, err = repositories.Tags.Update(tag)
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
}
if action == "delete" {
repositories.Tags.DeleteById(ctx.Param("id"))
}
rc.TagView(ctx)
}
func (rc *configController) AddTagHandler(c *gin.Context) {
tag, err := models.NewTag(c)
if err != nil {
fmt.Println(err)
c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
_, err = repositories.Tags.Create(tag)
if err != nil {
data := CreateSessionData(c, gin.H{
"error": err,
"success": "",
})
c.HTML(http.StatusOK, "tagview.html", data)
return
}
rc.TagView(c)
}
func (rc *configController) TagView(c *gin.Context) {
tags, err := repositories.Tags.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"tags": tags,
})
if err != nil {
c.HTML(http.StatusBadRequest, "tagview.html", data)
}
c.HTML(http.StatusOK, "tagview.html", data)
}
func (rc *configController) 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("tag 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 *configController) 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)
}

View File

@@ -63,7 +63,14 @@ func (rc *printController) PrintCartView(c *gin.Context) {
return return
} }
paper, err := repositories.Papers.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
data := CreateSessionData(c, gin.H{ data := CreateSessionData(c, gin.H{
"paper": paper,
"cartItems": cartItems, "cartItems": cartItems,
}) })
@@ -78,9 +85,16 @@ func (rc *printController) PrintOrderView(c *gin.Context) {
return return
} }
paper, err := repositories.Papers.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
cartItems := order.CartItems cartItems := order.CartItems
data := CreateSessionData(c, gin.H{ data := CreateSessionData(c, gin.H{
"paper": paper,
"cartItems": cartItems, "cartItems": cartItems,
}) })
@@ -90,6 +104,7 @@ func (rc *printController) PrintOrderView(c *gin.Context) {
func (rc *printController) PrintHandler(c *gin.Context) { func (rc *printController) PrintHandler(c *gin.Context) {
variantIds := c.PostFormArray("variant-id[]") variantIds := c.PostFormArray("variant-id[]")
variantAmounts := c.PostFormArray("variant-amount[]") variantAmounts := c.PostFormArray("variant-amount[]")
variantPapertypes := c.PostFormArray("variant-papertype[]")
variantCoverPages := c.PostFormArray("variant-coverpage[]") variantCoverPages := c.PostFormArray("variant-coverpage[]")
if len(variantIds) != len(variantAmounts) || len(variantIds) != len(variantCoverPages) { if len(variantIds) != len(variantAmounts) || len(variantIds) != len(variantCoverPages) {
@@ -98,6 +113,7 @@ func (rc *printController) PrintHandler(c *gin.Context) {
} }
var printJobs []models.PrintJob var printJobs []models.PrintJob
priceTotal := 0.0
for idx := range variantIds { for idx := range variantIds {
variant, err := repositories.ShopItems.GetVariantById(variantIds[idx]) variant, err := repositories.ShopItems.GetVariantById(variantIds[idx])
@@ -113,9 +129,21 @@ func (rc *printController) PrintHandler(c *gin.Context) {
return return
} }
coverPage := false paperType, err := repositories.Papers.GetById(fmt.Sprintf("%v", variantPapertypes[idx]))
if variantCoverPages[idx] == "1" { if err != nil {
coverPage = true c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
var coverPage *models.Paper
if variantCoverPages[idx] != "0" {
coverPageTmp, err := repositories.Papers.GetById(fmt.Sprintf("%v", variantCoverPages[idx]))
if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
coverPage = &coverPageTmp
} }
variantAmount, err := strconv.Atoi(variantAmounts[idx]) variantAmount, err := strconv.Atoi(variantAmounts[idx])
@@ -124,23 +152,56 @@ func (rc *printController) PrintHandler(c *gin.Context) {
return return
} }
printJob, err := models.NewPrintJob(shopItem, variant, coverPage, uint(variantAmount)) fmt.Println("Printing Costs:")
printJob, err := models.NewPrintJob(shopItem, variant, paperType, coverPage, uint(variantAmount))
if err != nil { if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}}) c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return return
} }
printJob.CalculatePrintCosts()
priceTotal += printJob.PriceTotal
printJobs = append(printJobs, printJob) printJobs = append(printJobs, printJob)
} }
invoice := models.Invoice{
PrintJobs: printJobs,
PricePerClick: 0.002604,
PartCosts: 0.0067,
PriceTotal: priceTotal,
}
invoice, err := repositories.Invoices.Create(invoice)
if err != nil {
c.HTML(http.StatusBadRequest, "error.html", gin.H{"data": gin.H{"error": err}})
return
}
executeJobs := func() { executeJobs := func() {
for _, printJob := range printJobs { for _, printJob := range printJobs {
printJob.Execute() err := printJob.Execute()
if err != nil {
fmt.Printf("Error: %s\n", err)
continue
}
printJob.ShopItem.WasPrinted = true
_, err = repositories.ShopItems.Update(printJob.ShopItem)
if err != nil {
fmt.Printf("Error: %s\n", err)
}
} }
} }
go executeJobs() go executeJobs()
c.HTML(http.StatusOK, "index.html", nil) fmt.Println(invoice)
data := CreateSessionData(c, gin.H{
"invoice": invoice,
})
c.HTML(http.StatusOK, "printstarted.html", data)
} }

View File

@@ -29,15 +29,10 @@ type ShopItemController interface {
AddItemHandler(*gin.Context) AddItemHandler(*gin.Context)
AddItemsView(*gin.Context) AddItemsView(*gin.Context)
AddItemsHandler(*gin.Context) AddItemsHandler(*gin.Context)
CreateTag(*gin.Context)
GetAllTags(*gin.Context)
EditItemView(*gin.Context) EditItemView(*gin.Context)
EditItemHandler(*gin.Context) EditItemHandler(*gin.Context)
DeleteItemView(*gin.Context) DeleteItemView(*gin.Context)
DeleteItemHandler(*gin.Context) DeleteItemHandler(*gin.Context)
TagView(*gin.Context)
TagHandler(*gin.Context)
AddTagHandler(*gin.Context)
} }
type shopItemController struct{} type shopItemController struct{}
@@ -83,6 +78,8 @@ func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.Shop
image, err := ctx.FormFile("image") image, err := ctx.FormFile("image")
dstImage := defaultImagePath dstImage := defaultImagePath
printMode := ctx.PostForm("print-mode") printMode := ctx.PostForm("print-mode")
fitToPage := ctx.PostForm("fit-to-page")
customPrintOptions := ctx.PostForm("custom-print-options")
if err == nil { if err == nil {
dstImage = filepath.Join("static/uploads", image.Filename) dstImage = filepath.Join("static/uploads", image.Filename)
@@ -155,17 +152,26 @@ func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.Shop
}) })
} }
doFit := false
fmt.Println("FitToPage: ", fitToPage)
if fitToPage == "true" {
doFit = true
}
shopItem := models.ShopItem{ shopItem := models.ShopItem{
Name: name, Name: name,
Abstract: abstract, Abstract: abstract,
Description: description, Description: description,
Category: category, Category: category,
IsPublic: true, IsPublic: true,
BasePrice: rc.GetBasePrice(variants), BasePrice: rc.GetBasePrice(variants),
Image: dstImage, Image: dstImage,
Pdf: dstPdf, Pdf: dstPdf,
Variants: variants, Variants: variants,
PrintMode: printMode, PrintMode: printMode,
WasPrinted: false,
FitToPage: doFit,
CustomPrintOptions: customPrintOptions,
} }
fmt.Println("Creating Shopitem: ", shopItem) fmt.Println("Creating Shopitem: ", shopItem)
@@ -395,16 +401,19 @@ func (rc *shopItemController) AddItemsHandler(c *gin.Context) {
} }
shopItem := models.ShopItem{ shopItem := models.ShopItem{
Name: file.Filename, Name: file.Filename,
Abstract: file.Filename, Abstract: file.Filename,
Description: file.Filename, Description: file.Filename,
Category: category, Category: category,
IsPublic: true, IsPublic: true,
BasePrice: rc.GetBasePrice(variants), BasePrice: rc.GetBasePrice(variants),
Image: dstImage, Image: dstImage,
Pdf: dstPdf, Pdf: dstPdf,
Variants: variants, Variants: variants,
PrintMode: "CreateBooklet", PrintMode: "CreateBooklet",
WasPrinted: false,
FitToPage: false,
CustomPrintOptions: "",
} }
_, err = repositories.ShopItems.Create(shopItem) _, err = repositories.ShopItems.Create(shopItem)
@@ -542,6 +551,9 @@ func (rc *shopItemController) EditItemHandler(c *gin.Context) {
newShopItem.Variants = shopItem.Variants newShopItem.Variants = shopItem.Variants
newShopItem.BasePrice = shopItem.BasePrice newShopItem.BasePrice = shopItem.BasePrice
newShopItem.IsPublic = shopItem.IsPublic newShopItem.IsPublic = shopItem.IsPublic
newShopItem.WasPrinted = false
newShopItem.FitToPage = shopItem.FitToPage
newShopItem.CustomPrintOptions = shopItem.CustomPrintOptions
if len(shopItem.Tags) != 0 { if len(shopItem.Tags) != 0 {
newShopItem.Tags = shopItem.Tags newShopItem.Tags = shopItem.Tags
@@ -621,115 +633,6 @@ func (rc *shopItemController) DeleteItemHandler(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", data) c.HTML(http.StatusOK, "index.html", data)
} }
func (rc *shopItemController) TagHandler(ctx *gin.Context) {
name := ctx.PostForm("name")
color := ctx.PostForm("color")
action := ctx.PostForm("action")
tag, err := repositories.Tags.GetById(ctx.Param("id"))
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
if action == "update" {
tag.Name = name
tag.Color = color
tag, err = repositories.Tags.Update(tag)
if err != nil {
fmt.Println(err)
ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
}
if action == "delete" {
repositories.Tags.DeleteById(ctx.Param("id"))
}
rc.TagView(ctx)
}
func (rc *shopItemController) AddTagHandler(c *gin.Context) {
tag, err := models.NewTag(c)
if err != nil {
fmt.Println(err)
c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
return
}
_, err = repositories.Tags.Create(tag)
if err != nil {
data := CreateSessionData(c, gin.H{
"error": err,
"success": "",
})
c.HTML(http.StatusOK, "tagview.html", data)
return
}
rc.TagView(c)
}
func (rc *shopItemController) TagView(c *gin.Context) {
tags, err := repositories.Tags.GetAll()
if err != nil {
c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"data": gin.H{"error": err}})
}
data := CreateSessionData(c, gin.H{
"tags": tags,
})
if err != nil {
c.HTML(http.StatusBadRequest, "tagview.html", data)
}
c.HTML(http.StatusOK, "tagview.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
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1744463964, "lastModified": 1765186076,
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=", "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650", "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -19,7 +19,7 @@
packages = with pkgs; [ packages = with pkgs; [
go go
gotools gotools
poppler_utils #get first pdf page to png poppler-utils #get first pdf page to png
cups cups
imagemagick imagemagick
tailwindcss tailwindcss
@@ -29,7 +29,7 @@
packages.zineshop = nixpkgs.legacyPackages.x86_64-linux.buildGoModule { packages.zineshop = nixpkgs.legacyPackages.x86_64-linux.buildGoModule {
pname = "zineshop"; pname = "zineshop";
version = "1.0"; version = "1.0";
vendorHash = "sha256-0M/xblZXVw4xIFZeDewYrFu7VGUCsPTPG13r9ZpTGJo="; vendorHash = "sha256-aMB2kRVNvBmZO6YIMCXvENPL4NEOb4AOaMvSf8+z3xw=";
src = ./.; src = ./.;
postInstall = '' postInstall = ''
@@ -72,7 +72,7 @@
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
environment.systemPackages = [ environment.systemPackages = [
zineshop-pkg zineshop-pkg
pkgs.poppler_utils #get first pdf page to png pkgs.poppler-utils #get first pdf page to png
pkgs.cups pkgs.cups
pkgs.imagemagick pkgs.imagemagick
]; ];
@@ -104,7 +104,7 @@
WorkingDirectory = "/var/lib/zineshop"; WorkingDirectory = "/var/lib/zineshop";
ExecStart = pkgs.writeScript "start-zineshop" '' ExecStart = pkgs.writeScript "start-zineshop" ''
#! ${pkgs.bash}/bin/bash #! ${pkgs.bash}/bin/bash
PATH="$PATH:${lib.makeBinPath [ pkgs.poppler_utils pkgs.cups pkgs.imagemagick ]}" PATH="$PATH:${lib.makeBinPath [ pkgs.poppler-utils pkgs.cups pkgs.imagemagick ]}"
${zineshop-pkg}/bin/zineshop ${zineshop-pkg}/bin/zineshop
''; '';
Restart = "on-failure"; Restart = "on-failure";

15
go.mod
View File

@@ -6,7 +6,8 @@ require (
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.23.0 github.com/pdfcpu/pdfcpu v0.11.0
golang.org/x/crypto v0.38.0
gorm.io/driver/sqlite v1.5.7 gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
) )
@@ -22,22 +23,30 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.0 // indirect
github.com/hhrutter/tiff v1.0.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/image v0.27.0 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.25.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

31
go.sum
View File

@@ -30,6 +30,12 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -46,6 +52,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -53,10 +61,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pdfcpu/pdfcpu v0.11.0 h1:mL18Y3hSHzSezmnrzA21TqlayBOXuAx7BUzzZyroLGM=
github.com/pdfcpu/pdfcpu v0.11.0/go.mod h1:F1ca4GIVFdPtmgvIdvXAycAm88noyNxZwzr9CpTy+Mw=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -76,22 +91,26 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

20
main.go
View File

@@ -19,6 +19,7 @@ var (
userController controllers.UserController = controllers.UserController{} userController controllers.UserController = controllers.UserController{}
cartItemController controllers.CartItemController = controllers.NewCartItemController() cartItemController controllers.CartItemController = controllers.NewCartItemController()
printController controllers.PrintController = controllers.NewPrintController() printController controllers.PrintController = controllers.NewPrintController()
configController controllers.ConfigController = controllers.NewConfigController()
authValidator middlewares.AuthValidator = middlewares.AuthValidator{} authValidator middlewares.AuthValidator = middlewares.AuthValidator{}
) )
@@ -67,10 +68,23 @@ func main() {
viewRoutes.GET("/cart/print", authValidator.RequireAdmin, printController.PrintCartView) viewRoutes.GET("/cart/print", authValidator.RequireAdmin, printController.PrintCartView)
viewRoutes.POST("/print", authValidator.RequireAdmin, printController.PrintHandler) viewRoutes.POST("/print", authValidator.RequireAdmin, printController.PrintHandler)
viewRoutes.GET("/tags", authValidator.RequireAdmin, shopItemController.TagView) viewRoutes.GET("/config", authValidator.RequireAdmin, configController.ConfigView)
viewRoutes.POST("/tags/:id", authValidator.RequireAdmin, shopItemController.TagHandler) viewRoutes.POST("/config/:id", authValidator.RequireAdmin, configController.ConfigHandler)
viewRoutes.POST("/config", authValidator.RequireAdmin, configController.AddConfigHandler)
viewRoutes.GET("/tags", authValidator.RequireAdmin, configController.TagView)
viewRoutes.POST("/tags/:id", authValidator.RequireAdmin, configController.TagHandler)
viewRoutes.GET("/tags/:id", userController.TagView) viewRoutes.GET("/tags/:id", userController.TagView)
viewRoutes.POST("/tags", authValidator.RequireAdmin, shopItemController.AddTagHandler) viewRoutes.POST("/tags", authValidator.RequireAdmin, configController.AddTagHandler)
viewRoutes.GET("/paper", authValidator.RequireAdmin, configController.PaperView)
viewRoutes.POST("/paper/:id", authValidator.RequireAdmin, configController.PaperHandler)
viewRoutes.GET("/paper/:id", userController.TagView)
viewRoutes.POST("/paper", authValidator.RequireAdmin, configController.AddPaperHandler)
viewRoutes.GET("/invoice", authValidator.RequireAdmin, configController.InvoiceView)
viewRoutes.POST("/invoice/:id", authValidator.RequireAdmin, configController.InvoiceHandler)
viewRoutes.GET("/cart", authValidator.RequireAuth, cartItemController.CartItemView) viewRoutes.GET("/cart", authValidator.RequireAuth, cartItemController.CartItemView)
viewRoutes.POST("/cart", authValidator.RequireAuth, cartItemController.AddItemHandler) viewRoutes.POST("/cart", authValidator.RequireAuth, cartItemController.AddItemHandler)
viewRoutes.POST("/cart/delete", authValidator.RequireAuth, cartItemController.DeleteItemHandler) viewRoutes.POST("/cart/delete", authValidator.RequireAuth, cartItemController.DeleteItemHandler)

11
models/config.go Normal file
View File

@@ -0,0 +1,11 @@
package models
import (
"gorm.io/gorm"
)
type Config struct {
gorm.Model
Key string `json:"key" binding:"required" gorm:"unique;not null"`
Value string `json:"value" binding:"required"`
}

83
models/paper.go Normal file
View File

@@ -0,0 +1,83 @@
package models
import (
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strconv"
"strings"
)
type PaperSize string
const (
A3 PaperSize = "A3"
A4 PaperSize = "A4"
A5 PaperSize = "A5"
SRA3 PaperSize = "SRA3"
)
func ParseSize(s string) (c PaperSize, err error) {
s = strings.ToUpper(s)
if s == "A3" {
return A3, nil
} else if s == "A4" {
return A4, nil
} else if s == "A5" {
return A5, nil
} else if s == "SRA3" {
return SRA3, nil
}
return c, fmt.Errorf("Cannot parse category %s", s)
}
type Paper struct {
gorm.Model
Name string `json:"name" binding:"required" gorm:"not null"`
Brand string `json:"brand" binding:"required"`
Size PaperSize `json:"size" binding:"required"`
Weight int `json:"weight" binding:"required"`
Price float64 `json:"price" binding:"required"`
}
func NewPaper(ctx *gin.Context) (Paper, error) {
name := ctx.PostForm("name")
brand := ctx.PostForm("brand")
sizeTmp := ctx.PostForm("size")
weightTmp := ctx.PostForm("weight")
priceTmp := ctx.PostForm("price")
size, err := ParseSize(sizeTmp)
if err != nil {
return Paper{}, fmt.Errorf("Couldnt parse Size")
}
weight, err := strconv.Atoi(weightTmp)
if err != nil {
return Paper{}, fmt.Errorf("Couldnt parse Weight")
}
price, err := strconv.ParseFloat(priceTmp, 64)
if err != nil {
return Paper{}, fmt.Errorf("Couldnt parse Price")
}
if name == "" || brand == "" {
return Paper{}, fmt.Errorf("Name or brand empty")
}
// Convert the price string to float64
tag := Paper{
Name: name,
Brand: brand,
Size: size,
Weight: weight,
Price: price,
}
return tag, nil
}

View File

@@ -2,6 +2,9 @@ package models
import ( import (
"fmt" "fmt"
"git.dynamicdiscord.de/kalipso/zineshop/utils"
"gorm.io/gorm"
"math"
"os/exec" "os/exec"
"strings" "strings"
) )
@@ -16,14 +19,39 @@ const (
ShortEdge PrintOption = "-o Binding=TopBinding" ShortEdge PrintOption = "-o Binding=TopBinding"
CreateBooklet PrintOption = "-o Combination=Booklet -o PageSize=A5" CreateBooklet PrintOption = "-o Combination=Booklet -o PageSize=A5"
TriFold PrintOption = "-o Fold=TriFold -o Binding=TopBinding" TriFold PrintOption = "-o Fold=TriFold -o Binding=TopBinding"
FitToPage PrintOption = "-o fit-to-page"
) )
type PrintJob struct { type OldPrintJob struct {
Pdf string Pdf string
Amount uint Amount uint
Options []PrintOption Options []PrintOption
} }
type Invoice struct {
gorm.Model
PrintJobs []PrintJob
PricePerClick float64
PartCosts float64
PriceTotal float64
}
type PrintJob struct {
gorm.Model
ShopItemID uint
ShopItem ShopItem
VariantID uint
Variant ItemVariant
PaperTypeId uint
PaperType Paper `gorm:"foreignKey:PaperTypeId"`
CoverPaperTypeId *uint
CoverPaperType *Paper `gorm:"foreignKey:CoverPaperTypeId"`
Amount uint
PricePerPiece float64
PriceTotal float64
InvoiceID uint
}
func GetPrintMode(mode string) PrintOption { func GetPrintMode(mode string) PrintOption {
if mode == "LongEdge" { if mode == "LongEdge" {
return LongEdge return LongEdge
@@ -40,7 +68,7 @@ func GetPrintMode(mode string) PrintOption {
return CreateBooklet return CreateBooklet
} }
func NewPrintJob(shopItem ShopItem, variant ItemVariant, coverPage bool, amount uint) (PrintJob, error) { func NewPrintJob(shopItem ShopItem, variant ItemVariant, paperType Paper, coverPaperType *Paper, amount uint) (PrintJob, error) {
if shopItem.Pdf == "" { if shopItem.Pdf == "" {
return PrintJob{}, fmt.Errorf("ShopItem has no PDF assigned") return PrintJob{}, fmt.Errorf("ShopItem has no PDF assigned")
} }
@@ -49,32 +77,49 @@ func NewPrintJob(shopItem ShopItem, variant ItemVariant, coverPage bool, amount
return PrintJob{}, fmt.Errorf("Amount to big. This is denied for security reasons") return PrintJob{}, fmt.Errorf("Amount to big. This is denied for security reasons")
} }
var result PrintJob result := PrintJob{
result.Pdf = shopItem.Pdf ShopItem: shopItem,
result.Amount = amount Variant: variant,
PaperType: paperType,
if variant.Name == "Colored" { CoverPaperType: coverPaperType,
result.Options = append(result.Options, Colored) Amount: amount,
} }
if coverPage {
result.Options = append(result.Options, CoverPage)
}
result.Options = append(result.Options, GetPrintMode(shopItem.PrintMode))
return result, nil return result, nil
} }
func (p *PrintJob) IsColored() bool {
return p.Variant.Name == "Colored"
}
func (p *PrintJob) GeneratePrintOptions() []PrintOption {
var result []PrintOption
if p.Variant.Name == "Colored" {
result = append(result, Colored)
}
if p.CoverPaperType != nil {
result = append(result, CoverPage)
}
if p.ShopItem.FitToPage {
result = append(result, FitToPage)
}
result = append(result, GetPrintMode(p.ShopItem.PrintMode))
result = append(result, PrintOption(p.ShopItem.CustomPrintOptions))
return result
}
func (p *PrintJob) Execute() error { func (p *PrintJob) Execute() error {
baseCommand := "lp -d KonicaBooklet" baseCommand := "lp -d KonicaBooklet"
baseCommand += fmt.Sprintf(" -n %v ", p.Amount) baseCommand += fmt.Sprintf(" -n %v ", p.Amount)
for _, option := range p.Options { for _, option := range p.GeneratePrintOptions() {
baseCommand += fmt.Sprintf(" %v ", option) baseCommand += fmt.Sprintf(" %v ", option)
} }
baseCommand += fmt.Sprintf(" -- %s", p.Pdf) baseCommand += fmt.Sprintf(" -- %s", p.ShopItem.Pdf)
parts := strings.Fields(baseCommand) parts := strings.Fields(baseCommand)
@@ -91,3 +136,51 @@ func (p *PrintJob) Execute() error {
fmt.Printf("Output:\n%s\n", output) fmt.Printf("Output:\n%s\n", output)
return nil return nil
} }
func (p *PrintJob) CalculatePrintCosts() (float64, error) {
pageCount := utils.CountPagesAtPath(p.ShopItem.Pdf)
if pageCount == 0 {
fmt.Println("Pagecount of 0 - something is wrong here.")
return 0, fmt.Errorf("Cant calculate price, pdf seems to be empty")
}
printMode := GetPrintMode(p.ShopItem.PrintMode)
//Get actual pagecount depending on printmode
actualPageCount := pageCount
fmt.Println("PagCount: ", actualPageCount)
if printMode == CreateBooklet {
dividedCount := float64(pageCount) / 4.0
actualPageCount = int(math.Ceil(dividedCount))
}
if printMode == LongEdge || printMode == ShortEdge {
dividedCount := float64(pageCount) / 2.0
actualPageCount = int(math.Ceil(dividedCount))
}
PPC := 0.002604
partCost := 0.0067
if p.IsColored() {
partCost = 0.0478
}
printingCosts := float64(actualPageCount-1) * p.PaperType.Price
if p.CoverPaperType != nil {
printingCosts += p.CoverPaperType.Price
} else {
printingCosts += p.PaperType.Price
}
printingCosts += float64(actualPageCount/2) * PPC
printingCosts += partCost * float64(actualPageCount)
fmt.Printf("Printing Costs per Zine: %v\n", printingCosts)
fmt.Printf("Printing Costs Total: %v\n", printingCosts*float64(p.Amount))
p.PricePerPiece = printingCosts
p.PriceTotal = printingCosts * float64(p.Amount)
return printingCosts, nil
}

View File

@@ -42,15 +42,18 @@ type ItemVariant struct {
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"` Abstract string `json:"abstract" binding:"required"`
Description string `json:"description" binding:"required"` Description string `json:"description" binding:"required"`
Category Category `json:"category"` Category Category `json:"category"`
Variants []ItemVariant `json:"variant"` Variants []ItemVariant `json:"variant"`
BasePrice float64 `json:"basePrice"` BasePrice float64 `json:"basePrice"`
IsPublic bool `json:"isPublic" gorm:"default:true"` IsPublic bool `json:"isPublic" gorm:"default:true"`
Tags []Tag `gorm:"many2many:item_tags;"` Tags []Tag `gorm:"many2many:item_tags;"`
Image string Image string
Pdf string Pdf string
PrintMode string `json:"printMode" gorm:"default:CreateBooklet"` PrintMode string `json:"printMode" gorm:"default:CreateBooklet"`
CustomPrintOptions string `gorm:"default:''"`
FitToPage bool `gorm:"default:false"`
WasPrinted bool `gorm:"default:false"`
} }

View File

@@ -0,0 +1,100 @@
package repositories
import (
"strconv"
"gorm.io/gorm"
"git.dynamicdiscord.de/kalipso/zineshop/models"
)
type InvoiceRepository interface {
Create(models.Invoice) (models.Invoice, error)
GetAll() ([]models.Invoice, error)
GetAllSorted(string) ([]models.Invoice, error)
GetAllNewestFirst() ([]models.Invoice, error)
GetAllNewestLast() ([]models.Invoice, error)
GetById(string) (models.Invoice, error)
//GetByInvoiceId(string) (models.Invoice, error)
Update(models.Invoice) (models.Invoice, error)
DeleteById(string) error
}
type GORMInvoiceRepository struct {
DB *gorm.DB
}
func NewGORMInvoiceRepository(db *gorm.DB) InvoiceRepository {
return &GORMInvoiceRepository{
DB: db,
}
}
func (t *GORMInvoiceRepository) Create(invoice models.Invoice) (models.Invoice, error) {
result := t.DB.Create(&invoice)
if result.Error != nil {
return models.Invoice{}, result.Error
}
return invoice, nil
}
func (t *GORMInvoiceRepository) GetAll() ([]models.Invoice, error) {
var invoice []models.Invoice
result := t.DB.Preload("PrintJobs.ShopItem").Preload("PrintJobs.Variant").Preload("PrintJobs.PaperType").Preload("PrintJobs.CoverPaperType").Preload("PrintJobs").Find(&invoice)
return invoice, result.Error
}
func (t *GORMInvoiceRepository) GetAllSorted(sortString string) ([]models.Invoice, error) {
var invoices []models.Invoice
result := t.DB.Preload("PrintJobs.ShopItem").Preload("PrintJobs.Variant").Preload("PrintJobs.PaperType").Preload("PrintJobs.CoverPaperType").Preload("PrintJobs").Order(sortString).Find(&invoices)
return invoices, result.Error
}
func (r *GORMInvoiceRepository) GetAllNewestFirst() ([]models.Invoice, error) {
return r.GetAllSorted("created_at desc")
}
func (r *GORMInvoiceRepository) GetAllNewestLast() ([]models.Invoice, error) {
return r.GetAllSorted("created_at asc")
}
func (t *GORMInvoiceRepository) GetById(id string) (models.Invoice, error) {
invoiceId, err := strconv.Atoi(id)
if err != nil {
return models.Invoice{}, err
}
var invoice models.Invoice
result := t.DB.Preload("PrintJobs").First(&invoice, uint(invoiceId))
if result.Error != nil {
return models.Invoice{}, result.Error
}
return invoice, nil
}
func (t *GORMInvoiceRepository) Update(invoice models.Invoice) (models.Invoice, error) {
result := t.DB.Save(&invoice)
if result.Error != nil {
return models.Invoice{}, result.Error
}
return invoice, nil
}
func (t *GORMInvoiceRepository) DeleteById(id string) error {
invoiceId, err := strconv.Atoi(id)
if err != nil {
return err
}
result := t.DB.Delete(&models.Invoice{}, invoiceId)
return result.Error
}

View File

@@ -0,0 +1,81 @@
package repositories
import (
"strconv"
"gorm.io/gorm"
"git.dynamicdiscord.de/kalipso/zineshop/models"
)
type ConfigRepository interface {
Create(models.Config) (models.Config, error)
GetAll() ([]models.Config, error)
GetById(string) (models.Config, error)
Update(models.Config) (models.Config, error)
DeleteById(string) error
}
type GORMConfigRepository struct {
DB *gorm.DB
}
func NewGORMConfigRepository(db *gorm.DB) ConfigRepository {
return &GORMConfigRepository{
DB: db,
}
}
func (t *GORMConfigRepository) Create(config models.Config) (models.Config, error) {
result := t.DB.Create(&config)
if result.Error != nil {
return models.Config{}, result.Error
}
return config, nil
}
func (t *GORMConfigRepository) GetAll() ([]models.Config, error) {
var configs []models.Config
result := t.DB.Find(&configs)
return configs, result.Error
}
func (t *GORMConfigRepository) GetById(id string) (models.Config, error) {
configId, err := strconv.Atoi(id)
if err != nil {
return models.Config{}, err
}
var config models.Config
result := t.DB.First(&config, uint(configId))
if result.Error != nil {
return models.Config{}, result.Error
}
return config, nil
}
func (t *GORMConfigRepository) Update(config models.Config) (models.Config, error) {
result := t.DB.Save(&config)
if result.Error != nil {
return models.Config{}, result.Error
}
return config, nil
}
func (t *GORMConfigRepository) DeleteById(id string) error {
configId, err := strconv.Atoi(id)
if err != nil {
return err
}
result := t.DB.Delete(&models.Config{}, configId)
return result.Error
}

View File

@@ -0,0 +1,82 @@
package repositories
import (
"strconv"
"gorm.io/gorm"
"git.dynamicdiscord.de/kalipso/zineshop/models"
)
type PaperRepository interface {
Create(models.Paper) (models.Paper, error)
GetAll() ([]models.Paper, error)
GetById(string) (models.Paper, error)
//GetByShopItemId(string) (models.Paper, error)
Update(models.Paper) (models.Paper, error)
DeleteById(string) error
}
type GORMPaperRepository struct {
DB *gorm.DB
}
func NewGORMPaperRepository(db *gorm.DB) PaperRepository {
return &GORMPaperRepository{
DB: db,
}
}
func (t *GORMPaperRepository) Create(tag models.Paper) (models.Paper, error) {
result := t.DB.Create(&tag)
if result.Error != nil {
return models.Paper{}, result.Error
}
return tag, nil
}
func (t *GORMPaperRepository) GetAll() ([]models.Paper, error) {
var tags []models.Paper
result := t.DB.Find(&tags)
return tags, result.Error
}
func (t *GORMPaperRepository) GetById(id string) (models.Paper, error) {
tagId, err := strconv.Atoi(id)
if err != nil {
return models.Paper{}, err
}
var tag models.Paper
result := t.DB.First(&tag, uint(tagId))
if result.Error != nil {
return models.Paper{}, result.Error
}
return tag, nil
}
func (t *GORMPaperRepository) Update(tag models.Paper) (models.Paper, error) {
result := t.DB.Save(&tag)
if result.Error != nil {
return models.Paper{}, result.Error
}
return tag, nil
}
func (t *GORMPaperRepository) DeleteById(id string) error {
tagId, err := strconv.Atoi(id)
if err != nil {
return err
}
result := t.DB.Delete(&models.Paper{}, tagId)
return result.Error
}

View File

@@ -0,0 +1,82 @@
package repositories
import (
"strconv"
"gorm.io/gorm"
"git.dynamicdiscord.de/kalipso/zineshop/models"
)
type PrintJobRepository interface {
Create(models.PrintJob) (models.PrintJob, error)
GetAll() ([]models.PrintJob, error)
GetById(string) (models.PrintJob, error)
//GetByShopItemId(string) (models.PrintJob, error)
Update(models.PrintJob) (models.PrintJob, error)
DeleteById(string) error
}
type GORMPrintJobRepository struct {
DB *gorm.DB
}
func NewGORMPrintJobRepository(db *gorm.DB) PrintJobRepository {
return &GORMPrintJobRepository{
DB: db,
}
}
func (t *GORMPrintJobRepository) Create(printJob models.PrintJob) (models.PrintJob, error) {
result := t.DB.Create(&printJob)
if result.Error != nil {
return models.PrintJob{}, result.Error
}
return printJob, nil
}
func (t *GORMPrintJobRepository) GetAll() ([]models.PrintJob, error) {
var printJobs []models.PrintJob
result := t.DB.Preload("ShopItem").Preload("Variant").Preload("PaperType").Preload("CoverPaperType").Find(&printJobs)
return printJobs, result.Error
}
func (t *GORMPrintJobRepository) GetById(id string) (models.PrintJob, error) {
printJobId, err := strconv.Atoi(id)
if err != nil {
return models.PrintJob{}, err
}
var printJob models.PrintJob
result := t.DB.Preload("ShopItem").Preload("Variant").Preload("PaperType").Preload("CoverPaperType").First(&printJob, uint(printJobId))
if result.Error != nil {
return models.PrintJob{}, result.Error
}
return printJob, nil
}
func (t *GORMPrintJobRepository) Update(printJob models.PrintJob) (models.PrintJob, error) {
result := t.DB.Save(&printJob)
if result.Error != nil {
return models.PrintJob{}, result.Error
}
return printJob, nil
}
func (t *GORMPrintJobRepository) DeleteById(id string) error {
printJobId, err := strconv.Atoi(id)
if err != nil {
return err
}
result := t.DB.Delete(&models.PrintJob{}, printJobId)
return result.Error
}

View File

@@ -9,12 +9,16 @@ import (
) )
var ( var (
ShopItems ShopItemRepository ShopItems ShopItemRepository
Users UserRepository Users UserRepository
Tags TagRepository Tags TagRepository
CartItems CartItemRepository CartItems CartItemRepository
Orders OrderRepository Orders OrderRepository
Tokens RegisterTokenRepository Tokens RegisterTokenRepository
ConfigOptions ConfigRepository
Papers PaperRepository
PrintJobs PrintJobRepository
Invoices InvoiceRepository
) )
func InitRepositories() { func InitRepositories() {
@@ -29,6 +33,10 @@ func InitRepositories() {
&models.Tag{}, &models.Tag{},
&models.CartItem{}, &models.CartItem{},
&models.Order{}, &models.Order{},
&models.Config{},
&models.Paper{},
&models.PrintJob{},
&models.Invoice{},
&models.RegisterToken{}) &models.RegisterToken{})
if err != nil { if err != nil {
@@ -41,4 +49,8 @@ func InitRepositories() {
CartItems = NewGORMCartItemRepository(db) CartItems = NewGORMCartItemRepository(db)
Orders = NewGORMOrderRepository(db) Orders = NewGORMOrderRepository(db)
Tokens = NewGORMRegisterTokenRepository(db) Tokens = NewGORMRegisterTokenRepository(db)
ConfigOptions = NewGORMConfigRepository(db)
Papers = NewGORMPaperRepository(db)
PrintJobs = NewGORMPrintJobRepository(db)
Invoices = NewGORMInvoiceRepository(db)
} }

View File

@@ -583,6 +583,10 @@ video {
right: 0px; right: 0px;
} }
.col-span-1 {
grid-column: span 1 / span 1;
}
.col-span-12 { .col-span-12 {
grid-column: span 12 / span 12; grid-column: span 12 / span 12;
} }
@@ -591,6 +595,14 @@ video {
grid-column: span 3 / span 3; grid-column: span 3 / span 3;
} }
.col-span-7 {
grid-column: span 7 / span 7;
}
.-m-1\.5 {
margin: -0.375rem;
}
.m-2 { .m-2 {
margin: 0.5rem; margin: 0.5rem;
} }
@@ -678,10 +690,18 @@ video {
display: block; display: block;
} }
.inline-block {
display: inline-block;
}
.flex { .flex {
display: flex; display: flex;
} }
.inline-flex {
display: inline-flex;
}
.table { .table {
display: table; display: table;
} }
@@ -742,6 +762,10 @@ video {
width: 3rem; width: 3rem;
} }
.w-16 {
width: 4rem;
}
.w-4 { .w-4 {
width: 1rem; width: 1rem;
} }
@@ -758,6 +782,10 @@ video {
width: 100%; width: 100%;
} }
.min-w-full {
min-width: 100%;
}
.max-w-2xl { .max-w-2xl {
max-width: 42rem; max-width: 42rem;
} }
@@ -766,6 +794,10 @@ video {
max-width: 48rem; max-width: 48rem;
} }
.max-w-4xl {
max-width: 56rem;
}
.max-w-6xl { .max-w-6xl {
max-width: 72rem; max-width: 72rem;
} }
@@ -810,6 +842,14 @@ video {
grid-template-columns: repeat(12, minmax(0, 1fr)); grid-template-columns: repeat(12, minmax(0, 1fr));
} }
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-cols-6 { .grid-cols-6 {
grid-template-columns: repeat(6, minmax(0, 1fr)); grid-template-columns: repeat(6, minmax(0, 1fr));
} }
@@ -818,10 +858,18 @@ video {
flex-direction: column; flex-direction: column;
} }
.flex-wrap {
flex-wrap: wrap;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }
.items-stretch {
align-items: stretch;
}
.justify-center { .justify-center {
justify-content: center; justify-content: center;
} }
@@ -830,6 +878,10 @@ video {
justify-content: space-between; justify-content: space-between;
} }
.gap-2 {
gap: 0.5rem;
}
.gap-4 { .gap-4 {
gap: 1rem; gap: 1rem;
} }
@@ -851,6 +903,12 @@ video {
row-gap: 1rem; row-gap: 1rem;
} }
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
}
.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));
@@ -892,6 +950,10 @@ video {
border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1)); border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1));
} }
.overflow-hidden {
overflow: hidden;
}
.overflow-x-auto { .overflow-x-auto {
overflow-x: auto; overflow-x: auto;
} }
@@ -900,6 +962,11 @@ video {
white-space: nowrap; white-space: nowrap;
} }
.break-normal {
overflow-wrap: normal;
word-break: normal;
}
.rounded { .rounded {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
@@ -974,6 +1041,11 @@ video {
background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1)); background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
} }
.bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
}
.bg-blue-600 { .bg-blue-600 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
@@ -1049,6 +1121,16 @@ video {
background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1)); background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1));
} }
.bg-green-50 {
--tw-bg-opacity: 1;
background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1));
}
.bg-green-500 {
--tw-bg-opacity: 1;
background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1));
}
.bg-green-600 { .bg-green-600 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1)); background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
@@ -1134,6 +1216,16 @@ video {
background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1)); background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
} }
.bg-red-50 {
--tw-bg-opacity: 1;
background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
}
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));
}
.bg-red-800 { .bg-red-800 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(153 27 27 / var(--tw-bg-opacity, 1)); background-color: rgb(153 27 27 / var(--tw-bg-opacity, 1));
@@ -1242,6 +1334,10 @@ video {
object-fit: cover; object-fit: cover;
} }
.p-1\.5 {
padding: 0.375rem;
}
.p-2 { .p-2 {
padding: 0.5rem; padding: 0.5rem;
} }
@@ -1289,6 +1385,11 @@ video {
padding-bottom: 0.125rem; padding-bottom: 0.125rem;
} }
.py-1 {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.py-1\.5 { .py-1\.5 {
padding-top: 0.375rem; padding-top: 0.375rem;
padding-bottom: 0.375rem; padding-bottom: 0.375rem;
@@ -1319,6 +1420,11 @@ video {
padding-bottom: 6rem; padding-bottom: 6rem;
} }
.py-3 {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.py-4 { .py-4 {
padding-top: 1rem; padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
@@ -1365,6 +1471,18 @@ video {
text-align: right; text-align: right;
} }
.text-start {
text-align: start;
}
.text-end {
text-align: end;
}
.align-middle {
vertical-align: middle;
}
.text-2xl { .text-2xl {
font-size: 1.5rem; font-size: 1.5rem;
line-height: 2rem; line-height: 2rem;
@@ -1436,6 +1554,10 @@ video {
font-weight: 600; font-weight: 600;
} }
.uppercase {
text-transform: uppercase;
}
.leading-10 { .leading-10 {
line-height: 2.5rem; line-height: 2.5rem;
} }
@@ -1547,6 +1669,11 @@ video {
color: rgb(34 197 94 / var(--tw-text-opacity, 1)); color: rgb(34 197 94 / var(--tw-text-opacity, 1));
} }
.text-green-700 {
--tw-text-opacity: 1;
color: rgb(21 128 61 / var(--tw-text-opacity, 1));
}
.text-green-800 { .text-green-800 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(22 101 52 / var(--tw-text-opacity, 1)); color: rgb(22 101 52 / var(--tw-text-opacity, 1));
@@ -1627,6 +1754,11 @@ video {
color: rgb(239 68 68 / var(--tw-text-opacity, 1)); color: rgb(239 68 68 / var(--tw-text-opacity, 1));
} }
.text-red-700 {
--tw-text-opacity: 1;
color: rgb(185 28 28 / var(--tw-text-opacity, 1));
}
.text-red-800 { .text-red-800 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(153 27 27 / var(--tw-text-opacity, 1)); color: rgb(153 27 27 / var(--tw-text-opacity, 1));
@@ -1722,27 +1854,22 @@ video {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.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-xl {
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px 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-2xl { .shadow-2xl {
--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); --tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color); --tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
} }
.shadow-red-900 { .shadow-md {
--tw-shadow-color: #7f1d1d; --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--tw-shadow: var(--tw-shadow-colored); --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);
}
.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);
} }
.outline { .outline {
@@ -1761,6 +1888,24 @@ video {
outline-color: #d1d5db; outline-color: #d1d5db;
} }
.ring-1 {
--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(1px + 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);
}
.ring-inset {
--tw-ring-inset: inset;
}
.ring-green-600\/20 {
--tw-ring-color: rgb(22 163 74 / 0.2);
}
.ring-red-600\/10 {
--tw-ring-color: rgb(220 38 38 / 0.1);
}
.filter { .filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
} }
@@ -1849,6 +1994,11 @@ video {
background-color: rgb(67 56 202 / var(--tw-bg-opacity, 1)); background-color: rgb(67 56 202 / var(--tw-bg-opacity, 1));
} }
.hover\:bg-red-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1));
}
.hover\:bg-red-900:hover { .hover\:bg-red-900:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(127 29 29 / var(--tw-bg-opacity, 1)); background-color: rgb(127 29 29 / var(--tw-bg-opacity, 1));
@@ -2003,6 +2153,10 @@ video {
display: flex; display: flex;
} }
.sm\:grid {
display: grid;
}
.sm\:hidden { .sm\:hidden {
display: none; display: none;
} }
@@ -2015,6 +2169,10 @@ video {
max-width: 24rem; max-width: 24rem;
} }
.sm\:grid-cols-8 {
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.sm\:items-center { .sm\:items-center {
align-items: center; align-items: center;
} }
@@ -2102,6 +2260,10 @@ video {
max-width: 80rem; max-width: 80rem;
} }
.lg\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.lg\:grid-cols-3 { .lg\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
@@ -2126,125 +2288,22 @@ video {
} }
@media (min-width: 1280px) { @media (min-width: 1280px) {
.xl\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.xl\:grid-cols-4 { .xl\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
} }
} }
@media (min-width: 1536px) { @media (min-width: 1536px) {
.\32xl\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.\32xl\:px-0 { .\32xl\:px-0 {
padding-left: 0px; padding-left: 0px;
padding-right: 0px; padding-right: 0px;
} }
} }
@media (prefers-color-scheme: dark) {
.dark\:hidden {
display: none;
}
.dark\:divide-gray-800 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-opacity: 1;
border-color: rgb(31 41 55 / var(--tw-divide-opacity, 1));
}
.dark\:border-gray-600 {
--tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity, 1));
}
.dark\:border-gray-700 {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity, 1));
}
.dark\:border-gray-800 {
--tw-border-opacity: 1;
border-color: rgb(31 41 55 / var(--tw-border-opacity, 1));
}
.dark\:bg-gray-600 {
--tw-bg-opacity: 1;
background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1));
}
.dark\:bg-gray-700 {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
}
.dark\:bg-gray-800 {
--tw-bg-opacity: 1;
background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
}
.dark\:bg-gray-900 {
--tw-bg-opacity: 1;
background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1));
}
.dark\:bg-red-600 {
--tw-bg-opacity: 1;
background-color: rgb(220 38 38 / 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-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
}
.dark\:text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
}
.dark\:text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
}
.dark\:placeholder-gray-400::-moz-placeholder {
--tw-placeholder-opacity: 1;
color: rgb(156 163 175 / var(--tw-placeholder-opacity, 1));
}
.dark\:placeholder-gray-400::placeholder {
--tw-placeholder-opacity: 1;
color: rgb(156 163 175 / var(--tw-placeholder-opacity, 1));
}
.dark\:hover\:bg-gray-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1));
}
.dark\:hover\:bg-gray-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
}
.dark\:hover\:text-white:hover {
--tw-text-opacity: 1;
color: rgb(255 255 255 / 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));
}
.dark\:focus\:ring-blue-500:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
}
.dark\:focus\:ring-gray-700:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(55 65 81 / var(--tw-ring-opacity, 1));
}
}

View File

@@ -3,6 +3,8 @@ package utils
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt"
"github.com/pdfcpu/pdfcpu/pkg/api"
) )
func GenerateSessionId(length int) string { func GenerateSessionId(length int) string {
@@ -17,3 +19,14 @@ func GenerateSessionId(length int) string {
func GenerateToken() string { func GenerateToken() string {
return GenerateSessionId(16) return GenerateSessionId(16)
} }
func CountPagesAtPath(path string) (pages int) {
ctx, err := api.ReadContextFile(path)
if err != nil {
fmt.Println("Error reading PDF:", err)
return
}
pages = ctx.PageCount
return
}

View File

@@ -25,7 +25,7 @@
<div> <div>
<label for="description" class="block text-sm/6 font-medium text-gray-900" for="passwordConfirmation">Description</label> <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> <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 focus:border-blue-500 focus:outline-none focus:ring"></textarea>
</div> </div>
<div class="mb-4"> <div class="mb-4">
@@ -127,7 +127,7 @@
<label class="block text-sm font-medium text-gray-900"> <label class="block text-sm font-medium text-gray-900">
Print Mode Print Mode
</label> </label>
<select name="print-mode" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> <select name="print-mode" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 ">
<option selected value="CreateBooklet">Create Booklet</option> <option selected value="CreateBooklet">Create Booklet</option>
<option value="LongEdge">Long Edge</option> <option value="LongEdge">Long Edge</option>
<option value="ShortEdge">Short Edge</option> <option value="ShortEdge">Short Edge</option>

View File

@@ -62,7 +62,7 @@
<form action="/checkout" method="GET"> <form action="/checkout" method="GET">
<div class="flex flex-col md:flex-row items-center md:items-center justify-between lg:px-6 pb-6 border-b border-gray-200 max-lg:max-w-lg max-lg:mx-auto"> <div class="flex flex-col md:flex-row items-center md:items-center justify-between lg:px-6 pb-6 border-b border-gray-200 max-lg:max-w-lg max-lg:mx-auto">
<h2 class="text-gray-900 font-manrope font-semibold leading-9 w-full max-md:text-center max-md:mb-4">Select shipping method</h2> <h2 class="text-gray-900 font-manrope font-semibold leading-9 w-full max-md:text-center max-md:mb-4">Select shipping method</h2>
<select name="shippingMethod" id="shippingMethod" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> <select name="shippingMethod" id="shippingMethod" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 ">
<option selected value="">Shipping</option> <option selected value="">Shipping</option>
{{ range .data.shipping }} {{ range .data.shipping }}
<option value="{{ .Id }}">{{ .Name }} - {{ .Price }}€</option> <option value="{{ .Id }}">{{ .Name }} - {{ .Price }}€</option>

View File

@@ -70,7 +70,7 @@
<div> <div>
<label for="comment" class="block text-sm/6 font-medium text-gray-900" for="passwordConfirmation">Comment</label> <label for="comment" class="block text-sm/6 font-medium text-gray-900" for="passwordConfirmation">Comment</label>
<textarea id="comment" name="comment" 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> <textarea id="comment" name="comment" type="textarea" class="block w-full px-4 py-2 mt-2 text-gray-700 bg-white border border-gray-300 rounded-md focus:border-blue-500 focus:outline-none focus:ring"></textarea>
</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">

33
views/configview.html Normal file
View File

@@ -0,0 +1,33 @@
{{ 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/logo-black.png" alt="Your Company">
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Edit config options</h2>
</div>
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
{{ range .data.configOptions }}
<form action="/config/{{ .ID }}" method="POST">
<div class="max-w-md mx-auto mt-4">
<div class="flex">
<input type="text" id="key" name="key" value="{{ .Key }}" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<input type="text" id="value" name="value" value="{{ .Value }}" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<button type="submit" name="action" value="update" class="bg-blue-600 text-white ml-4 mr-4 rounded px-4 hover:bg-blue-700">Update</button>
<button type="submit" name="action" value="delete" class="bg-red-800 text-white rounded px-4 hover:bg-red-900">Delete</button>
</div>
</form>
{{ end }}
<form action="/config" method="POST">
<div class="max-w-md mx-auto mt-4">
<div class="flex">
<input type="text" id="key" name="key" placeholder="" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<input type="text" id="value" name="value" placeholder="" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<button type="submit" class="bg-green-600 text-white ml-4 mr-4 rounded px-4 hover:bg-green-700">Add</button>
</div>
</div>
</form>
</div>
{{ template "footer.html" . }}

View File

@@ -62,14 +62,14 @@
<div> <div>
<label for="description" class="block text-sm/6 font-medium text-gray-900">Description</label> <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> <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 focus:border-blue-500 focus:outline-none focus:ring">{{ .data.shopItem.Description}}</textarea>
</div> </div>
<label class="block text-sm/6 font-medium text-gray-900"> <label class="block text-sm/6 font-medium text-gray-900">
Print Mode Print Mode
</label> </label>
<select name="print-mode" required class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg <select name="print-mode" required class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg
focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 ">
<option selected value="{{ .data.shopItem.PrintMode }}">{{ .data.shopItem.PrintMode }}</option> <option selected value="{{ .data.shopItem.PrintMode }}">{{ .data.shopItem.PrintMode }}</option>
<option value="CreateBooklet">CreateBooklet</option> <option value="CreateBooklet">CreateBooklet</option>
<option value="LongEdge">Long Edge</option> <option value="LongEdge">Long Edge</option>
@@ -77,6 +77,20 @@
<option value="TriFold">Tri-Fold (Flyer)</option> <option value="TriFold">Tri-Fold (Flyer)</option>
</select> </select>
<label class="flex text-sm/6 items-center">
<input type="checkbox" {{ if .data.shopItem.FitToPage }} checked {{ else }} {{ end }} class="form-checkbox h-4 w-4 text-gray-900"
value="true" name="fit-to-page">
<span class="ml-2 text-sm/6 text-gray-900">Fit to Page</span>
</label>
<div>
<label for="custom-print-options" class="block text-sm/6 font-medium text-gray-900">Custom Print Options:</label>
<textarea id="custom-print-options" name="custom-print-options" type="textarea" class="block w-full px-4 py-2
mt-2 text-gray-900 bg-white border border-gray-300 rounded-md focus:border-blue-500 focus:outline-none
focus:ring">{{ .data.shopItem.CustomPrintOptions}}</textarea>
</div>
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm/6 text-gray-900">Select Categories</label> <label class="block text-sm/6 text-gray-900">Select Categories</label>

View File

@@ -14,7 +14,7 @@
<div class="flex"> <div class="flex">
<input type="text" id="name" name="name" value="{{ .Token }}" readonly="readonly" class="flex-grow border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500"> <input type="text" id="name" name="name" value="{{ .Token }}" readonly="readonly" class="flex-grow border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<div> <div>
<select name="order-status" required class="bg-gray-50 border ml-4 border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> <select name="order-status" required class="bg-gray-50 border ml-4 border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 ">
<option selected value="{{ .Status }}">{{ .Status }}</option> <option selected value="{{ .Status }}">{{ .Status }}</option>
<option value="AwaitingConfirmation">AwaitingConfirmation</option> <option value="AwaitingConfirmation">AwaitingConfirmation</option>
<option value="Received">Received</option> <option value="Received">Received</option>

View File

@@ -38,6 +38,12 @@
hover:text-white">Print</a> hover:text-white">Print</a>
<a href="/cart" class="rounded-md bg-gray-900 m-2 px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 <a href="/cart" 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">Cart</a> hover:text-white">Cart</a>
<a href="/config" 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">Config</a>
<a href="/paper" 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">Paper</a>
<a href="/invoice" 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">Invoices</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>
{{ end }} {{ end }}

55
views/invoice.html Normal file
View File

@@ -0,0 +1,55 @@
<div class="-m-1.5 overflow-x-auto">
<div class="p-1.5 min-w-full inline-block align-middle">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-gray-200 ">
<thead>
<tr>
<th scope="col" class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase "></th>
<th scope="col" class="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase ">Name</th>
<th scope="col" class="px-6 py-3 text-end text-xs font-medium text-gray-500 uppercase ">Paper</th>
<th scope="col" class="px-6 py-3 text-end text-xs font-medium text-gray-500 uppercase ">Amount</th>
<th scope="col" class="px-6 py-3 text-end text-xs font-medium text-gray-500 uppercase ">Price</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 ">
{{ range .PrintJobs }}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 ">
<a href="/shopitems/{{ .Variant.ShopItemID }}" class="flex items-center aspect-square w-8 h-10 shrink-0">
<img class="h-auto w-full max-h-full " src="/{{ .ShopItem.Image }}" alt="imac image" />
</a>
</td>
<td class="px-6 py-4 text-sm text-gray-800 ">
<div class="text-sm break-normal">
<p>{{ .ShopItem.Name }}</p>
<p>{{ .Variant.Name }}</p>
</div>
</td>
<td class="px-6 py-4 text-sm text-gray-800 ">
<div class="text-xs">
<p>{{ .PaperType.Brand }} - {{.PaperType.Name }}: {{ .PaperType.Size }} {{ .PaperType.Weight }}g</p>
{{ if .CoverPaperType }}
<p class="text-red-700">{{ .CoverPaperType.Brand }} - {{.CoverPaperType.Name }}: {{ .CoverPaperType.Size }} {{ .CoverPaperType.Weight }}g</p>
{{ end }}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 ">{{ .Amount }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 ">{{ .PriceTotal }}</td>
</tr>
{{ end }}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 ">
<b>TOTAL</b>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 "></td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 "></td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 "></td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 "><b>{{ .PriceTotal }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

29
views/invoiceview.html Normal file
View File

@@ -0,0 +1,29 @@
{{ 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/logo-black.png" alt="Your Company">
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Invoices</h2>
</div>
{{ range .data.invoices }}
<div class="w-full grid grid-cols-1 gap-4 mx-auto p-4 m-4 flex flex-wrap border rounded shadow-md max-w-4xl">
<div class="">
<div class="font-bold text-center mb-4">
Invoice #{{ .ID }} - {{ .CreatedAt }}
</div>
</div>
<div class="flex flex-col">
{{ template "invoice.html" . }}
</div>
<div class="grid grid-cols-2 mt-4 flex flex-wrap gap-2 w-full">
<form action="/invoice/{{ .ID }}" method="POST">
<button type="submit" name="action" value="delete" class="bg-red-500 hover:bg-red-700 text-white text-sm/6 font-bold py-2 px-4 rounded">Delete</button>
</form>
</div>
</div>
{{ end }}
</div>
</div>
{{ template "footer.html" . }}

View File

@@ -1,27 +1,27 @@
{{ template "header.html" . }} {{ template "header.html" . }}
<section class="bg-white py-8 antialiased dark:bg-gray-900 md:py-16"> <section class="bg-white py-8 antialiased md:py-16">
<div class="mx-auto max-w-screen-xl px-4 2xl:px-0"> <div class="mx-auto max-w-screen-xl px-4 2xl:px-0">
<div class="mx-auto max-w-3xl"> <div class="mx-auto max-w-3xl">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">Order summary</h2> <h2 class="text-xl font-semibold text-gray-900 sm:text-2xl">Order summary</h2>
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400"> <dd class="mt-1 text-base font-normal text-gray-500 ">
Thanks for your order! As soon as your payment arrived we will print your Order. Thanks for your order! As soon as your payment arrived we will print your Order.
</dd> </dd>
<div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 dark:border-gray-700 sm:mt-8"> <div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 sm:mt-8">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Order status: {{ .data.order.Status }}</h4> <h4 class="text-lg font-semibold text-gray-900 ">Order status: {{ .data.order.Status }}</h4>
<dl> <dl>
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400"> <dd class="mt-1 text-base font-normal text-gray-500 ">
Order Code: {{ .data.order.Token }} Order Code: {{ .data.order.Token }}
</dd> </dd>
</dl> </dl>
</div> </div>
<div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 dark:border-gray-700 sm:mt-8"> <div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 sm:mt-8">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Payment information</h4> <h4 class="text-lg font-semibold text-gray-900 ">Payment information</h4>
<dl> <dl>
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400"> <dd class="mt-1 text-base font-normal text-gray-500 ">
Either you transfer money to our bank account, or you come by and pay in cash.<br><br> Either you transfer money to our bank account, or you come by and pay in cash.<br><br>
Miteinander Dresden e.V.*<br> Miteinander Dresden e.V.*<br>
@@ -33,11 +33,11 @@
</dl> </dl>
</div> </div>
<div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 dark:border-gray-700 sm:mt-8"> <div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 sm:mt-8">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Delivery information</h4> <h4 class="text-lg font-semibold text-gray-900 ">Delivery information</h4>
<dl> <dl>
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400"> <dd class="mt-1 text-base font-normal text-gray-500 ">
<p><b>Shipping:</b> {{ .data.shipping.Name }}</p> <p><b>Shipping:</b> {{ .data.shipping.Name }}</p>
{{ if .data.askAddress }} {{ if .data.askAddress }}
<p><b>First Name:</b> {{ .data.order.FirstName }}</p> <p><b>First Name:</b> {{ .data.order.FirstName }}</p>
@@ -54,21 +54,21 @@
</div> </div>
<div class="mt-6 sm:mt-8"> <div class="mt-6 sm:mt-8">
<div class="relative overflow-x-auto border-b border-gray-200 dark:border-gray-800"> <div class="relative overflow-x-auto border-b border-gray-200 ">
<table class="w-full text-left font-medium text-gray-900 dark:text-white "> <table class="w-full text-left font-medium text-gray-900 ">
<tbody class="divide-y divide-gray-200 dark:divide-gray-800"> <tbody class="divide-y divide-gray-200 ">
{{ range .data.order.CartItems }} {{ range .data.order.CartItems }}
<tr> <tr>
<td class="whitespace-nowrap py-4"> <td class="whitespace-nowrap py-4">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<a href="#" class="flex items-center aspect-square w-8 h-10 shrink-0"> <a href="#" class="flex items-center aspect-square w-8 h-10 shrink-0">
<img class="h-auto w-full max-h-full dark:hidden" src="/{{ .ShopItem.Image }}" alt="imac image" /> <img class="h-auto w-full max-h-full " src="/{{ .ShopItem.Image }}" alt="imac image" />
</a> </a>
<a href="/shopitems/{{ .ShopItem.ID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a> <a href="/shopitems/{{ .ShopItem.ID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a>
</div> </div>
</td> </td>
<td class="p-4 text-base font-normal text-gray-900 dark:text-white">x{{ .Quantity }}</td> <td class="p-4 text-base font-normal text-gray-900 ">x{{ .Quantity }}</td>
<td class="p-4 text-right text-base font-bold text-gray-900 dark:text-white">{{ .ItemVariant.Price }}€</td> <td class="p-4 text-right text-base font-bold text-gray-900 ">{{ .ItemVariant.Price }}€</td>
</tr> </tr>
{{ end }} {{ end }}
@@ -81,20 +81,20 @@
<div class="space-y-4"> <div class="space-y-4">
<div class="space-y-2"> <div class="space-y-2">
<dl class="flex items-center justify-between gap-4"> <dl class="flex items-center justify-between gap-4">
<dt class="text-gray-500 dark:text-gray-400">Original price</dt> <dt class="text-gray-500 ">Original price</dt>
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ .data.priceProducts }}€</dd> <dd class="text-base font-medium text-gray-900 ">{{ .data.priceProducts }}€</dd>
</dl> </dl>
<dl class="flex items-center justify-between gap-4"> <dl class="flex items-center justify-between gap-4">
<dt class="text-gray-500 dark:text-gray-400">Shipping</dt> <dt class="text-gray-500 ">Shipping</dt>
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ .data.shipping.Price }}€</dd> <dd class="text-base font-medium text-gray-900 ">{{ .data.shipping.Price }}€</dd>
</dl> </dl>
</div> </div>
<dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 dark:border-gray-700"> <dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 ">
<dt class="text-lg font-bold text-gray-900 dark:text-white">Total</dt> <dt class="text-lg font-bold text-gray-900 ">Total</dt>
<dd class="text-lg font-bold text-gray-900 dark:text-white">{{ .data.priceTotal }}€</dd> <dd class="text-lg font-bold text-gray-900 ">{{ .data.priceTotal }}€</dd>
</dl> </dl>
</div> </div>
</div> </div>

View File

@@ -1,16 +1,16 @@
{{ template "header.html" . }} {{ template "header.html" . }}
<section class="bg-white py-8 antialiased dark:bg-gray-900 md:py-16"> <section class="bg-white py-8 antialiased md:py-16">
<form action="/order" method="POST" class="mx-auto max-w-screen-xl px-4 2xl:px-0"> <form action="/order" method="POST" class="mx-auto max-w-screen-xl px-4 2xl:px-0">
<input type="hidden" name="confirm-order" value="true" required> <input type="hidden" name="confirm-order" value="true" required>
<div class="mx-auto max-w-3xl"> <div class="mx-auto max-w-3xl">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white sm:text-2xl">Order summary</h2> <h2 class="text-xl font-semibold text-gray-900 sm:text-2xl">Order summary</h2>
<div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 dark:border-gray-700 sm:mt-8"> <div class="mt-6 space-y-4 border-b border-t border-gray-200 py-8 sm:mt-8">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Delivery information</h4> <h4 class="text-lg font-semibold text-gray-900 ">Delivery information</h4>
<dl> <dl>
<dd class="mt-1 text-base font-normal text-gray-500 dark:text-gray-400"> <dd class="mt-1 text-base font-normal text-gray-500">
<p><b>Shipping:</b> {{ .data.shipping.Name }}</p> <p><b>Shipping:</b> {{ .data.shipping.Name }}</p>
{{ if .data.askAddress }} {{ if .data.askAddress }}
<p><b>First Name:</b> {{ .data.order.FirstName }}</p> <p><b>First Name:</b> {{ .data.order.FirstName }}</p>
@@ -25,25 +25,25 @@
</dd> </dd>
</dl> </dl>
<a href="/checkout?shippingMethod={{ .data.order.Shipping }}" data-modal-target="billingInformationModal" data-modal-toggle="billingInformationModal" class="text-base font-medium text-primary-700 hover:underline dark:text-primary-500">Edit</a> <a href="/checkout?shippingMethod={{ .data.order.Shipping }}" data-modal-target="billingInformationModal" data-modal-toggle="billingInformationModal" class="text-base font-medium text-primary-700 hover:underline ">Edit</a>
</div> </div>
<div class="mt-6 sm:mt-8"> <div class="mt-6 sm:mt-8">
<div class="relative overflow-x-auto border-b border-gray-200 dark:border-gray-800"> <div class="relative overflow-x-auto border-b border-gray-200 ">
<table class="w-full text-left font-medium text-gray-900 dark:text-white "> <table class="w-full text-left font-medium text-gray-900 ">
<tbody class="divide-y divide-gray-200 dark:divide-gray-800"> <tbody class="divide-y divide-gray-200 ">
{{ range .data.order.CartItems }} {{ range .data.order.CartItems }}
<tr> <tr>
<td class="whitespace-nowrap py-4"> <td class="whitespace-nowrap py-4">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<a href="#" class="flex items-center aspect-square w-8 h-10 shrink-0"> <a href="#" class="flex items-center aspect-square w-8 h-10 shrink-0">
<img class="h-auto w-full max-h-full dark:hidden" src="/{{ .ShopItem.Image }}" alt="imac image" /> <img class="h-auto w-full max-h-full " src="/{{ .ShopItem.Image }}" alt="imac image" />
</a> </a>
<a href="/shopitems/{{ .ShopItem.ID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a> <a href="/shopitems/{{ .ShopItem.ID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a>
</div> </div>
</td> </td>
<td class="p-4 text-base font-normal text-gray-900 dark:text-white">x{{ .Quantity }}</td> <td class="p-4 text-base font-normal text-gray-900 ">x{{ .Quantity }}</td>
<td class="p-4 text-right text-base font-bold text-gray-900 dark:text-white">{{ .ItemVariant.Price }}€</td> <td class="p-4 text-right text-base font-bold text-gray-900 ">{{ .ItemVariant.Price }}€</td>
</tr> </tr>
{{ end }} {{ end }}
@@ -52,32 +52,32 @@
</div> </div>
<div class="mt-4 space-y-6"> <div class="mt-4 space-y-6">
<h4 class="text-xl font-semibold text-gray-900 dark:text-white">Order summary</h4> <h4 class="text-xl font-semibold text-gray-900 ">Order summary</h4>
<div class="space-y-4"> <div class="space-y-4">
<div class="space-y-2"> <div class="space-y-2">
<dl class="flex items-center justify-between gap-4"> <dl class="flex items-center justify-between gap-4">
<dt class="text-gray-500 dark:text-gray-400">Original price</dt> <dt class="text-gray-500 ">Original price</dt>
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ .data.priceProducts }}€</dd> <dd class="text-base font-medium text-gray-900 ">{{ .data.priceProducts }}€</dd>
</dl> </dl>
<dl class="flex items-center justify-between gap-4"> <dl class="flex items-center justify-between gap-4">
<dt class="text-gray-500 dark:text-gray-400">Shipping</dt> <dt class="text-gray-500 ">Shipping</dt>
<dd class="text-base font-medium text-gray-900 dark:text-white">{{ .data.shipping.Price }}€</dd> <dd class="text-base font-medium text-gray-900 ">{{ .data.shipping.Price }}€</dd>
</dl> </dl>
</div> </div>
<dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 dark:border-gray-700"> <dl class="flex items-center justify-between gap-4 border-t border-gray-200 pt-2 ">
<dt class="text-lg font-bold text-gray-900 dark:text-white">Total</dt> <dt class="text-lg font-bold text-gray-900 ">Total</dt>
<dd class="text-lg font-bold text-gray-900 dark:text-white">{{ .data.priceTotal }}€</dd> <dd class="text-lg font-bold text-gray-900 ">{{ .data.priceTotal }}€</dd>
</dl> </dl>
</div> </div>
<div class="gap-4 sm:flex sm:items-center"> <div class="gap-4 sm:flex sm:items-center">
<button type="button" class="w-full rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"><a href="/">Return to Shopping</a></button> <button type="button" class="w-full rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 "><a href="/">Return to Shopping</a></button>
<button type="submit" class="w-full bg-gray-900 dark:bg-gray-600 rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700">Place binding order</button> <button type="submit" class="w-full bg-gray-900 rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-100">Place binding order</button>
</div> </div>
</div> </div>
</div> </div>

82
views/paperview.html Normal file
View File

@@ -0,0 +1,82 @@
{{ 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/logo-black.png" alt="Your Company">
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Edit Paper</h2>
</div>
<div class="mt-10 ">
<div class="w-full grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
{{ range .data.paper }}
<form action="/paper/{{ .ID }}" method="POST">
<div class="max-w-md mx-auto p-4 m-4 bg-gray-100 flex flex-wrap border rounded shadow-md">
<div class="font-bold text-center mb-4">
{{ .Brand }} - {{ .Name }}: {{ .Size }} {{ .Weight }}g/m2
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Name</label>
<input type="text" id="name" name="name" value="{{ .Name }}" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Brand</label>
<input type="text" id="brand" name="brand" value="{{ .Brand }}" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">DIN Size</label>
<input type="text" id="size" name="size" value="{{ .Size }}" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Weight</label>
<input type="text" id="weight" name="weight" value="{{ .Weight }}" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Price per Sheet</label>
<input type="number" step="0.0001" min="0.0000" id="price" name="price" value="{{ .Price }}" class="w-full flex-grow border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
</div>
<div class="grid grid-cols-2 mt-4 flex flex-wrap gap-2 w-full">
<button type="submit" name="action" value="update" class="bg-blue-500 hover:bg-blue-700 text-white text-sm/6 font-bold py-2 px-4 rounded">Update</button>
<button type="submit" name="action" value="delete" class="bg-red-500 hover:bg-red-700 text-white text-sm/6 font-bold py-2 px-4 rounded">Delete</button>
</div>
</div>
</form>
{{ end }}
</div>
<form action="/paper" method="POST">
<div class="max-w-md mx-auto p-4 flex flex-wrap border rounded shadow-md">
<div class="font-bold text-center mb-4">
Add new Paper
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Name</label>
<input type="text" id="name" name="name" placeholder="name" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Brand</label>
<input type="text" id="brand" name="brand" placeholder="brand" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">DIN Size</label>
<input type="text" id="size" name="size" placeholder="size" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Weight</label>
<input type="text" id="weight" name="weight" placeholder="Weight" class="w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
<div>
<label for="variant-value1" class="block text-sm/6 font-medium text-gray-900">Price per Sheet</label>
<input type="number" step="0.0001" min="0.0000" id="price" name="price" placeholder="price per sheet"
class="w-full flex-grow border border-gray-300 rounded-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
</div>
</div>
<div class="grid grid-cols-4 mt-4 flex flex-wrap gap-2 w-full">
<button type="submit" class="bg-green-500 hover:bg-green-700 text-white text-sm/6 font-bold py-2 px-4 rounded">Add</button>
</div>
</div>
</form>
</div>
{{ template "footer.html" . }}

22
views/printstarted.html Normal file
View File

@@ -0,0 +1,22 @@
{{ template "header.html" . }}
<section class="bg-white py-8 antialiased md:py-16">
<div class="mx-auto max-w-3xl">
<div class="mt-6 sm:mt-8">
<div class="relative overflow-x-auto border-b border-gray-200 ">
<h2 class="title font-manrope font-bold text-4xl leading-10 mb-8 text-center text-black">Print Started
</h2>
<div class="w-full grid grid-cols-1 gap-4 mx-auto p-4 m-4 flex flex-wrap border rounded shadow-md">
<div class="flex flex-col">
{{ template "invoice.html" .data.invoice }}
</div>
<div class="grid grid-cols-2 mt-4 flex flex-wrap gap-2 w-full">
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{{ template "footer.html" . }}

View File

@@ -1,9 +1,10 @@
{{ template "header.html" . }} {{ template "header.html" . }}
<section class="bg-white py-8 antialiased dark:bg-gray-900 md:py-16"> <section class="bg-white py-8 antialiased md:py-16">
<div class="mx-auto max-w-3xl"> <div class="mx-auto max-w-4xl">
<div class="mt-6 sm:mt-8"> <div class="mt-6 sm:mt-8">
<div class="relative overflow-x-auto border-b border-gray-200 dark:border-gray-800"> <div class="relative overflow-x-auto border-b border-gray-200">
<div class="m-4">
<h2 class="title font-manrope font-bold text-4xl leading-10 mb-8 text-center text-black">Zineshop Print <h2 class="title font-manrope font-bold text-4xl leading-10 mb-8 text-center text-black">Zineshop Print
Service Service
</h2> </h2>
@@ -15,48 +16,50 @@
<p class="font-normal text-base leading-7 text-gray-500 text-left mb-5 mt-6"> <p class="font-normal text-base leading-7 text-gray-500 text-left mb-5 mt-6">
<bold>CoverPage</bold>: If selected, the Printer will take Paper from the BypassTray for the first page. For <bold>CoverPage</bold>: If selected, the Printer will take Paper from the BypassTray for the first page. For
example you can put colored paper there to have a nice looking front page, and the rest will be normal paper. example you can put colored paper there to have a nice looking front page, and the rest will be normal paper.
Makue sure you put paper in that tray when selecting this option.<br><br> Make sure you put paper in that tray when selecting this option.<br><br>
Print Order: The Zines will be printed from top to bottom as seen in this list. Print Order: The Zines will be printed from top to bottom as seen in this list.
</p> </p>
</div>
<form action="/print" method="POST"> <form action="/print" method="POST">
<table class="w-full text-left font-medium text-gray-900 dark:text-white">
<tbody class="divide-y divide-gray-200 dark:divide-gray-800">
{{ range .data.cartItems }} {{ range .data.cartItems }}
<tr> <div class="flex items-stretch border rounded m-4 p-2 sm:grid sm:grid-cols-8 gap-4">
<input type="hidden" name="variant-id[]" value="{{ .ItemVariant.ID }}" required> <input type="hidden" name="variant-id[]" value="{{ .ItemVariant.ID }}" required>
<td class="whitespace-nowrap py-4"> <div class="col-span-1 hidden sm:block">
<div class="flex items-center gap-4"> <a href="/shopitems/{{ .ItemVariant.ShopItemID }}" class="flex items-center aspect-square h-full shrink-0">
<a href="/shopitems/{{ .ItemVariant.ShopItemID }}" class="flex items-center aspect-square w-8 h-10 shrink-0"> <img class="h-full w-auto" src="/{{ .ShopItem.Image }}" alt="imac image" />
<img class="h-auto w-full max-h-full dark:hidden" src="/{{ .ShopItem.Image }}" alt="imac image" /> </a>
</a> </div>
<div class="flex flex-col col-span-7 justify-between p-4">
<div class="flex items-center mb-8">
<a href="/shopitems/{{ .ItemVariant.ShopItemID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a> <a href="/shopitems/{{ .ItemVariant.ShopItemID }}" class="hover:underline">{{ .ShopItem.Name }} - {{ .ItemVariant.Name }}</a>
</div> </div>
</td> <div class="flex space-x-2 mb-2">
{{ if .ShopItem.WasPrinted }}
<td class="whitespace-nowrap py-4"> <span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-green-600/20 ring-inset">Tested</span>
<select name="variant-coverpage[]" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> {{ else }}
<option selected value="0">Normal</option> <span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-red-600/10 ring-inset">Not Tested</span>
<option value="1">CoverPage</option> {{ end }}
<select name="variant-papertype[]" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
{{ range $.data.paper }}
<option value="{{ .ID }}">{{ .Brand }} - {{ .Name }}: {{ .Size }} {{ .Weight }}g/m2</option>
{{ end}}
</select> </select>
</td> <select name="variant-coverpage[]" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5">
<td class="whitespace-nowrap py-4"> <option selected value="0">CoverPage: None</option>
Amount: {{ range $.data.paper }}
</td> <option value="{{ .ID }}">{{ .Brand }} - {{ .Name }}: {{ .Size }} {{ .Weight }}g/m2</option>
<td class="p-4 text-right text-base font-bold text-gray-900 dark:text-white"> {{ end}}
<div> </select>
<div class="mt-2"> <input type="number" name="variant-amount[]" value="{{ .Quantity }}" step="1" min="0" required class="text-right border rounded p-2 w-16">
<input type="number" name="variant-amount[]" value="{{ .Quantity }}" step="1" min="0" 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>
</td> </div>
</tr> </div>
{{ end }} {{ end }}
</tbody>
</table>
<div class="max-lg:max-w-lg max-lg:mx-auto"> <div class="max-lg:max-w-lg max-lg:mx-auto">
<p class="font-normal text-base leading-7 text-gray-500 text-center mb-5 mt-6">If CoverPage selected, make sure you put paper in the BypassTray</p> <p class="font-normal text-base leading-7 text-gray-500 text-center mb-5 mt-6">If CoverPage selected, make sure you put paper in the BypassTray</p>

View File

@@ -1,46 +1,46 @@
{{ template "header.html" . }} {{ template "header.html" . }}
<div class="bg-gray-100 dark:bg-gray-800 py-8"> <div class="bg-gray-100 py-8">
<form action="/cart" method="POST"> <form action="/cart" method="POST">
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col md:flex-row -mx-4"> <div class="flex flex-col md:flex-row -mx-4">
<div class="md:flex-1 px-4"> <div class="md:flex-1 px-4">
<div class="rounded-lg bg-gray-300 dark:bg-gray-700 mb-4"> <div class="rounded-lg bg-gray-300 mb-4">
<img class="w-full h-full object-cover" src="/{{ .data.shopItem.Image}}" alt="Product Image"> <img class="w-full h-full object-cover" src="/{{ .data.shopItem.Image}}" alt="Product Image">
</div> </div>
<div class="flex -mx-2 mb-4"> <div class="flex -mx-2 mb-4">
{{ if .loggedIn }} {{ if .loggedIn }}
<input type="hidden" id="{{ .data.shopItem.ID}}" name="ShopItemId" value="{{ .data.shopItem.ID }}"> <input type="hidden" id="{{ .data.shopItem.ID}}" name="ShopItemId" value="{{ .data.shopItem.ID }}">
<div class="w-1/3 px-2"> <div class="w-1/3 px-2">
<button type="submit" class="w-full bg-gray-900 dark:bg-gray-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700">Add to Cart</button> <button type="submit" class="w-full bg-gray-900 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 ">Add to Cart</button>
</div> </div>
{{ end }} {{ end }}
<div class="w-1/3 px-2"> <div class="w-1/3 px-2">
<button type="button" class="w-full bg-blue-900 dark:bg-gray-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700"><a href="/{{ .data.shopItem.Pdf }}">View</a></button> <button type="button" class="w-full bg-blue-900 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 "><a href="/{{ .data.shopItem.Pdf }}">View</a></button>
</div> </div>
{{ if .isAdmin }} {{ if .isAdmin }}
<div class="w-1/3 px-2"> <div class="w-1/3 px-2">
<button type="button" class="w-full bg-blue-900 dark:bg-gray-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700"><a href="{{ .data.shopItem.ID }}/edit">Edit</a></button> <button type="button" class="w-full bg-blue-900 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 "><a href="{{ .data.shopItem.ID }}/edit">Edit</a></button>
</div> </div>
<div class="w-1/3 px-2"> <div class="w-1/3 px-2">
<button type="button" class="w-full bg-red-900 dark:bg-red-600 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 dark:hover:bg-gray-700"><a href="{{ .data.shopItem.ID }}/delete">Delete</a></button> <button type="button" class="w-full bg-red-900 text-white py-2 px-4 rounded-lg font-bold hover:bg-gray-800 "><a href="{{ .data.shopItem.ID }}/delete">Delete</a></button>
</div> </div>
{{ end }} {{ end }}
</div> </div>
</div> </div>
<div class="md:flex-1 px-4"> <div class="md:flex-1 px-4">
<h2 class="text-2xl font-bold text-gray-800 dark:text-white mb-2">{{ .data.shopItem.Name }}</h2> <h2 class="text-2xl font-bold text-gray-800 ">{{ .data.shopItem.Name }}</h2>
<p class="text-gray-600 dark:text-gray-300 text-sm mb-4"> <p class="text-gray-600 text-sm mb-4">
{{ .data.shopItem.Abstract }} {{ .data.shopItem.Abstract }}
</p> </p>
{{ if .loggedIn }} {{ if .loggedIn }}
<div class="flex mb-4"> <div class="flex mb-4">
<label for="ItemVariantId" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"></label> <label for="ItemVariantId" class="block mb-2 text-sm font-medium text-gray-900 "></label>
<select name="ItemVariantId" id="ItemVariantId" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> <select name="ItemVariantId" id="ItemVariantId" required class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 ">
<option selected value="">Choose a variant</option> <option selected value="">Choose a variant</option>
{{ range .data.shopItem.Variants }} {{ range .data.shopItem.Variants }}
<option value="{{ .ID }}">{{ .Name }} - {{ .Price }}€</option> <option value="{{ .ID }}">{{ .Name }} - {{ .Price }}€</option>
@@ -51,27 +51,27 @@
<div class="flex mb-4"> <div class="flex mb-4">
<div class="mr-4"> <div class="mr-4">
<span class="font-bold text-gray-700 dark:text-gray-300">Tags:</span> <span class="font-bold text-gray-700 ">Tags:</span>
<p class="mt-1 text-sm text-gray-500"> <p class="mt-1 text-sm text-gray-500">
{{ range .data.shopItem.Tags }} {{ range .data.shopItem.Tags }}
<a href="/tags/{{ .ID }}"><span class="bg-{{ .Color }}-100 text-{{ .Color }}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-{{ .Color }}-900 dark:text-{{ .Color }}-300">{{ .Name }}</span></a> <a href="/tags/{{ .ID }}"><span class="bg-{{ .Color }}-100 text-{{ .Color }}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm ">{{ .Name }}</span></a>
{{ end }} {{ end }}
</p> </p>
</div> </div>
</div> </div>
<!-- <!--
<div class="mb-4"> <div class="mb-4">
<span class="font-bold text-gray-700 dark:text-gray-300">Select Size:</span> <span class="font-bold text-gray-700 ">Select Size:</span>
<div class="flex items-center mt-2"> <div class="flex items-center mt-2">
<button class="bg-gray-300 dark:bg-gray-700 text-gray-700 dark:text-white py-2 px-4 rounded-full font-bold mr-2 hover:bg-gray-400 dark:hover:bg-gray-600">Black/White</button> <button class="bg-gray-300 text-gray-700 py-2 px-4 rounded-full font-bold mr-2 hover:bg-gray-400 ">Black/White</button>
<button class="bg-gray-300 dark:bg-gray-700 text-gray-700 dark:text-white py-2 px-4 rounded-full font-bold mr-2 hover:bg-gray-400 dark:hover:bg-gray-600">Colored</button> <button class="bg-gray-300 text-gray-700 py-2 px-4 rounded-full font-bold mr-2 hover:bg-gray-400 ">Colored</button>
<button class="bg-gray-300 dark:bg-gray-700 text-gray-700 dark:text-white py-2 px-4 rounded-full font-bold mr-2 hover:bg-gray-400 dark:hover:bg-gray-600">Colored Covering Page</button> <button class="bg-gray-300 text-gray-700 py-2 px-4 rounded-full font-bold mr-2 hover:bg-gray-400 ">Colored Covering Page</button>
</div> </div>
</div> </div>
--> -->
<div> <div>
<span class="font-bold text-gray-700 dark:text-gray-300">Product Description:</span> <span class="font-bold text-gray-700 ">Product Description:</span>
<p class="text-gray-600 dark:text-gray-300 text-sm mt-2"> <p class="text-gray-600 text-sm mt-2">
{{ .data.shopItem.Description }} {{ .data.shopItem.Description }}
</p> </p>
</div> </div>

View File

@@ -21,7 +21,7 @@
<p class="mt-1 text-sm text-gray-500"> <p class="mt-1 text-sm text-gray-500">
{{ range .Tags }} {{ range .Tags }}
<a href="/tags/{{ .ID }}"><span class="bg-{{ .Color }}-100 text-{{ .Color }}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-{{ .Color }}-900 dark:text-{{ .Color }}-300">{{ .Name }}</span></a> <a href="/tags/{{ .ID }}"><span class="bg-{{ .Color }}-100 text-{{ .Color }}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm ">{{ .Name }}</span></a>
{{ end }} {{ end }}
</p> </p>
</div> </div>

View File

@@ -14,7 +14,7 @@
<div class="flex"> <div class="flex">
<span class="bg-{{ .Color }}-100 text-{{ .Color }}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm <span class="bg-{{ .Color }}-100 text-{{ .Color }}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm
dark:bg-{{ .Color }}-900 dark:text-{{ .Color }}-300">Preview</span> ">Preview</span>
<input type="text" id="name" name="name" value="{{ .Name }}" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500"> <input type="text" id="name" name="name" value="{{ .Name }}" class="flex-grow border border-gray-300 rounded-l-md p-2 focus:outline-none focus:ring focus:ring-blue-500">
<select name="color" required> <select name="color" required>
<option selected value="{{ .Color }}">{{ .Color }}</option> <option selected value="{{ .Color }}">{{ .Color }}</option>