frontend: product page

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
Ahmet Alp Balkan 2018-06-25 22:47:33 -07:00
parent 8a6be04035
commit e16172c14a
5 changed files with 116 additions and 13 deletions

View file

@ -8,6 +8,7 @@ import (
"net/http"
"github.com/google/uuid"
"github.com/gorilla/mux"
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{}))
currencies, err := fe.getCurrencies(r.Context())
if err != nil {
log.Println(err) // TODO(ahmetb) use structured logging
w.WriteHeader(http.StatusInternalServerError)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
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) {
log.Printf("[home] session_id=%+v", r.Context().Value(ctxKeySessionID{}))
for _, c := range r.Cookies() {

View file

@ -64,16 +64,16 @@ func main() {
r := mux.NewRouter()
r.HandleFunc("/", refreshCookies(
ensureSessionID(
svc.homeHandler))).
Methods(http.MethodGet, http.MethodHead)
svc.homeHandler))).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/",
http.FileServer(http.Dir("./static/"))))
r.HandleFunc("/logout", svc.logoutHandler).
Methods(http.MethodGet)
r.HandleFunc("/logout", svc.logoutHandler).Methods(http.MethodGet)
r.HandleFunc("/setCurrency", refreshCookies(
ensureSessionID(
svc.setCurrencyHandler))).
Methods(http.MethodPost)
svc.setCurrencyHandler))).Methods(http.MethodPost)
log.Printf("starting server on :" + srvPort)
log.Fatal(http.ListenAndServe("localhost:"+srvPort, r))
}

View file

@ -31,6 +31,12 @@ func (fe *frontendServer) getProducts(ctx context.Context) ([]*pb.Product, error
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) {
if avoidNoopCurrencyConversionRPC && money.GetCurrencyCode() == currency {
return money, nil

View file

@ -16,22 +16,26 @@
</div>
</section>
<div class="album py-5 bg-light">
<div class="py-5 bg-light">
<div class="container">
<div class="row">
{{ range $.products }}
<div class="col-md-4">
<div class="card mb-4 box-shadow">
<img class="card-img-top" alt =""
style="width: auto; height: 225px"
src="{{.Item.Picture}}">
<a href="/product/{{.Item.Id}}">
<img class="card-img-top" alt =""
style="width: 100%; height: auto;"
src="{{.Item.Picture}}">
</a>
<div class="card-body">
<h5 class="card-title">
{{ .Item.Name }}
</h5>
<div class="d-flex justify-content-between align-items-center">
<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>
<small class="text-muted">
{{.Price.CurrencyCode}}

View 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 }}