commit
3ad7aa5514
6 changed files with 540 additions and 0 deletions
@ -0,0 +1,13 @@ |
|||||||
|
module terminalscale |
||||||
|
|
||||||
|
go 1.22.2 |
||||||
|
|
||||||
|
require github.com/terminaldotshop/terminal-sdk-go v1.10.0 |
||||||
|
|
||||||
|
require ( |
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect |
||||||
|
github.com/tidwall/gjson v1.18.0 // indirect |
||||||
|
github.com/tidwall/match v1.1.1 // indirect |
||||||
|
github.com/tidwall/pretty v1.2.1 // indirect |
||||||
|
github.com/tidwall/sjson v1.2.5 // indirect |
||||||
|
) |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= |
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= |
||||||
|
github.com/terminaldotshop/terminal-sdk-go v1.10.0 h1:AHmD9ifNd5vJPKYntuvVbxw4OqnSJbgq1FLeEXDQwTY= |
||||||
|
github.com/terminaldotshop/terminal-sdk-go v1.10.0/go.mod h1:28WE2YTqRVLNDpZFiPm4ny3f1hjJuJkf0e2Jwy5Gxys= |
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= |
||||||
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= |
||||||
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= |
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= |
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= |
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= |
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= |
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= |
||||||
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= |
||||||
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= |
||||||
@ -0,0 +1,115 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"github.com/gorilla/websocket" |
||||||
|
"github.com/terminaldotshop/terminal-sdk-go" |
||||||
|
"github.com/terminaldotshop/terminal-sdk-go/option" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
"text/template" |
||||||
|
) |
||||||
|
|
||||||
|
type ProductView struct { |
||||||
|
Name string |
||||||
|
Description string |
||||||
|
Color string |
||||||
|
VariantID string |
||||||
|
} |
||||||
|
|
||||||
|
type VariantView struct { |
||||||
|
Name string |
||||||
|
PriceFormatted string |
||||||
|
} |
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{ |
||||||
|
CheckOrigin: func(r *http.Request) bool { |
||||||
|
return true |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
|
||||||
|
fs := http.FileServer(http.Dir("static")) |
||||||
|
http.Handle("/static/", http.StripPrefix("/static/", fs)) |
||||||
|
http.HandleFunc("/products", getProducts) |
||||||
|
http.HandleFunc("/ws", ws) |
||||||
|
|
||||||
|
client := terminal.NewClient( |
||||||
|
option.WithBearerToken("trm_test_3532f9f1592e704eadbc"), // defaults to os.LookupEnv("TERMINAL_BEARER_TOKEN")
|
||||||
|
option.WithEnvironmentDev(), |
||||||
|
) |
||||||
|
|
||||||
|
response, err := client.Address.List(context.TODO()) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
panic(err.Error()) |
||||||
|
} |
||||||
|
fmt.Printf("%+v\n", response.Data) |
||||||
|
log.Println("✅ Server listening on http://localhost:8080/products") |
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil)) |
||||||
|
} |
||||||
|
|
||||||
|
func ws(w http.ResponseWriter, r *http.Request) { |
||||||
|
conn, err := upgrader.Upgrade(w, r, nil) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Upgrade error:", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
defer conn.Close() |
||||||
|
|
||||||
|
for { |
||||||
|
_, msg, err := conn.ReadMessage() |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Read Errors", err) |
||||||
|
break |
||||||
|
} |
||||||
|
fmt.Printf("Received", msg) |
||||||
|
|
||||||
|
err = conn.WriteMessage(websocket.TextMessage, []byte("Server got: "+string(msg))) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("Write error", err) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func getProducts(w http.ResponseWriter, r *http.Request) { |
||||||
|
tmpl := template.Must(template.ParseFiles("index.html")) |
||||||
|
|
||||||
|
client := terminal.NewClient( |
||||||
|
option.WithBearerToken("trm_test_3532f9f1592e704eadbc"), // defaults to os.LookupEnv("TERMINAL_BEARER_TOKEN")
|
||||||
|
option.WithEnvironmentDev(), // defaults to option.WithEnvironmentProduction()
|
||||||
|
) |
||||||
|
|
||||||
|
products, err := client.Product.List(context.TODO()) |
||||||
|
if err != nil { |
||||||
|
http.Error(w, "Failed to fetch products", http.StatusInternalServerError) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
views := []ProductView{} |
||||||
|
for _, p := range products.Data { |
||||||
|
if !p.Tags.MarketNa || p.Subscription == "required" { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
variantId := "" |
||||||
|
if len(p.Variants) > 0 { |
||||||
|
variantId = p.Variants[0].ID |
||||||
|
} |
||||||
|
views = append(views, ProductView{ |
||||||
|
Name: p.Name, |
||||||
|
Description: p.Description, |
||||||
|
Color: p.Tags.Color, |
||||||
|
VariantID: variantId, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html") |
||||||
|
if err := tmpl.Execute(w, views); err != nil { |
||||||
|
http.Error(w, "Failed to render template", http.StatusInternalServerError) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,75 @@ |
|||||||
|
document.addEventListener("DOMContentLoaded", () => { |
||||||
|
// State
|
||||||
|
let selectedCoffee = { |
||||||
|
id: "espresso", |
||||||
|
name: "Espresso", |
||||||
|
icon: "☕", |
||||||
|
} |
||||||
|
let weight = 0 |
||||||
|
let autoOrder = false |
||||||
|
|
||||||
|
// DOM Elements
|
||||||
|
const coffeeButtons = document.querySelectorAll(".coffee-button") |
||||||
|
const weightDisplay = document.getElementById("weight-display") |
||||||
|
const autoOrderSwitch = document.getElementById("auto-order-switch") |
||||||
|
const activeCoffee = document.querySelector('.coffee-button.active') |
||||||
|
const orderNow = document.querySelector('.order-now') |
||||||
|
|
||||||
|
if (activeCoffee === null) { |
||||||
|
orderNow.disabled = true |
||||||
|
} |
||||||
|
|
||||||
|
// Coffee Selection
|
||||||
|
coffeeButtons.forEach((button) => { |
||||||
|
button.addEventListener("click", function () { |
||||||
|
coffeeButtons.forEach((btn) => { |
||||||
|
btn.classList.remove("active") |
||||||
|
btn.style.backgroundColor = "" |
||||||
|
}) |
||||||
|
|
||||||
|
// Remove active class from all buttons
|
||||||
|
coffeeButtons.forEach((btn) => btn.classList.remove("active")) |
||||||
|
const color = this.dataset.color |
||||||
|
// Add active class to clicked button
|
||||||
|
this.classList.add("active") |
||||||
|
orderNow.disabled = false |
||||||
|
this.style.backgroundColor = color |
||||||
|
|
||||||
|
// Update selected coffee
|
||||||
|
selectedCoffee = { |
||||||
|
id: this.dataset.id, |
||||||
|
name: this.dataset.name, |
||||||
|
icon: this.dataset.icon, |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
// Auto Order Toggle
|
||||||
|
autoOrderSwitch.addEventListener("change", function (event) { |
||||||
|
const hasActiveCoffee = document.querySelector('.coffee-button.active'); |
||||||
|
|
||||||
|
if (!hasActiveCoffee) { |
||||||
|
event.preventDefault(); |
||||||
|
this.checked = !this.checked; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
autoOrder = this.checked |
||||||
|
document.querySelector('.order-now').disabled = this.checked; |
||||||
|
console.log(this.checked) |
||||||
|
}) |
||||||
|
|
||||||
|
// Simulate weight changes
|
||||||
|
weight += 1 |
||||||
|
weightDisplay.textContent =weight + "oz" |
||||||
|
|
||||||
|
const socket = new WebSocket("ws://localhost/ws"); |
||||||
|
|
||||||
|
socket.onOpen = () => { |
||||||
|
console.log('Connected to server'); |
||||||
|
} |
||||||
|
|
||||||
|
socket.onMessage = (event) => { |
||||||
|
console.log(event.data); |
||||||
|
} |
||||||
|
}) |
||||||
@ -0,0 +1,248 @@ |
|||||||
|
* { |
||||||
|
box-sizing: border-box; |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", |
||||||
|
"Helvetica Neue", sans-serif; |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
min-height: 100vh; |
||||||
|
background-color: #000000; |
||||||
|
} |
||||||
|
|
||||||
|
.coffee-interface { |
||||||
|
width: 500px; |
||||||
|
height: 320px; |
||||||
|
background-color: #444; |
||||||
|
border-radius: 8px; |
||||||
|
overflow: hidden; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
||||||
|
/* Fixed width to prevent any resizing */ |
||||||
|
min-width: 500px; |
||||||
|
max-width: 500px; |
||||||
|
} |
||||||
|
|
||||||
|
header { |
||||||
|
background-color: rgb(255,94,0); |
||||||
|
color: white; |
||||||
|
padding: 8px 16px; |
||||||
|
border-top-left-radius: 8px; |
||||||
|
border-top-right-radius: 8px; |
||||||
|
height: 40px; /* Fixed height for header */ |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
|
||||||
|
h1 { |
||||||
|
font-size: 1.125rem; |
||||||
|
font-weight: 700; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.settings-toggle { |
||||||
|
background: none; |
||||||
|
border: none; |
||||||
|
color: white; |
||||||
|
cursor: pointer; |
||||||
|
padding: 5px; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
width: 30px; |
||||||
|
height: 30px; |
||||||
|
border-radius: 50%; |
||||||
|
transition: background-color 0.2s; |
||||||
|
margin-left: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.settings-toggle:hover { |
||||||
|
background-color: rgba(255, 255, 255, 0.2); |
||||||
|
} |
||||||
|
|
||||||
|
.settings-icon { |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
.icon { |
||||||
|
margin-right: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.content { |
||||||
|
display: flex; |
||||||
|
flex: 1; |
||||||
|
gap: 8px; |
||||||
|
padding: 8px; |
||||||
|
height: 280px; /* Fixed height for content area */ |
||||||
|
} |
||||||
|
|
||||||
|
.column { |
||||||
|
width: 242px; /* Exact half of 500px minus padding and gap */ |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.card { |
||||||
|
background-color: white; |
||||||
|
border-radius: 6px; |
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
|
||||||
|
/* Make the auto-order card take up more space */ |
||||||
|
.card-auto-order { |
||||||
|
flex: 2; |
||||||
|
} |
||||||
|
|
||||||
|
.card-content { |
||||||
|
padding: 12px; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
h2 { |
||||||
|
font-size: 0.9rem; |
||||||
|
font-weight: 500; |
||||||
|
margin-bottom: 4px; |
||||||
|
} |
||||||
|
|
||||||
|
.coffee-grid { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
gap: 8px; |
||||||
|
margin-top: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.coffee-button { |
||||||
|
height: 56px; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
padding: 4px; |
||||||
|
border: 1px solid #e2e8f0; |
||||||
|
border-radius: 6px; |
||||||
|
background-color: white; |
||||||
|
cursor: pointer; |
||||||
|
transition: all 0.2s; |
||||||
|
/* Fixed width for buttons */ |
||||||
|
width: 100px; |
||||||
|
} |
||||||
|
|
||||||
|
.coffee-button:hover { |
||||||
|
background-color: #f8fafc; |
||||||
|
} |
||||||
|
|
||||||
|
.coffee-button.active { |
||||||
|
color: white; |
||||||
|
border-color: #000000; |
||||||
|
} |
||||||
|
|
||||||
|
.coffee-icon { |
||||||
|
font-size: 1.25rem; |
||||||
|
} |
||||||
|
|
||||||
|
.coffee-name { |
||||||
|
font-size: 0.75rem; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.weight-container { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
margin-bottom: 6px; |
||||||
|
} |
||||||
|
|
||||||
|
.weight-badge { |
||||||
|
font-size: 1.125rem; |
||||||
|
font-weight: 700; |
||||||
|
padding: 2px 8px; |
||||||
|
border: 1px solid #e2e8f0; |
||||||
|
border-radius: 4px; |
||||||
|
/* Fixed width for weight display to prevent movement */ |
||||||
|
min-width: 70px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.weight-threshold { |
||||||
|
font-size: 1.125rem; |
||||||
|
font-weight: 700; |
||||||
|
padding: 2px 8px; |
||||||
|
border: 1px solid #e2e8f0; |
||||||
|
border-radius: 4px; |
||||||
|
/* Fixed width for weight display to prevent movement */ |
||||||
|
min-width: 70px; |
||||||
|
text-align: center; |
||||||
|
width: 70px; |
||||||
|
} |
||||||
|
|
||||||
|
.auto-order { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.label { |
||||||
|
font-size: 0.9rem; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.sublabel { |
||||||
|
font-size: 0.75rem; |
||||||
|
color: #64748b; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.small-icon { |
||||||
|
font-size: 0.75rem; |
||||||
|
margin-right: 4px; |
||||||
|
} |
||||||
|
|
||||||
|
.switch-container { |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.switch-input { |
||||||
|
height: 0; |
||||||
|
width: 0; |
||||||
|
visibility: hidden; |
||||||
|
position: absolute; |
||||||
|
} |
||||||
|
|
||||||
|
.switch { |
||||||
|
cursor: pointer; |
||||||
|
width: 40px; |
||||||
|
height: 20px; |
||||||
|
background: #e2e8f0; |
||||||
|
display: block; |
||||||
|
border-radius: 100px; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.switch:after { |
||||||
|
content: ""; |
||||||
|
position: absolute; |
||||||
|
top: 2px; |
||||||
|
left: 2px; |
||||||
|
width: 16px; |
||||||
|
height: 16px; |
||||||
|
background: white; |
||||||
|
border-radius: 50%; |
||||||
|
transition: 0.3s; |
||||||
|
} |
||||||
|
|
||||||
|
.switch-input:checked + .switch { |
||||||
|
background: rgb(255, 94, 0); |
||||||
|
} |
||||||
|
|
||||||
|
.switch-input:checked + .switch:after { |
||||||
|
left: calc(100% - 2px); |
||||||
|
transform: translateX(-100%); |
||||||
|
} |
||||||
Loading…
Reference in new issue