satellite/marketingweb: Add Partner Offer Type (#2540)

This commit is contained in:
Faris Huskovic 2019-07-25 18:06:23 -04:00 committed by GitHub
parent e260f9f1cc
commit 2144181c99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 239 additions and 86 deletions

View File

@ -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)

View File

@ -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"},

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

View File

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

View File

@ -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 {

View File

@ -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

View File

@ -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;
}

View File

@ -45,7 +45,7 @@ See LICENSE for copying information. -->
{{end}}
{{template "stopFreeCredit" .}}
<div class="row offer-heading ">
<p class="offer-type">Other&nbsp;Offers</p>
<p class="offer-type">Discontinued&nbsp;Offers</p>
</div>
{{range .FreeCredits.Done}}
<div class="row data-row">

View File

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

View File

@ -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">

View File

@ -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&nbsp;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">&#8734;</div>
<div class="col">2006-01-02</div>
<div class="col">&#8734;</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&nbsp;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 &#183;</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&nbsp;Offers</p>
<p class="offer-type">Discontinued&nbsp;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}}

View File

@ -48,7 +48,7 @@ See LICENSE for copying information. -->
{{end}}
{{template "stopReferralOffer" .}}
<div class="row offer-heading ">
<p class="offer-type">Other&nbsp;Offers</p>
<p class="offer-type">Discontinued&nbsp;Offers</p>
</div>
{{range .ReferralOffers.Done}}
<div class="row data-row">

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