frontend: product page
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
parent
8a6be04035
commit
e16172c14a
5 changed files with 116 additions and 13 deletions
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
pb "frontend/genproto"
|
pb "frontend/genproto"
|
||||||
)
|
)
|
||||||
|
@ -54,8 +55,7 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
|
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
|
||||||
currencies, err := fe.getCurrencies(r.Context())
|
currencies, err := fe.getCurrencies(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err) // TODO(ahmetb) use structured logging
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("currencies: %+v", currencies)
|
log.Printf("currencies: %+v", currencies)
|
||||||
|
@ -92,6 +92,47 @@ func (fe *frontendServer) homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fe *frontendServer) productHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
if id == "" {
|
||||||
|
http.Error(w, "product id not specified", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[productHandler] id=%s", id)
|
||||||
|
p, err := fe.getProduct(r.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("could not retrieve product: %+v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currencies, err := fe.getCurrencies(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
price, err := fe.convertCurrency(r.Context(), &pb.Money{
|
||||||
|
Amount: p.GetPriceUsd(),
|
||||||
|
CurrencyCode: defaultCurrency}, currentCurrency(r))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("failed to convert currency: %+v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
product := struct {
|
||||||
|
Item *pb.Product
|
||||||
|
Price *pb.Money
|
||||||
|
}{p, price}
|
||||||
|
|
||||||
|
if err := templates.ExecuteTemplate(w, "product", map[string]interface{}{
|
||||||
|
"user_currency": currentCurrency(r),
|
||||||
|
"currencies": currencies,
|
||||||
|
"product": product,
|
||||||
|
}); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
func (fe *frontendServer) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
|
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
|
||||||
for _, c := range r.Cookies() {
|
for _, c := range r.Cookies() {
|
||||||
|
|
|
@ -64,16 +64,16 @@ func main() {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/", refreshCookies(
|
r.HandleFunc("/", refreshCookies(
|
||||||
ensureSessionID(
|
ensureSessionID(
|
||||||
svc.homeHandler))).
|
svc.homeHandler))).Methods(http.MethodGet, http.MethodHead)
|
||||||
Methods(http.MethodGet, http.MethodHead)
|
r.HandleFunc("/product/{id}", refreshCookies(
|
||||||
|
ensureSessionID(
|
||||||
|
svc.productHandler))).Methods(http.MethodGet, http.MethodHead)
|
||||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/",
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/",
|
||||||
http.FileServer(http.Dir("./static/"))))
|
http.FileServer(http.Dir("./static/"))))
|
||||||
r.HandleFunc("/logout", svc.logoutHandler).
|
r.HandleFunc("/logout", svc.logoutHandler).Methods(http.MethodGet)
|
||||||
Methods(http.MethodGet)
|
|
||||||
r.HandleFunc("/setCurrency", refreshCookies(
|
r.HandleFunc("/setCurrency", refreshCookies(
|
||||||
ensureSessionID(
|
ensureSessionID(
|
||||||
svc.setCurrencyHandler))).
|
svc.setCurrencyHandler))).Methods(http.MethodPost)
|
||||||
Methods(http.MethodPost)
|
|
||||||
log.Printf("starting server on :" + srvPort)
|
log.Printf("starting server on :" + srvPort)
|
||||||
log.Fatal(http.ListenAndServe("localhost:"+srvPort, r))
|
log.Fatal(http.ListenAndServe("localhost:"+srvPort, r))
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,12 @@ func (fe *frontendServer) getProducts(ctx context.Context) ([]*pb.Product, error
|
||||||
return resp.GetProducts(), err
|
return resp.GetProducts(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fe *frontendServer) getProduct(ctx context.Context, id string) (*pb.Product, error) {
|
||||||
|
resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn).
|
||||||
|
GetProduct(ctx, &pb.GetProductRequest{Id: id})
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
func (fe *frontendServer) convertCurrency(ctx context.Context, money *pb.Money, currency string) (*pb.Money, error) {
|
func (fe *frontendServer) convertCurrency(ctx context.Context, money *pb.Money, currency string) (*pb.Money, error) {
|
||||||
if avoidNoopCurrencyConversionRPC && money.GetCurrencyCode() == currency {
|
if avoidNoopCurrencyConversionRPC && money.GetCurrencyCode() == currency {
|
||||||
return money, nil
|
return money, nil
|
||||||
|
|
|
@ -16,22 +16,26 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="album py-5 bg-light">
|
<div class="py-5 bg-light">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{{ range $.products }}
|
{{ range $.products }}
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card mb-4 box-shadow">
|
<div class="card mb-4 box-shadow">
|
||||||
<img class="card-img-top" alt =""
|
<a href="/product/{{.Item.Id}}">
|
||||||
style="width: auto; height: 225px"
|
<img class="card-img-top" alt =""
|
||||||
src="{{.Item.Picture}}">
|
style="width: 100%; height: auto;"
|
||||||
|
src="{{.Item.Picture}}">
|
||||||
|
</a>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">
|
<h5 class="card-title">
|
||||||
{{ .Item.Name }}
|
{{ .Item.Name }}
|
||||||
</h5>
|
</h5>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary">Buy</button>
|
<a href="/product/{{.Item.Id}}">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary">Buy</button>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
{{.Price.CurrencyCode}}
|
{{.Price.CurrencyCode}}
|
||||||
|
|
52
src/frontend/templates/product.html
Normal file
52
src/frontend/templates/product.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{{ define "product" }}
|
||||||
|
{{ template "header" . }}
|
||||||
|
|
||||||
|
<main role="main">
|
||||||
|
<div class="py-5">
|
||||||
|
<div class="container bg-light px-lg-5 py-lg-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-5">
|
||||||
|
<img class="img-fluid" style="width: 100%;"
|
||||||
|
src="{{$.product.Item.Picture}}" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-7">
|
||||||
|
<h2>{{$.product.Item.Name}}</h2>
|
||||||
|
|
||||||
|
<p class="text-muted">
|
||||||
|
{{$.product.Price.CurrencyCode}}
|
||||||
|
{{$.product.Price.Amount.Decimal}}.
|
||||||
|
{{- $.product.Price.Amount.Fractional}}{{- if lt $.product.Price.Amount.Fractional 10}}0{{end}}
|
||||||
|
</p>
|
||||||
|
<hr/>
|
||||||
|
<p>
|
||||||
|
<h6>Product Description:</h6>
|
||||||
|
{{$.product.Item.Description}}
|
||||||
|
</p>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<form method="POST" action="/addToCart" class="form-inline text-muted">
|
||||||
|
<input type="hidden" name="product_id" value="{{$.product.Item.Id}}"/>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<label class="input-group-text" for="quantity">Quantity</label>
|
||||||
|
</div>
|
||||||
|
<select name="quantity" id="quantity" class="custom-select">
|
||||||
|
<option>1</option>
|
||||||
|
<option>2</option>
|
||||||
|
<option>3</option>
|
||||||
|
<option>4</option>
|
||||||
|
<option>5</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-secondary ml-3">Add to Cart</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{ template "footer" . }}
|
||||||
|
{{ end }}
|
Loading…
Reference in a new issue