satellite/marketingweb: Add Partner Offer Type (#2540)
This commit is contained in:
parent
e260f9f1cc
commit
2144181c99
@ -115,6 +115,8 @@ func (s *Server) parseTemplates() (err error) {
|
||||
filepath.Join(s.templateDir, "partner-offers-modal.html"),
|
||||
filepath.Join(s.templateDir, "stop-free-credit.html"),
|
||||
filepath.Join(s.templateDir, "stop-referral-offer.html"),
|
||||
filepath.Join(s.templateDir, "partner-offers.html"),
|
||||
filepath.Join(s.templateDir, "stop-partner-offer.html"),
|
||||
)
|
||||
|
||||
pageNotFoundFiles := append(s.commonPages(),
|
||||
@ -171,6 +173,9 @@ func (s *Server) CreateOffer(w http.ResponseWriter, req *http.Request) {
|
||||
offer.Type = rewards.Referral
|
||||
case "free-credit":
|
||||
offer.Type = rewards.FreeCredit
|
||||
case "partner-offer":
|
||||
offer.Type = rewards.Partner
|
||||
offer.Name = offer.FormatPartnerName()
|
||||
default:
|
||||
err := errs.New("response status %d : invalid offer type", http.StatusBadRequest)
|
||||
s.serveBadRequest(w, req, err)
|
||||
|
@ -42,7 +42,17 @@ func TestCreateAndStopOffers(t *testing.T) {
|
||||
}, {
|
||||
Path: "/create/free-credit-offer",
|
||||
Values: url.Values{
|
||||
"Name": {"Free Credit Credit"},
|
||||
"Name": {"Free Credit"},
|
||||
"Description": {"desc"},
|
||||
"ExpiresAt": {"2119-06-27"},
|
||||
"InviteeCredit": {"50"},
|
||||
"InviteeCreditDurationDays": {"50"},
|
||||
"RedeemableCap": {"150"},
|
||||
},
|
||||
}, {
|
||||
Path: "/create/partner-offer",
|
||||
Values: url.Values{
|
||||
"Name": {"OSPP003-FileZilla"},
|
||||
"Description": {"desc"},
|
||||
"ExpiresAt": {"2119-06-27"},
|
||||
"InviteeCredit": {"50"},
|
||||
|
67
satellite/rewards/partners.go
Normal file
67
satellite/rewards/partners.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package rewards
|
||||
|
||||
// PartnerInfo contains the name and ID of an Open Source Partner
|
||||
type PartnerInfo struct {
|
||||
ID, Name string
|
||||
}
|
||||
|
||||
// FormattedName returns formatted partner name
|
||||
func (p PartnerInfo) FormattedName() string {
|
||||
return p.ID + "-" + p.Name
|
||||
}
|
||||
|
||||
// Partners contains a list of partners.
|
||||
type Partners map[string]PartnerInfo
|
||||
|
||||
// LoadPartnerInfos returns our current Open Source Partners.
|
||||
func LoadPartnerInfos() Partners {
|
||||
return Partners{
|
||||
"Couchbase": PartnerInfo{
|
||||
Name: "Couchbase",
|
||||
ID: "OSPP001",
|
||||
},
|
||||
"MongoDB": PartnerInfo{
|
||||
Name: "MongoDB",
|
||||
ID: "OSPP002",
|
||||
},
|
||||
"FileZilla": PartnerInfo{
|
||||
Name: "FileZilla",
|
||||
ID: "OSPP003",
|
||||
},
|
||||
"InfluxDB": PartnerInfo{
|
||||
Name: "InfluxDB",
|
||||
ID: "OSPP004",
|
||||
},
|
||||
"Kafka": PartnerInfo{
|
||||
Name: "Kafka",
|
||||
ID: "OSPP005",
|
||||
},
|
||||
"Minio": PartnerInfo{
|
||||
Name: "Minio",
|
||||
ID: "OSPP006",
|
||||
},
|
||||
"Nextcloud": PartnerInfo{
|
||||
Name: "Nextcloud",
|
||||
ID: "OSPP007",
|
||||
},
|
||||
"MariaDB": PartnerInfo{
|
||||
Name: "MariaDB",
|
||||
ID: "OSPP008",
|
||||
},
|
||||
"Plesk": PartnerInfo{
|
||||
Name: "Plesk",
|
||||
ID: "OSPP009",
|
||||
},
|
||||
"Pydio": PartnerInfo{
|
||||
Name: "Pydio",
|
||||
ID: "OSPP010",
|
||||
},
|
||||
"Zenko": PartnerInfo{
|
||||
Name: "Zenko",
|
||||
ID: "OSPP011",
|
||||
},
|
||||
}
|
||||
}
|
@ -40,6 +40,19 @@ type NewOffer struct {
|
||||
Type OfferType
|
||||
}
|
||||
|
||||
// FormatPartnerName formats partner's name into combination of its partnerID and name
|
||||
func (o NewOffer) FormatPartnerName() string {
|
||||
if o.Type != Partner {
|
||||
return o.Name
|
||||
}
|
||||
|
||||
partnerInfo := PartnerInfo{
|
||||
ID: LoadPartnerInfos()[o.Name].ID,
|
||||
Name: o.Name,
|
||||
}
|
||||
return partnerInfo.FormattedName()
|
||||
}
|
||||
|
||||
// UpdateOffer holds fields needed for update an offer
|
||||
type UpdateOffer struct {
|
||||
ID int
|
||||
@ -112,10 +125,20 @@ type OrganizedOffers struct {
|
||||
Done Offers
|
||||
}
|
||||
|
||||
// OpenSourcePartner contains all data for an Open Source Partner.
|
||||
type OpenSourcePartner struct {
|
||||
PartnerInfo
|
||||
PartnerOffers OrganizedOffers
|
||||
}
|
||||
|
||||
// PartnerSet contains a list of Open Source Partners.
|
||||
type PartnerSet []OpenSourcePartner
|
||||
|
||||
// OfferSet provides a separation of marketing offers by type.
|
||||
type OfferSet struct {
|
||||
ReferralOffers OrganizedOffers
|
||||
FreeCredits OrganizedOffers
|
||||
PartnerTables PartnerSet
|
||||
}
|
||||
|
||||
// OrganizeOffersByStatus organizes offers by OfferStatus.
|
||||
@ -138,8 +161,8 @@ func (offers Offers) OrganizeOffersByStatus() OrganizedOffers {
|
||||
// OrganizeOffersByType organizes offers by OfferType.
|
||||
func (offers Offers) OrganizeOffersByType() OfferSet {
|
||||
var (
|
||||
fc, ro Offers
|
||||
offerSet OfferSet
|
||||
fc, ro, p Offers
|
||||
offerSet OfferSet
|
||||
)
|
||||
|
||||
for _, offer := range offers {
|
||||
@ -148,6 +171,8 @@ func (offers Offers) OrganizeOffersByType() OfferSet {
|
||||
fc = append(fc, offer)
|
||||
case Referral:
|
||||
ro = append(ro, offer)
|
||||
case Partner:
|
||||
p = append(p, offer)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@ -155,5 +180,46 @@ func (offers Offers) OrganizeOffersByType() OfferSet {
|
||||
|
||||
offerSet.FreeCredits = fc.OrganizeOffersByStatus()
|
||||
offerSet.ReferralOffers = ro.OrganizeOffersByStatus()
|
||||
offerSet.PartnerTables = organizePartnerData(p)
|
||||
return offerSet
|
||||
}
|
||||
|
||||
// createPartnerSet generates a PartnerSet from the config file.
|
||||
func createPartnerSet() PartnerSet {
|
||||
partners := LoadPartnerInfos()
|
||||
var ps PartnerSet
|
||||
for _, partner := range partners {
|
||||
ps = append(ps, OpenSourcePartner{
|
||||
PartnerInfo: PartnerInfo{
|
||||
Name: partner.Name,
|
||||
ID: partner.ID,
|
||||
},
|
||||
})
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
// matchOffersToPartnerSet assigns offers to the partner they belong to.
|
||||
func matchOffersToPartnerSet(offers Offers, partnerSet PartnerSet) PartnerSet {
|
||||
for i := range partnerSet {
|
||||
var partnerOffersByName Offers
|
||||
|
||||
for _, o := range offers {
|
||||
if o.Name == partnerSet[i].PartnerInfo.FormattedName() {
|
||||
partnerOffersByName = append(partnerOffersByName, o)
|
||||
}
|
||||
}
|
||||
|
||||
partnerSet[i].PartnerOffers = partnerOffersByName.OrganizeOffersByStatus()
|
||||
}
|
||||
|
||||
return partnerSet
|
||||
}
|
||||
|
||||
// organizePartnerData returns a list of Open Source Partners
|
||||
// whose offers have been organized by status, type, and
|
||||
// assigned to the correct partner.
|
||||
func organizePartnerData(offers Offers) PartnerSet {
|
||||
partnerData := matchOffersToPartnerSet(offers, createPartnerSet())
|
||||
return partnerData
|
||||
}
|
||||
|
@ -45,6 +45,18 @@ func TestOffer_Database(t *testing.T) {
|
||||
Status: rewards.Active,
|
||||
Type: rewards.FreeCredit,
|
||||
},
|
||||
{
|
||||
Name: "partner",
|
||||
Description: "test offer 2",
|
||||
AwardCredit: currency.Cents(0),
|
||||
InviteeCredit: currency.Cents(50),
|
||||
AwardCreditDurationDays: 0,
|
||||
InviteeCreditDurationDays: 30,
|
||||
RedeemableCap: 50,
|
||||
ExpiresAt: time.Now().UTC().Add(time.Hour * 1),
|
||||
Status: rewards.Active,
|
||||
Type: rewards.Partner,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range validOffers {
|
||||
|
@ -18,8 +18,9 @@ import (
|
||||
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/mattn/go-sqlite3"
|
||||
"math/rand"
|
||||
|
||||
"github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// Prevent conditional imports from causing build failures
|
||||
|
@ -192,21 +192,4 @@ input{
|
||||
|
||||
.header-row .col-heading{
|
||||
margin-left:-110px;
|
||||
}
|
||||
|
||||
.partner-wrapper,
|
||||
.partners-table{
|
||||
margin-left:-10px;
|
||||
margin-right:0px;
|
||||
padding-right:0px;
|
||||
}
|
||||
|
||||
.partners-table{
|
||||
width:1800px;
|
||||
}
|
||||
|
||||
.partner-wrapper{
|
||||
padding-left:0px;
|
||||
overflow-x: auto;
|
||||
max-width: 1150px;
|
||||
}
|
@ -45,7 +45,7 @@ See LICENSE for copying information. -->
|
||||
{{end}}
|
||||
{{template "stopFreeCredit" .}}
|
||||
<div class="row offer-heading ">
|
||||
<p class="offer-type">Other Offers</p>
|
||||
<p class="offer-type">Discontinued Offers</p>
|
||||
</div>
|
||||
{{range .FreeCredits.Done}}
|
||||
<div class="row data-row">
|
||||
|
@ -15,11 +15,9 @@ See LICENSE for copying information. -->
|
||||
</div>
|
||||
{{template "freeOffers" .}}
|
||||
{{template "freeOffersModal" .}}
|
||||
<div class="row mt-4 mb-4 d-flex justify-content-between pr-4 modal-btn">
|
||||
<h3 class="mt-4">Partner Offers</h3>
|
||||
<button type="button" class="btn btn-primary mt-4" data-toggle="modal" data-target="#partner-offers-modal">+ Create Partner Offer</button>
|
||||
</div>
|
||||
{{template "partnerOffers" .}}
|
||||
{{template "partnerOffersModal" .}}
|
||||
|
||||
{{range .PartnerTables}}
|
||||
{{template "partnerOffers" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
@ -2,7 +2,7 @@
|
||||
See LICENSE for copying information. -->
|
||||
|
||||
{{define "partnerOffersModal"}}
|
||||
<div class="modal fade p-5" id="partner-offers-modal" tabindex="-1" role="dialog" aria-labelledby="partner-offers-modal-lbl" aria-hidden="true">
|
||||
<div class="modal fade p-5" id="partner-offers-modal-{{.Name}}" tabindex="-1" role="dialog" aria-labelledby="partner-offers-modal-lbl" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
@ -12,33 +12,22 @@ See LICENSE for copying information. -->
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="form-row">
|
||||
<form action="/create/partner-offer" method="POST" enctype="application/x-www-form-urlencoded">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<label for="Name">Offer Name</label>
|
||||
<input type="text" class="form-control" name="Name" id="Name" placeholder="May Referral" required>
|
||||
<label for="Name">Partner</label>
|
||||
<input type="text" class="form-control" name="Name" id="Name" value="{{.Name}}" readonly>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label for="Description">Description</label>
|
||||
<input type="text" class="form-control" name="Description" id="Description" placeholder="Our test with $50 for May" required>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label for="ExpiresAt">Credit Exp Date</label>
|
||||
<input type="date" class="form-control" id="ExpiresAt" name="ExpiresAt" placeholder="06/01/19" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<label for="InviteeCredit">Give Credit</label>
|
||||
<input type="number" class="form-control" name="InviteeCredit" id="InviteeCredit" placeholder="$50" required>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label for="InviteeCreditDurationDays">Give Credit Exp.</label>
|
||||
<input type="number" class="form-control" name="InviteeCreditDurationDays" id="InviteeCreditDurationDays" min="1" placeholder="14 days" required>
|
||||
<label for="InviteeCreditDurationDays">Credit Duration</label>
|
||||
<input type="number" class="form-control" id="InviteeCreditDurationDays" name="InviteeCreditDurationDays" placeholder="14" required>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label for="RedeemableCap">Redeemable Capacity</label>
|
||||
<input type="number" class="form-control" name="RedeemableCap" id="RedeemableCap" min="1" placeholder="150 users" required>
|
||||
<div class="form-group">
|
||||
<input type="date" class="invisible form-control" id="ExpiresAt" name="ExpiresAt" value="2119-07-31">
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-1 text-left">
|
||||
|
@ -2,57 +2,52 @@
|
||||
See LICENSE for copying information. -->
|
||||
|
||||
{{define "partnerOffers"}}
|
||||
<div class="partner-wrapper">
|
||||
<div class="offers-table partners-table mt-2 mb-5 container-fluid">
|
||||
|
||||
<div class="row mt-4 mb-4 d-flex justify-content-between pr-4 modal-btn">
|
||||
<h3 class="mt-4">{{.Name}} Offers</h3>
|
||||
<button type="button" class="btn btn-primary mt-4" data-toggle="modal" data-target="#partner-offers-modal-{{.Name}}">+ Create Partner Offer</button>
|
||||
</div>
|
||||
|
||||
{{template "partnerOffersModal" .}}
|
||||
|
||||
<div class="offers-table mt-2 mb-5 container">
|
||||
<div class="row header-row">
|
||||
<div class="col col-heading">Name</div>
|
||||
<div class="col col-heading">Give Credit</div>
|
||||
<div class="col col-heading">Referrals Used</div>
|
||||
<div class="col col-heading">Redeemable Capacity</div>
|
||||
<div class="col col-heading">Created</div>
|
||||
<div class="col col-heading">Expiration</div>
|
||||
<div class="col col-heading">Duration</div>
|
||||
<div class="col col-heading">Status</div>
|
||||
<div class="col col-heading">Link</div>
|
||||
</div><hr>
|
||||
<div class="row offer-heading">
|
||||
<p class="offer-type">Default Offer</p>
|
||||
</div>
|
||||
<div class="row data-row">
|
||||
<div class="col">Default partner offer</div>
|
||||
<div class="col">$30</div>
|
||||
<div class="col">0</div>
|
||||
<div class="col">∞</div>
|
||||
<div class="col">2006-01-02</div>
|
||||
<div class="col">∞</div>
|
||||
<div class="col">Default</div>
|
||||
<div class="col ml-5">https://mars.tardigrade.io/refs/001</div>
|
||||
</div><hr>
|
||||
<div class="row offer-heading ">
|
||||
<p class="offer-type">Current Offer</p>
|
||||
</div>
|
||||
{{$currentOffer := .PartnerOffers.Active}}
|
||||
{{if not $currentOffer.IsEmpty}}
|
||||
<div class="row data-row">
|
||||
<div class="col">current partner offer</div>
|
||||
<div class="col">$30</div>
|
||||
<div class="col">0</div>
|
||||
<div class="col">150</div>
|
||||
<div class="col">2006-01-02</div>
|
||||
<div class="col">2019-11-02</div>
|
||||
<div class="col">Live</div>
|
||||
<div class="col ml-5">https://mars.tardigrade.io/refs/002</div>
|
||||
<div class="col-2">{{$currentOffer.Name}}</div>
|
||||
<div class="col-2">${{$currentOffer.InviteeCredit}}</div>
|
||||
<div class="col-3">{{$currentOffer.InviteeCreditDurationDays}}</div>
|
||||
<div class="col-2 col-offset-1 stop-offer">
|
||||
<span data-toggle="modal" data-target=".stop-partner-offer-modal-{{.Name}}">
|
||||
<strong>Live ·</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-3">https://mars.tardigrade.io/refs/{{$currentOffer.Name}}</div>
|
||||
</div><hr>
|
||||
{{end}}
|
||||
{{template "stopPartnerOffer" .}}
|
||||
<div class="row offer-heading ">
|
||||
<p class="offer-type">Other Offers</p>
|
||||
<p class="offer-type">Discontinued Offers</p>
|
||||
</div>
|
||||
{{range .PartnerOffers.Done}}
|
||||
<div class="row data-row">
|
||||
<div class="col">finished partner offer</div>
|
||||
<div class="col">$30</div>
|
||||
<div class="col">0</div>
|
||||
<div class="col">150</div>
|
||||
<div class="col">2006-01-02</div>
|
||||
<div class="col">2019-11-02</div>
|
||||
<div class="col">Off</div>
|
||||
<div class="col ml-5">https://mars.tardigrade.io/refs/003</div>
|
||||
<div class="col-2">{{.Name}}</div>
|
||||
<div class="col-2">${{.InviteeCredit}}</div>
|
||||
<div class="col-3">{{.InviteeCreditDurationDays}}</div>
|
||||
<div class="col-2 col-offset-1">off</div>
|
||||
<div class="col-3">https://mars.tardigrade.io/refs/{{.Name}}</div>
|
||||
</div><hr>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
@ -48,7 +48,7 @@ See LICENSE for copying information. -->
|
||||
{{end}}
|
||||
{{template "stopReferralOffer" .}}
|
||||
<div class="row offer-heading ">
|
||||
<p class="offer-type">Other Offers</p>
|
||||
<p class="offer-type">Discontinued Offers</p>
|
||||
</div>
|
||||
{{range .ReferralOffers.Done}}
|
||||
<div class="row data-row">
|
||||
|
27
web/marketing/pages/stop-partner-offer.html
Normal file
27
web/marketing/pages/stop-partner-offer.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!-- Copyright (C) 2019 Storj Labs, Inc.
|
||||
See LICENSE for copying information. -->
|
||||
|
||||
{{define "stopPartnerOffer"}}
|
||||
<div class="modal fade stop-partner-offer-modal-{{.Name}} 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">Are you sure you want to discontinue this offer ?</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/{{.PartnerOffers.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