satellite/rewards: nicer offers handling (#2390)
* organize offers * revert changes to go.mod and go.sum * change OfferStatus enums back to original * revert modified auto-gen files * don't render empty row if offers is empty * change return val of ListAll to Offers * fix build * add method to check for empty offer when rendering template * fix typo * fix lint and typos * lean out IsEmpty * dont use named return vals * better clarify offer statuses * change back order of setting offer.Status * lint * satellite/marketingweb: allow disabling rewards (#2392) * implement handler for stop offer endpoint * use proper text and fix data-target for free-credit stop modal
This commit is contained in:
parent
94eeb58b45
commit
0d294103e9
@ -9,6 +9,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
@ -43,30 +44,6 @@ type Server struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// offerSet provides a separation of marketing offers by type.
|
|
||||||
type offerSet struct {
|
|
||||||
ReferralOffers rewards.Offers
|
|
||||||
FreeCredits rewards.Offers
|
|
||||||
}
|
|
||||||
|
|
||||||
// organizeOffers organizes offers by type.
|
|
||||||
func organizeOffers(offers []rewards.Offer) offerSet {
|
|
||||||
var os offerSet
|
|
||||||
for _, offer := range offers {
|
|
||||||
|
|
||||||
switch offer.Type {
|
|
||||||
case rewards.FreeCredit:
|
|
||||||
os.FreeCredits.Set = append(os.FreeCredits.Set, offer)
|
|
||||||
case rewards.Referral:
|
|
||||||
os.ReferralOffers.Set = append(os.ReferralOffers.Set, offer)
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return os
|
|
||||||
}
|
|
||||||
|
|
||||||
// commonPages returns templates that are required for all routes.
|
// commonPages returns templates that are required for all routes.
|
||||||
func (s *Server) commonPages() []string {
|
func (s *Server) commonPages() []string {
|
||||||
return []string{
|
return []string{
|
||||||
@ -93,6 +70,7 @@ func NewServer(logger *zap.Logger, config Config, db rewards.DB, listener net.Li
|
|||||||
mux.HandleFunc("/", s.GetOffers)
|
mux.HandleFunc("/", s.GetOffers)
|
||||||
mux.PathPrefix("/static/").Handler(fs)
|
mux.PathPrefix("/static/").Handler(fs)
|
||||||
mux.HandleFunc("/create/{offer_type}", s.CreateOffer)
|
mux.HandleFunc("/create/{offer_type}", s.CreateOffer)
|
||||||
|
mux.HandleFunc("/stop/{offer_id}", s.StopOffer)
|
||||||
}
|
}
|
||||||
s.server.Handler = mux
|
s.server.Handler = mux
|
||||||
|
|
||||||
@ -119,7 +97,7 @@ func (s *Server) GetOffers(w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.templates.home.ExecuteTemplate(w, "base", organizeOffers(offers)); err != nil {
|
if err := s.templates.home.ExecuteTemplate(w, "base", offers.OrganizeOffersByType()); err != nil {
|
||||||
s.log.Error("failed to execute template", zap.Error(err))
|
s.log.Error("failed to execute template", zap.Error(err))
|
||||||
s.serveInternalError(w, req, err)
|
s.serveInternalError(w, req, err)
|
||||||
}
|
}
|
||||||
@ -133,6 +111,8 @@ func (s *Server) parseTemplates() (err error) {
|
|||||||
filepath.Join(s.templateDir, "referral-offers-modal.html"),
|
filepath.Join(s.templateDir, "referral-offers-modal.html"),
|
||||||
filepath.Join(s.templateDir, "free-offers.html"),
|
filepath.Join(s.templateDir, "free-offers.html"),
|
||||||
filepath.Join(s.templateDir, "free-offers-modal.html"),
|
filepath.Join(s.templateDir, "free-offers-modal.html"),
|
||||||
|
filepath.Join(s.templateDir, "stop-free-credit.html"),
|
||||||
|
filepath.Join(s.templateDir, "stop-referral-offer.html"),
|
||||||
)
|
)
|
||||||
|
|
||||||
pageNotFoundFiles := append(s.commonPages(),
|
pageNotFoundFiles := append(s.commonPages(),
|
||||||
@ -147,7 +127,9 @@ func (s *Server) parseTemplates() (err error) {
|
|||||||
filepath.Join(s.templateDir, "err.html"),
|
filepath.Join(s.templateDir, "err.html"),
|
||||||
)
|
)
|
||||||
|
|
||||||
s.templates.home, err = template.New("landingPage").ParseFiles(homeFiles...)
|
s.templates.home, err = template.New("landingPage").Funcs(template.FuncMap{
|
||||||
|
"isEmpty": rewards.Offer.IsEmpty,
|
||||||
|
}).ParseFiles(homeFiles...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Error.Wrap(err)
|
return Error.Wrap(err)
|
||||||
}
|
}
|
||||||
@ -202,6 +184,24 @@ func (s *Server) CreateOffer(w http.ResponseWriter, req *http.Request) {
|
|||||||
http.Redirect(w, req, "/", http.StatusSeeOther)
|
http.Redirect(w, req, "/", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopOffer expires the current offer and replaces it with the default offer.
|
||||||
|
func (s *Server) StopOffer(w http.ResponseWriter, req *http.Request) {
|
||||||
|
offerID, err := strconv.Atoi(mux.Vars(req)["offer_id"])
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("failed to parse offer id", zap.Error(err))
|
||||||
|
s.serveBadRequest(w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.db.Finish(req.Context(), offerID); err != nil {
|
||||||
|
s.log.Error("failed to stop offer", zap.Error(err))
|
||||||
|
s.serveInternalError(w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, req, "/", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
// serveNotFound handles 404 errors and defaults to 500 if template parsing fails.
|
// serveNotFound handles 404 errors and defaults to 500 if template parsing fails.
|
||||||
func (s *Server) serveNotFound(w http.ResponseWriter, req *http.Request) {
|
func (s *Server) serveNotFound(w http.ResponseWriter, req *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
@ -6,6 +6,7 @@ package marketingweb_test
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -20,7 +21,7 @@ type CreateRequest struct {
|
|||||||
Values url.Values
|
Values url.Values
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateOffer(t *testing.T) {
|
func TestCreateAndStopOffers(t *testing.T) {
|
||||||
testplanet.Run(t, testplanet.Config{
|
testplanet.Run(t, testplanet.Config{
|
||||||
SatelliteCount: 1,
|
SatelliteCount: 1,
|
||||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||||
@ -54,8 +55,10 @@ func TestCreateOffer(t *testing.T) {
|
|||||||
addr := planet.Satellites[0].Marketing.Listener.Addr()
|
addr := planet.Satellites[0].Marketing.Listener.Addr()
|
||||||
|
|
||||||
var group errgroup.Group
|
var group errgroup.Group
|
||||||
for _, offer := range requests {
|
for index, offer := range requests {
|
||||||
o := offer
|
o := offer
|
||||||
|
id := strconv.Itoa(index + 1)
|
||||||
|
|
||||||
group.Go(func() error {
|
group.Go(func() error {
|
||||||
baseURL := "http://" + addr.String()
|
baseURL := "http://" + addr.String()
|
||||||
|
|
||||||
@ -69,6 +72,11 @@ func TestCreateOffer(t *testing.T) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = http.Post(baseURL+"/stop/"+id, "application/x-www-form-urlencoded", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// DB holds information about offer
|
// DB holds information about offer
|
||||||
type DB interface {
|
type DB interface {
|
||||||
ListAll(ctx context.Context) ([]Offer, error)
|
ListAll(ctx context.Context) (Offers, error)
|
||||||
GetCurrentByType(ctx context.Context, offerType OfferType) (*Offer, error)
|
GetCurrentByType(ctx context.Context, offerType OfferType) (*Offer, error)
|
||||||
Create(ctx context.Context, offer *NewOffer) (*Offer, error)
|
Create(ctx context.Context, offer *NewOffer) (*Offer, error)
|
||||||
Redeem(ctx context.Context, offerID int, isDefault bool) error
|
Redeem(ctx context.Context, offerID int, isDefault bool) error
|
||||||
@ -57,16 +57,19 @@ const (
|
|||||||
Referral = OfferType(2)
|
Referral = OfferType(2)
|
||||||
)
|
)
|
||||||
|
|
||||||
// OfferStatus indicates the status of an offer
|
// OfferStatus represents the different stage an offer can have in its life-cycle.
|
||||||
type OfferStatus int
|
type OfferStatus int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Done is a default offer status when an offer is not being used currently
|
|
||||||
|
// Done is the status of an offer that is no longer in use.
|
||||||
Done = OfferStatus(iota)
|
Done = OfferStatus(iota)
|
||||||
// Default is a offer status when an offer is used as a default offer
|
|
||||||
Default
|
// Active is the status of an offer that is currently in use.
|
||||||
// Active is a offer status when an offer is currently being used
|
|
||||||
Active
|
Active
|
||||||
|
|
||||||
|
// Default is the status of an offer when there is no active offer.
|
||||||
|
Default
|
||||||
)
|
)
|
||||||
|
|
||||||
// Offer contains info needed for giving users free credits through different offer programs
|
// Offer contains info needed for giving users free credits through different offer programs
|
||||||
@ -91,53 +94,63 @@ type Offer struct {
|
|||||||
Type OfferType
|
Type OfferType
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDefault evaluates the default status of offers for templates.
|
// IsEmpty evaluates whether or not an on offer is empty
|
||||||
func (o Offer) IsDefault() bool {
|
func (o Offer) IsEmpty() bool {
|
||||||
if o.Status == Default {
|
return o.Name == ""
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCurrent evaluates the current status of offers for templates.
|
// Offers contains a slice of offers.
|
||||||
func (o Offer) IsCurrent() bool {
|
type Offers []Offer
|
||||||
if o.Status == Active {
|
|
||||||
return true
|
// OrganizedOffers contains a list of offers organized by status.
|
||||||
}
|
type OrganizedOffers struct {
|
||||||
return false
|
Active Offer
|
||||||
|
Default Offer
|
||||||
|
Done Offers
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDone evaluates the done status of offers for templates.
|
// OfferSet provides a separation of marketing offers by type.
|
||||||
func (o Offer) IsDone() bool {
|
type OfferSet struct {
|
||||||
if o.Status == Done {
|
ReferralOffers OrganizedOffers
|
||||||
return true
|
FreeCredits OrganizedOffers
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offers holds a set of organized offers.
|
// OrganizeOffersByStatus organizes offers by OfferStatus.
|
||||||
type Offers struct {
|
func (offers Offers) OrganizeOffersByStatus() OrganizedOffers {
|
||||||
Set []Offer
|
var oo OrganizedOffers
|
||||||
}
|
|
||||||
|
|
||||||
// GetCurrentFromSet returns the current offer from an organized set.
|
for _, offer := range offers {
|
||||||
func (offers Offers) GetCurrentFromSet() Offer {
|
switch offer.Status {
|
||||||
var o Offer
|
case Active:
|
||||||
for _, offer := range offers.Set {
|
oo.Active = offer
|
||||||
if offer.IsCurrent() {
|
case Default:
|
||||||
o = offer
|
oo.Default = offer
|
||||||
|
case Done:
|
||||||
|
oo.Done = append(oo.Done, offer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return o
|
return oo
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultFromSet returns the current offer from an organized set.
|
// OrganizeOffersByType organizes offers by OfferType.
|
||||||
func (offers Offers) GetDefaultFromSet() Offer {
|
func (offers Offers) OrganizeOffersByType() OfferSet {
|
||||||
var o Offer
|
var (
|
||||||
for _, offer := range offers.Set {
|
fc, ro Offers
|
||||||
if offer.IsDefault() {
|
offerSet OfferSet
|
||||||
o = offer
|
)
|
||||||
|
|
||||||
|
for _, offer := range offers {
|
||||||
|
switch offer.Type {
|
||||||
|
case FreeCredit:
|
||||||
|
fc = append(fc, offer)
|
||||||
|
case Referral:
|
||||||
|
ro = append(ro, offer)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return o
|
|
||||||
|
offerSet.FreeCredits = fc.OrganizeOffersByStatus()
|
||||||
|
offerSet.ReferralOffers = ro.OrganizeOffersByStatus()
|
||||||
|
return offerSet
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,9 @@ import (
|
|||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
|
||||||
"github.com/mattn/go-sqlite3"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Prevent conditional imports from causing build failures
|
// Prevent conditional imports from causing build failures
|
||||||
|
@ -1030,7 +1030,7 @@ func (m *lockedRewards) GetCurrentByType(ctx context.Context, offerType rewards.
|
|||||||
return m.db.GetCurrentByType(ctx, offerType)
|
return m.db.GetCurrentByType(ctx, offerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *lockedRewards) ListAll(ctx context.Context) ([]rewards.Offer, error) {
|
func (m *lockedRewards) ListAll(ctx context.Context) (rewards.Offers, error) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
return m.db.ListAll(ctx)
|
return m.db.ListAll(ctx)
|
||||||
|
@ -25,7 +25,7 @@ type offersDB struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListAll returns all offersDB from the db
|
// ListAll returns all offersDB from the db
|
||||||
func (db *offersDB) ListAll(ctx context.Context) ([]rewards.Offer, error) {
|
func (db *offersDB) ListAll(ctx context.Context) (rewards.Offers, error) {
|
||||||
offersDbx, err := db.db.All_Offer(ctx)
|
offersDbx, err := db.db.All_Offer(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, offerErr.Wrap(err)
|
return nil, offerErr.Wrap(err)
|
||||||
@ -153,7 +153,7 @@ func (db *offersDB) Finish(ctx context.Context, oID int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func offersFromDBX(offersDbx []*dbx.Offer) ([]rewards.Offer, error) {
|
func offersFromDBX(offersDbx []*dbx.Offer) (rewards.Offers, error) {
|
||||||
var offers []rewards.Offer
|
var offers []rewards.Offer
|
||||||
errList := new(errs.Group)
|
errList := new(errs.Group)
|
||||||
|
|
||||||
|
@ -181,4 +181,8 @@ input{
|
|||||||
.edit-offer:hover{
|
.edit-offer:hover{
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: #656565;
|
color: #656565;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-offer{
|
||||||
|
cursor:pointer;
|
||||||
}
|
}
|
BIN
web/marketing/img/warning.png
Normal file
BIN
web/marketing/img/warning.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -13,9 +13,10 @@ See LICENSE for copying information. -->
|
|||||||
<div class="col col-heading">Status</div>
|
<div class="col col-heading">Status</div>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
<div class="row offer-heading ">
|
<div class="row offer-heading ">
|
||||||
<p class="offer-type">default offer</p>
|
<p class="offer-type">Default Offer</p>
|
||||||
</div>
|
</div>
|
||||||
{{$defaultOffer := .FreeCredits.GetDefaultFromSet}}
|
{{$defaultOffer := .FreeCredits.Default}}
|
||||||
|
{{if not $defaultOffer.IsEmpty}}
|
||||||
<div class="row data-row">
|
<div class="row data-row">
|
||||||
<div class="col ml-3">{{$defaultOffer.Name}}</div>
|
<div class="col ml-3">{{$defaultOffer.Name}}</div>
|
||||||
<div class="col">${{$defaultOffer.AwardCredit}}</div>
|
<div class="col">${{$defaultOffer.AwardCredit}}</div>
|
||||||
@ -25,11 +26,12 @@ See LICENSE for copying information. -->
|
|||||||
<div class="col">{{printf "%.10s" $defaultOffer.ExpiresAt}}</div>
|
<div class="col">{{printf "%.10s" $defaultOffer.ExpiresAt}}</div>
|
||||||
<div class="col"></div>
|
<div class="col"></div>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
|
{{end}}
|
||||||
<div class="row offer-heading ">
|
<div class="row offer-heading ">
|
||||||
<p class="offer-type">current offer</p>
|
<p class="offer-type">Current Offer</p>
|
||||||
</div>
|
</div>
|
||||||
{{if gt (len .FreeCredits.Set) 0}}
|
{{$currentOffer := .FreeCredits.Active}}
|
||||||
{{$currentOffer := .FreeCredits.GetCurrentFromSet}}
|
{{if not $currentOffer.IsEmpty}}
|
||||||
<div class="row data-row">
|
<div class="row data-row">
|
||||||
<div class="col ml-3">{{$currentOffer.Name}}</div>
|
<div class="col ml-3">{{$currentOffer.Name}}</div>
|
||||||
<div class="col">${{$currentOffer.AwardCredit}}</div>
|
<div class="col">${{$currentOffer.AwardCredit}}</div>
|
||||||
@ -38,30 +40,26 @@ See LICENSE for copying information. -->
|
|||||||
<div class="col">{{printf "%.10s" $currentOffer.CreatedAt}}</div>
|
<div class="col">{{printf "%.10s" $currentOffer.CreatedAt}}</div>
|
||||||
<div class="col">{{printf "%.10s" $currentOffer.ExpiresAt}}</div>
|
<div class="col">{{printf "%.10s" $currentOffer.ExpiresAt}}</div>
|
||||||
<div class="col stop-offer">
|
<div class="col stop-offer">
|
||||||
<span data-toggle="modal" data-target=".stop-referral-offer-modal">
|
<span data-toggle="modal" data-target=".stop-free-credit-modal">
|
||||||
<strong>Live ·</strong>
|
<strong>Live ·</strong>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="row offer-heading ">
|
{{template "stopFreeCredit" .}}
|
||||||
<p class="offer-type">other offers</p>
|
<div class="row offer-heading ">
|
||||||
</div>
|
<p class="offer-type">Other Offers</p>
|
||||||
{{if gt (len .FreeCredits.Set) 0}}
|
</div>
|
||||||
{{range .FreeCredits.Set}}
|
{{range .FreeCredits.Done}}
|
||||||
{{$offer := .}}
|
<div class="row data-row">
|
||||||
{{if $offer.IsDone}}
|
<div class="col ml-3">{{.Name}}</div>
|
||||||
<div class="row data-row">
|
<div class="col">${{.AwardCredit}}</div>
|
||||||
<div class="col ml-3">{{$offer.Name}}</div>
|
<div class="col">{{.NumRedeemed}}</div>
|
||||||
<div class="col">${{$offer.AwardCredit}}</div>
|
<div class="col">{{.RedeemableCap}}</div>
|
||||||
<div class="col">{{$offer.NumRedeemed}}</div>
|
<div class="col">{{printf "%.10s" .CreatedAt}}</div>
|
||||||
<div class="col">{{$offer.RedeemableCap}}</div>
|
<div class="col">{{printf "%.10s" .ExpiresAt}}</div>
|
||||||
<div class="col">{{printf "%.10s" $offer.CreatedAt}}</div>
|
<div class="col">off</div>
|
||||||
<div class="col">{{printf "%.10s" $offer.ExpiresAt}}</div>
|
</div><hr>
|
||||||
<div class="col">off</div>
|
|
||||||
</div><hr>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
@ -16,7 +16,8 @@ See LICENSE for copying information. -->
|
|||||||
<div class="row offer-heading ">
|
<div class="row offer-heading ">
|
||||||
<p class="offer-type">Default Offer</p>
|
<p class="offer-type">Default Offer</p>
|
||||||
</div>
|
</div>
|
||||||
{{$defaultOffer := .ReferralOffers.GetDefaultFromSet}}
|
{{$defaultOffer := .ReferralOffers.Default}}
|
||||||
|
{{if not $defaultOffer.IsEmpty}}
|
||||||
<div class="row data-row">
|
<div class="row data-row">
|
||||||
<div class="col ml-3">{{$defaultOffer.Name}}</div>
|
<div class="col ml-3">{{$defaultOffer.Name}}</div>
|
||||||
<div class="col">${{$defaultOffer.InviteeCredit}}</div>
|
<div class="col">${{$defaultOffer.InviteeCredit}}</div>
|
||||||
@ -27,11 +28,12 @@ See LICENSE for copying information. -->
|
|||||||
<div class="col">∞</div>
|
<div class="col">∞</div>
|
||||||
<div class="col"></div>
|
<div class="col"></div>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
|
{{end}}
|
||||||
<div class="row offer-heading ">
|
<div class="row offer-heading ">
|
||||||
<p class="offer-type">Current Offer</p>
|
<p class="offer-type">Current Offer</p>
|
||||||
</div>
|
</div>
|
||||||
{{if gt (len .ReferralOffers.Set) 0}}
|
{{$currentOffer := .ReferralOffers.Active}}
|
||||||
{{$currentOffer := .ReferralOffers.GetCurrentFromSet}}
|
{{if not $currentOffer.IsEmpty}}
|
||||||
<div class="row data-row">
|
<div class="row data-row">
|
||||||
<div class="col ml-3">{{$currentOffer.Name}}</div>
|
<div class="col ml-3">{{$currentOffer.Name}}</div>
|
||||||
<div class="col">${{$currentOffer.InviteeCredit}}</div>
|
<div class="col">${{$currentOffer.InviteeCredit}}</div>
|
||||||
@ -47,25 +49,21 @@ See LICENSE for copying information. -->
|
|||||||
</div>
|
</div>
|
||||||
</div><hr>
|
</div><hr>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{template "stopReferralOffer" .}}
|
||||||
<div class="row offer-heading ">
|
<div class="row offer-heading ">
|
||||||
<p class="offer-type">Other Offers</p>
|
<p class="offer-type">Other Offers</p>
|
||||||
</div>
|
</div>
|
||||||
{{if gt (len .ReferralOffers.Set) 0}}
|
{{range .ReferralOffers.Done}}
|
||||||
{{range .ReferralOffers.Set}}
|
<div class="row data-row">
|
||||||
{{$offer := .}}
|
<div class="col ml-3">{{.Name}}</div>
|
||||||
{{if $offer.IsDone}}
|
<div class="col">${{.InviteeCredit}}</div>
|
||||||
<div class="row data-row">
|
<div class="col">${{.AwardCredit}}</div>
|
||||||
<div class="col ml-3">{{$offer.Name}}</div>
|
<div class="col">{{.NumRedeemed}}</div>
|
||||||
<div class="col">${{$offer.InviteeCredit}}</div>
|
<div class="col">{{.RedeemableCap}}</div>
|
||||||
<div class="col">${{$offer.AwardCredit}}</div>
|
<div class="col">{{printf "%.10s" .CreatedAt}}</div>
|
||||||
<div class="col">{{.NumRedeemed}}</div>
|
<div class="col">{{printf "%.10s" .ExpiresAt}}</div>
|
||||||
<div class="col">{{.RedeemableCap}}</div>
|
<div class="col">off</div>
|
||||||
<div class="col">{{printf "%.10s" .CreatedAt}}</div>
|
</div><hr>
|
||||||
<div class="col">{{printf "%.10s" .ExpiresAt}}</div>
|
|
||||||
<div class="col">off</div>
|
|
||||||
</div><hr>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
30
web/marketing/pages/stop-free-credit.html
Normal file
30
web/marketing/pages/stop-free-credit.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!-- Copyright (C) 2019 Storj Labs, Inc.
|
||||||
|
See LICENSE for copying information. -->
|
||||||
|
|
||||||
|
{{define "stopFreeCredit"}}
|
||||||
|
<div class="modal fade stop-free-credit-modal mt-5" tabindex="-1" role="dialog" aria-labelledby="confirm" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="row d-flex justify-content-between">
|
||||||
|
<img class="mt-3 ml-5" src="/static/img/warning.png" height="30" width="30"/>
|
||||||
|
<p class="mt-3">Replace
|
||||||
|
<strong>{{.FreeCredits.Active.Name}}</strong> with
|
||||||
|
<strong>{{.FreeCredits.Default.Name}}</strong> ?
|
||||||
|
</p>
|
||||||
|
<div class="mt-2 mb-2 d-flex">
|
||||||
|
<a href="/">
|
||||||
|
<button class="btn btn-outline-secondary mr-1">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<form method="POST" action="/stop/{{.FreeCredits.Active.ID}}">
|
||||||
|
<button class="btn btn-primary mr-4" type="submit">
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
30
web/marketing/pages/stop-referral-offer.html
Normal file
30
web/marketing/pages/stop-referral-offer.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!-- Copyright (C) 2019 Storj Labs, Inc.
|
||||||
|
See LICENSE for copying information. -->
|
||||||
|
|
||||||
|
{{define "stopReferralOffer"}}
|
||||||
|
<div class="modal fade stop-referral-offer-modal mt-5" tabindex="-1" role="dialog" aria-labelledby="confirm" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="row d-flex justify-content-between">
|
||||||
|
<img class="mt-3 ml-5" src="/static/img/warning.png" height="30" width="30"/>
|
||||||
|
<p class="mt-3">Replace
|
||||||
|
<strong>{{.ReferralOffers.Active.Name}}</strong> with
|
||||||
|
<strong>{{.ReferralOffers.Default.Name}}</strong> ?
|
||||||
|
</p>
|
||||||
|
<div class="mt-2 mb-2 d-flex">
|
||||||
|
<a href="/">
|
||||||
|
<button class="btn btn-outline-secondary mr-1">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<form method="POST" action="/stop/{{.ReferralOffers.Active.ID}}">
|
||||||
|
<button class="btn btn-primary mr-4" type="submit">
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
Loading…
Reference in New Issue
Block a user