commit
3ad7aa5514
6 changed files with 540 additions and 0 deletions
@ -0,0 +1,13 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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