mirror of
https://github.com/dnote/dnote
synced 2026-03-14 14:35:50 +01:00
Implement checkout flow (#187)
* Implement new payment flow * Update README * Fix style
This commit is contained in:
parent
813040ea21
commit
3dff87e5a2
127 changed files with 1777 additions and 720 deletions
26
README.md
26
README.md
|
|
@ -1,39 +1,35 @@
|
|||
# Dnote
|
||||
|
||||
A simple, encrypted personal knowledge base.
|
||||
A simple notebook for developers.
|
||||
|
||||
## What It Does
|
||||
|
||||
Instantly capture your microlessons and get automatic reminders for spaced repetition. Because:
|
||||
|
||||
* we forget exponentially unless we write down what we learn and come back.
|
||||
* ideas cannot be grokked unless we can put them down in clear words.
|
||||
Write technical notes and neatly organize them into books.
|
||||
|
||||
## How to Use
|
||||
|
||||
Use the following to keep Dnote handy.
|
||||
You can use Dnote in a command line, web browser, or in an IDE.
|
||||
|
||||
- [CLI](https://github.com/dnote/dnote/tree/master/cli)
|
||||
- [Web](https://dnote.io)
|
||||
- [Browser extension](https://github.com/dnote/browser-extension)
|
||||
- [Atom](https://github.com/dnote/dnote-atom)
|
||||
|
||||
It is designed to minimize switching environment.
|
||||
|
||||
### User Stories
|
||||
## Privacy
|
||||
|
||||
- [How I Built a Personal Knowledge Base for Myself](https://dnote.io/blog/how-i-built-personal-knowledge-base-for-myself/)
|
||||
- [I Wrote Down Everything I Learned While Programming for a Month](https://dnote.io/blog/writing-everything-i-learn-coding-for-a-month/)
|
||||
|
||||
## Security
|
||||
|
||||
Dnote is end-to-end encrypted and respects your privacy. It does not track you.
|
||||
|
||||
When syncing, your data is encrypted with AES-256. Dnote server has zero knowledge about note contents.
|
||||
Dnote is end-to-end encrypted with AES-256 to respect your privacy. It does not track you.
|
||||
|
||||
## Self-host
|
||||
|
||||
Instructions are coming soon.
|
||||
|
||||
## User Stories
|
||||
|
||||
- [How I Built a Personal Knowledge Base for Myself](https://dnote.io/blog/how-i-built-personal-knowledge-base-for-myself/)
|
||||
- [I Wrote Down Everything I Learned While Programming for a Month](https://dnote.io/blog/writing-everything-i-learn-coding-for-a-month/)
|
||||
|
||||
## Links
|
||||
|
||||
- [Dnote](https://dnote.io)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ A simple command line interface for Dnote.
|
|||
|
||||

|
||||
|
||||
It is Designed to minimize context switching for taking notes.
|
||||
It is Designed to minimize environment switching for taking notes.
|
||||
|
||||
## Install
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ done
|
|||
|
||||
serverPath="$GOPATH"/src/github.com/dnote/dnote/server
|
||||
webPath="$GOPATH"/src/github.com/dnote/dnote/web
|
||||
agplFiles=$(find "$serverPath" "$webPath" -type f \( -name "*.go" -o -name "*.js" \) ! -path "**/vendor/*" ! -path "**/node_modules/*")
|
||||
agplFiles=$(find "$serverPath" "$webPath" -type f \( -name "*.go" -o -name "*.js" -o -name "*.scss" \) ! -path "**/vendor/*" ! -path "**/node_modules/*")
|
||||
|
||||
for file in $agplFiles; do
|
||||
remove_notice "$file"
|
||||
|
|
|
|||
73
server/Gopkg.lock
generated
73
server/Gopkg.lock
generated
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9a88883f474d09f1da61894cd8115c7f33988d6941e4f6236324c777aaff8f2c"
|
||||
digest = "1:439bfb51db599cd80766736b93b3d10e9314361197ada0b1209a51627a00ccd5"
|
||||
name = "github.com/PuerkitoBio/goquery"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "dc2ec5c7ca4d9aae063b79b9f581dd3ea6afd2b2"
|
||||
version = "v1.4.1"
|
||||
revision = "2d2796f41742ece03e8086188fa4db16a3a0b458"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e3726ad6f38f710e84c8dcd0e830014de6eaeea81f28d91ae898afecc078479a"
|
||||
|
|
@ -30,12 +30,12 @@
|
|||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3dd078fda7500c341bc26cfbc6c6a34614f295a2457149fc1045cab767cbcf18"
|
||||
digest = "1:529d738b7976c3848cae5cf3a8036440166835e389c1f617af701eeb12a0518d"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
pruneopts = ""
|
||||
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
|
||||
version = "v1.2.0"
|
||||
revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30"
|
||||
version = "v1.3.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:dbbeb8ddb0be949954c8157ee8439c2adfd8dc1c9510eb44a6e58cb68c3dce28"
|
||||
|
|
@ -54,12 +54,12 @@
|
|||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c2c8666b4836c81a1d247bdf21c6a6fc1ab586538ab56f74437c2e0df5c375e1"
|
||||
digest = "1:ec5262e6c65f9fcedc01d7789ee4e18ad429dde822cfd50e57e3fd77c4d70c73"
|
||||
name = "github.com/gorilla/mux"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
|
||||
version = "v1.6.2"
|
||||
revision = "ed099d42384823742bba0bf9a72b53b55c9e2e38"
|
||||
version = "v1.7.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:51a1d17ba9fe6648d9fdd5b822baa616d6ec7f28c8669c2091126d206796b5ae"
|
||||
|
|
@ -78,12 +78,12 @@
|
|||
version = "v1.1.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d269638dbd514822446c3c818b6389c880058af79ec54d97731818b34fe66921"
|
||||
digest = "1:ac0cf20506076f1243a12c66052b862988915b1cd41881987addf0da946077c6"
|
||||
name = "github.com/jinzhu/gorm"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "6ed508ec6a4ecb3531899a69cbc746ccf65a4166"
|
||||
version = "v1.9.1"
|
||||
revision = "b7156195f7f3415f97c20abbd6aff894b847fee8"
|
||||
version = "v1.9.8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
|
@ -102,18 +102,19 @@
|
|||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:29145d7af4adafd72a79df5e41456ac9e232d5a28c1cd4dacf3ff008a217fc10"
|
||||
digest = "1:a65d93f562c7d66d8db6f0c5df8ade208e0f42d9a9626924896697351ca161b3"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [
|
||||
".",
|
||||
"oid",
|
||||
"scram",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "4ded0e9383f75c197b3a2aaa6d590ac52df6fd79"
|
||||
version = "v1.0.0"
|
||||
revision = "bc6a3c0594130b1e34005880bc600b6d3f49fa7f"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8f5ef35477c8db09c49cc8f702dcc85bafcfc4e2f14b63975b587a589ce2303c"
|
||||
digest = "1:38933a01adb848876d291f1d1415c942f131b859dd3910936ddddf72056f098f"
|
||||
name = "github.com/markbates/goth"
|
||||
packages = [
|
||||
".",
|
||||
|
|
@ -122,16 +123,16 @@
|
|||
"providers/gplus",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "f9c6649ab984d6ea71ef1e13b7b1cdffcf4592d3"
|
||||
version = "v1.46.1"
|
||||
revision = "a3986c89570720dfc7c4b263e46a6d2c233d74cd"
|
||||
version = "v1.52.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
|
||||
digest = "1:1d7e1867c49a6dd9856598ef7c3123604ea3daabf5b83f303ff457bcbc410b1d"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
||||
version = "v0.8.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6ab228f39a195cb1dab3564a0f27dc24a52bb3a19fa58dd2967f1e7b2482d82b"
|
||||
|
|
@ -150,23 +151,24 @@
|
|||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:424ed555c578339dc6d8ade516c6a2a6d3f461081aab905e955d240a2baed259"
|
||||
digest = "1:f572b2f945e18823b755499b67aa2f9cac9404a02452f740a0dd39c8266fac62"
|
||||
name = "github.com/stripe/stripe-go"
|
||||
packages = [
|
||||
".",
|
||||
"card",
|
||||
"customer",
|
||||
"form",
|
||||
"paymentsource",
|
||||
"sub",
|
||||
"webhook",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "2288bfd59470171d308a3b823b0ff9722a81bcd7"
|
||||
version = "v49.2.0"
|
||||
revision = "557f3da8db5392aca3d47980cbee64aef9792c65"
|
||||
version = "v60.17.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d263c39e9144265037f7473751f48b4193df3efe4763cc72e925c13d5bdbe551"
|
||||
digest = "1:5b3e9450868bcf9ecbca2b01ac04f142255b5744d89ec97e1ceedf57d4522645"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"bcrypt",
|
||||
|
|
@ -174,11 +176,11 @@
|
|||
"pbkdf2",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "7c1a557ab941a71c619514f229f0b27ccb0c27cf"
|
||||
revision = "22d7a77e9e5f409e934ed268692e56707cd169e5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3a3c1b660248c0ec25f00cfb9c6526bd5b0ede4c8bfa2ed56a3f5e7e9d0a19cd"
|
||||
digest = "1:aa38821ad1406a84f9577465ef53e56ca4d90745a710c753190bf7792d726c82"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
|
|
@ -187,29 +189,29 @@
|
|||
"html/atom",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "146acd28ed5894421fb5aac80ca93bc1b1f46f87"
|
||||
revision = "3ec19112720433827bbce8be9342797f5a6aaaf9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:235cb00e80dcf85b78a24be4bbe6c827fb28613b84037a9d524084308a849d91"
|
||||
digest = "1:348696484a568aa816b0aa29d4924afa1a4e5492e29a003eaf365f650a53c7b4"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "c57b0facaced709681d9f90397429b9430a74754"
|
||||
revision = "9f3314589c9a9136388751d9adae6b0ed400978a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:55a681cb66f28755765fa5fa5104cbd8dc85c55c02d206f9f89566451e3fe1aa"
|
||||
digest = "1:9522af4be529c108010f95b05f1022cb872f2b9ff8b101080f554245673466e1"
|
||||
name = "golang.org/x/time"
|
||||
packages = ["rate"]
|
||||
pruneopts = ""
|
||||
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
|
||||
revision = "9d24e82272b4f38b78bc8cff74fa936d31ccd8ef"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8c432632a230496c35a15cfdf441436f04c90e724ad99c8463ef0c82bbe93edb"
|
||||
digest = "1:01b9b21ce3c29e95c6226188ab77233e59f4e397262a078cd6f248405b86dda7"
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [
|
||||
"internal",
|
||||
|
|
@ -221,8 +223,8 @@
|
|||
"urlfetch",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "ae0ab99deb4dc413a2b4bd6c8bdd0eb67f1e4d06"
|
||||
version = "v1.2.0"
|
||||
revision = "4c25cacc810c02874000e4f7071286a8e96b2515"
|
||||
version = "v1.6.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "v3"
|
||||
|
|
@ -261,6 +263,7 @@
|
|||
"github.com/stripe/stripe-go",
|
||||
"github.com/stripe/stripe-go/card",
|
||||
"github.com/stripe/stripe-go/customer",
|
||||
"github.com/stripe/stripe-go/paymentsource",
|
||||
"github.com/stripe/stripe-go/sub",
|
||||
"github.com/stripe/stripe-go/webhook",
|
||||
"golang.org/x/crypto/bcrypt",
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
[[constraint]]
|
||||
name = "github.com/stripe/stripe-go"
|
||||
version = "49.1.0"
|
||||
version = "60.17.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/gomail.v2"
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ package handlers
|
|||
import (
|
||||
crand "crypto/rand"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/dnote/dnote/server/api/logger"
|
||||
"github.com/dnote/dnote/server/database"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -104,3 +106,11 @@ func getClientType(origin string) string {
|
|||
|
||||
return "web"
|
||||
}
|
||||
|
||||
// handleError logs the error and responds with the given status code with a generic status text
|
||||
func handleError(w http.ResponseWriter, logMessage string, err error, statusCode int) {
|
||||
logger.Err("[%d] %s: %v\n", statusCode, logMessage, err)
|
||||
|
||||
statusText := http.StatusText(statusCode)
|
||||
http.Error(w, statusText, statusCode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ func applyMiddleware(h http.Handler, rateLimit bool) http.Handler {
|
|||
// App is an application configuration
|
||||
type App struct {
|
||||
Clock clock.Clock
|
||||
StripeAPIBackend *stripe.BackendImplementation
|
||||
StripeAPIBackend stripe.Backend
|
||||
}
|
||||
|
||||
// init sets up the application based on the configuration
|
||||
|
|
|
|||
|
|
@ -20,75 +20,150 @@ package handlers
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dnote/dnote/server/api/helpers"
|
||||
"github.com/dnote/dnote/server/api/logger"
|
||||
"github.com/dnote/dnote/server/api/operations"
|
||||
"github.com/dnote/dnote/server/database"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stripe/stripe-go"
|
||||
"github.com/stripe/stripe-go/card"
|
||||
"github.com/stripe/stripe-go/customer"
|
||||
"github.com/stripe/stripe-go/paymentsource"
|
||||
"github.com/stripe/stripe-go/source"
|
||||
"github.com/stripe/stripe-go/sub"
|
||||
"github.com/stripe/stripe-go/webhook"
|
||||
)
|
||||
|
||||
type stripeToken struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
var proPlanID = "plan_EpgsEvY27pajfo"
|
||||
|
||||
func getOrCreateStripeCustomer(tx *gorm.DB, user database.User) (*stripe.Customer, error) {
|
||||
if user.StripeCustomerID != "" {
|
||||
c, err := customer.Get(user.StripeCustomerID, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting customer")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var account database.Account
|
||||
if err := tx.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "finding account")
|
||||
}
|
||||
|
||||
customerParams := &stripe.CustomerParams{
|
||||
Email: &account.Email.String,
|
||||
}
|
||||
c, err := customer.New(customerParams)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating customer")
|
||||
}
|
||||
|
||||
user.StripeCustomerID = c.ID
|
||||
if err := tx.Save(&user).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "updating user")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
var planID = "plan_EpgsEvY27pajfo"
|
||||
func addCustomerSource(customerID, sourceID string) (*stripe.PaymentSource, error) {
|
||||
params := &stripe.CustomerSourceParams{
|
||||
Customer: stripe.String(customerID),
|
||||
Source: &stripe.SourceParams{
|
||||
Token: stripe.String(sourceID),
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
src, err := paymentsource.New(params)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating source for customer")
|
||||
}
|
||||
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func createCustomerSubscription(customerID, planID string) (*stripe.Subscription, error) {
|
||||
subParams := &stripe.SubscriptionParams{
|
||||
Customer: stripe.String(customerID),
|
||||
Items: []*stripe.SubscriptionItemsParams{
|
||||
{
|
||||
Plan: stripe.String(planID),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s, err := sub.New(subParams)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating subscription for customer")
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type createSubPayload struct {
|
||||
Source stripe.Source `json:"source"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
|
||||
// createSub creates a subscription for a the current user
|
||||
func (a *App) createSub(w http.ResponseWriter, r *http.Request) {
|
||||
db := database.DBConn
|
||||
|
||||
user, ok := r.Context().Value(helpers.KeyUser).(database.User)
|
||||
if !ok {
|
||||
http.Error(w, "No authenticated user found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if user.StripeCustomerID != "" {
|
||||
http.Error(w, "Customer already exists", http.StatusForbidden)
|
||||
|
||||
var payload createSubPayload
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
handleError(w, "decoding params", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var tok stripeToken
|
||||
if err := json.NewDecoder(r.Body).Decode(&tok); err != nil {
|
||||
http.Error(w, errors.Wrap(err, "decoding params").Error(), http.StatusInternalServerError)
|
||||
db := database.DBConn
|
||||
tx := db.Begin()
|
||||
|
||||
if err := tx.Model(&user).
|
||||
Update(map[string]interface{}{
|
||||
"cloud": true,
|
||||
"billing_country": payload.Country,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
handleError(w, "updating user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
customerParams := &stripe.CustomerParams{
|
||||
Plan: &planID,
|
||||
Email: &tok.Email,
|
||||
}
|
||||
err := customerParams.SetSource(tok.ID)
|
||||
customer, err := getOrCreateStripeCustomer(tx, user)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "setting source").Error(), http.StatusInternalServerError)
|
||||
tx.Rollback()
|
||||
handleError(w, "getting customer", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: if customer exists, update not create
|
||||
c, err := customer.New(customerParams)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "creating customer").Error(), http.StatusInternalServerError)
|
||||
if _, err = addCustomerSource(customer.ID, payload.Source.ID); err != nil {
|
||||
tx.Rollback()
|
||||
handleError(w, "attaching source", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user.StripeCustomerID = c.ID
|
||||
user.Cloud = true
|
||||
if err := db.Save(&user).Error; err != nil {
|
||||
http.Error(w, errors.Wrap(err, "updating user").Error(), http.StatusInternalServerError)
|
||||
if _, err := createCustomerSubscription(customer.ID, proPlanID); err != nil {
|
||||
tx.Rollback()
|
||||
handleError(w, "creating subscription", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
handleError(w, "committing a subscription transaction", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
type updateSubPayload struct {
|
||||
|
|
@ -141,12 +216,11 @@ func (a *App) updateSub(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var payload updateSubPayload
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
http.Error(w, errors.Wrap(err, "decoding params").Error(), http.StatusInternalServerError)
|
||||
handleError(w, "decoding params", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateUpdateSubPayload(payload); err != nil {
|
||||
http.Error(w, errors.Wrap(err, "invalid payload").Error(), http.StatusBadRequest)
|
||||
handleError(w, "invalid payload", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +239,7 @@ func (a *App) updateSub(w http.ResponseWriter, r *http.Request) {
|
|||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
http.Error(w, errors.Wrapf(err, "during operation %s", payload.Op).Error(), statusCode)
|
||||
handleError(w, fmt.Sprintf("during operation %s", payload.Op), err, statusCode)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -189,13 +263,13 @@ type GetSubResponse struct {
|
|||
}
|
||||
|
||||
func respondWithEmptySub(w http.ResponseWriter) {
|
||||
emptyGetSubREsponse := GetSubResponse{
|
||||
emptyGetSubResponse := GetSubResponse{
|
||||
Items: []GetSubResponseItem{},
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(emptyGetSubREsponse); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
if err := json.NewEncoder(w).Encode(emptyGetSubResponse); err != nil {
|
||||
handleError(w, "encoding response", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -218,7 +292,7 @@ func (a *App) getSub(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if !i.Next() {
|
||||
if err := i.Err(); err != nil {
|
||||
http.Error(w, errors.Wrap(err, "fetching subscription").Error(), http.StatusInternalServerError)
|
||||
handleError(w, "fetching subscription", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +321,7 @@ func (a *App) getSub(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
handleError(w, "encoding response", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -262,13 +336,64 @@ type GetStripeSourceResponse struct {
|
|||
|
||||
func respondWithEmptyStripeToken(w http.ResponseWriter) {
|
||||
var resp GetStripeSourceResponse
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
handleError(w, "encoding response", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// getStripeCard retrieves card information from stripe and returns a stripe.Card
|
||||
// It handles legacy 'card' resource which have 'card_' prefixes, as well as the
|
||||
// more up-to-date 'source' resources which have 'src_' prefixes.
|
||||
func getStripeCard(stripeCustomerID, sourceID string) (*stripe.Card, error) {
|
||||
if strings.HasPrefix(sourceID, "card_") {
|
||||
params := &stripe.CardParams{
|
||||
Customer: stripe.String(stripeCustomerID),
|
||||
}
|
||||
cd, err := card.Get(sourceID, params)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching card")
|
||||
}
|
||||
|
||||
return cd, nil
|
||||
} else if strings.HasPrefix(sourceID, "src_") {
|
||||
src, err := source.Get(sourceID, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching source")
|
||||
}
|
||||
|
||||
brand, ok := src.TypeData["brand"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("casting brand")
|
||||
}
|
||||
last4, ok := src.TypeData["last4"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("casting last4")
|
||||
}
|
||||
expMonth, ok := src.TypeData["exp_month"].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("casting exp_month")
|
||||
}
|
||||
expYear, ok := src.TypeData["exp_year"].(float64)
|
||||
if !ok {
|
||||
return nil, errors.New("casting exp_year")
|
||||
}
|
||||
|
||||
cd := &stripe.Card{
|
||||
Brand: stripe.CardBrand(brand),
|
||||
Last4: last4,
|
||||
ExpMonth: uint8(expMonth),
|
||||
ExpYear: uint16(expYear),
|
||||
}
|
||||
|
||||
return cd, nil
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("malformed sourceID %s", sourceID)
|
||||
}
|
||||
|
||||
func (a *App) getStripeSource(w http.ResponseWriter, r *http.Request) {
|
||||
user, ok := r.Context().Value(helpers.KeyUser).(database.User)
|
||||
if !ok {
|
||||
|
|
@ -282,20 +407,18 @@ func (a *App) getStripeSource(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
c, err := customer.Get(user.StripeCustomerID, nil)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "fetching stripe customer").Error(), http.StatusInternalServerError)
|
||||
handleError(w, "fetching stripe customer", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if c.DefaultSource == nil {
|
||||
respondWithEmptyStripeToken(w)
|
||||
return
|
||||
}
|
||||
|
||||
params := &stripe.CardParams{
|
||||
Customer: stripe.String(user.StripeCustomerID),
|
||||
}
|
||||
cd, err := card.Get(c.DefaultSource.ID, params)
|
||||
cd, err := getStripeCard(user.StripeCustomerID, c.DefaultSource.ID)
|
||||
if err != nil {
|
||||
http.Error(w, errors.Wrap(err, "fetching stripe card").Error(), http.StatusInternalServerError)
|
||||
handleError(w, "fetching stripe source", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -308,7 +431,7 @@ func (a *App) getStripeSource(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
handleError(w, "encoding response", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -316,16 +439,14 @@ func (a *App) getStripeSource(w http.ResponseWriter, r *http.Request) {
|
|||
func (a *App) stripeWebhook(w http.ResponseWriter, req *http.Request) {
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
logger.Err("Error reading request body: %v\n", err)
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
handleError(w, "reading body", err, http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
webhookSecret := os.Getenv("StripeWebhookSecret")
|
||||
event, err := webhook.ConstructEvent(body, req.Header.Get("Stripe-Signature"), webhookSecret)
|
||||
if err != nil {
|
||||
logger.Err("Error verifying the signature: %v\n", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
handleError(w, "verifying stripe webhook signature", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -334,8 +455,7 @@ func (a *App) stripeWebhook(w http.ResponseWriter, req *http.Request) {
|
|||
{
|
||||
var subscription stripe.Subscription
|
||||
if json.Unmarshal(event.Data.Raw, &subscription); err != nil {
|
||||
logger.Err(errors.Wrap(err, "unmarshaling").Error())
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
handleError(w, "unmarshaling payload", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -343,8 +463,8 @@ func (a *App) stripeWebhook(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
default:
|
||||
{
|
||||
logger.Err("Unsupported webhook event type %s", event.Type)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
msg := fmt.Sprintf("Unsupported webhook event type %s", event.Type)
|
||||
handleError(w, msg, err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ type User struct {
|
|||
APIKey string `json:"-" gorm:"index"`
|
||||
Name string `json:"name"`
|
||||
StripeCustomerID string `json:"-"`
|
||||
BillingCountry string `json:"-"`
|
||||
Cloud bool `json:"-" gorm:"default:false"`
|
||||
IsAdmin bool `json:"-" gorm:"default:false"`
|
||||
Account Account
|
||||
|
|
|
|||
|
|
@ -349,15 +349,25 @@ func GetCookieByName(cookies []*http.Cookie, name string) *http.Cookie {
|
|||
return ret
|
||||
}
|
||||
|
||||
// CreateMockStripeBackend returns a mock stripe backend implementation that uses
|
||||
// CreateMockStripeBackend returns a mock stripe backend that uses
|
||||
// the given test server
|
||||
func CreateMockStripeBackend(ts *httptest.Server) *stripe.BackendImplementation {
|
||||
c := ts.Client()
|
||||
bi := stripe.BackendImplementation{
|
||||
Type: stripe.APIBackend,
|
||||
URL: ts.URL + "/v1",
|
||||
HTTPClient: c,
|
||||
}
|
||||
func CreateMockStripeBackend(ts *httptest.Server) stripe.Backend {
|
||||
stripeMockBackend := stripe.GetBackendWithConfig(
|
||||
stripe.APIBackend,
|
||||
&stripe.BackendConfig{
|
||||
URL: ts.URL + "/v1",
|
||||
HTTPClient: ts.Client(),
|
||||
},
|
||||
)
|
||||
|
||||
return &bi
|
||||
return stripeMockBackend
|
||||
}
|
||||
|
||||
// MustRespondJSON responds with the JSON-encoding of the given interface. If the encoding
|
||||
// fails, the test fails. It is used by test servers.
|
||||
func MustRespondJSON(t *testing.T, w http.ResponseWriter, i interface{}, message string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(i); err != nil {
|
||||
t.Fatal(message)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@
|
|||
"__DOMAIN__": true,
|
||||
"__BASE_URL__": true,
|
||||
"__BASE_NAME__": true,
|
||||
"__STRIPE_PUBLIC_KEY__": true,
|
||||
"socket": true,
|
||||
"webpackIsomorphicTools": true,
|
||||
"StripeCheckout": true
|
||||
|
|
|
|||
66
web/package-lock.json
generated
66
web/package-lock.json
generated
|
|
@ -6531,6 +6531,12 @@
|
|||
"path-is-inside": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"is-plain-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
||||
"dev": true
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
|
|
@ -7469,14 +7475,28 @@
|
|||
}
|
||||
},
|
||||
"mini-css-extract-plugin": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz",
|
||||
"integrity": "sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==",
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.6.0.tgz",
|
||||
"integrity": "sha512-79q5P7YGI6rdnVyIAV4NXpBQJFWdkzJxCim3Kog4078fM0piAaFlwocqbejdWtLW1cEzCexPrh6EdyFsPgVdAw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^1.1.0",
|
||||
"normalize-url": "^2.0.1",
|
||||
"schema-utils": "^1.0.0",
|
||||
"webpack-sources": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"normalize-url": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz",
|
||||
"integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prepend-http": "^2.0.0",
|
||||
"query-string": "^5.0.1",
|
||||
"sort-keys": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"minimalistic-assert": {
|
||||
|
|
@ -9239,6 +9259,12 @@
|
|||
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
|
||||
"dev": true
|
||||
},
|
||||
"prepend-http": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
|
||||
"integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.1.tgz",
|
||||
|
|
@ -9391,6 +9417,17 @@
|
|||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
},
|
||||
"query-string": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
|
||||
"integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"strict-uri-encode": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
|
|
@ -9595,6 +9632,14 @@
|
|||
"shallowequal": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"react-stripe-elements": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-stripe-elements/-/react-stripe-elements-3.0.0.tgz",
|
||||
"integrity": "sha512-ouGHPmPhdg7KUTOFVuYIr0UWVgBjG5CqmOn9/y0vaJICryPB6llkiOGeUXKrac7fu1/vtPdn8t/4JKnbi8PT8g==",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"react-tooltip": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-3.10.0.tgz",
|
||||
|
|
@ -10861,6 +10906,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"sort-keys": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
|
||||
"integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-plain-obj": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"source-list-map": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
|
||||
|
|
@ -11131,6 +11185,12 @@
|
|||
"lodash": "^4.17.10"
|
||||
}
|
||||
},
|
||||
"strict-uri-encode": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@
|
|||
"react-router": "^5.0.0",
|
||||
"react-router-config": "^5.0.0",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-stripe-elements": "^3.0.0",
|
||||
"react-tooltip": "^3.10.0",
|
||||
"redux": "^4.0.0",
|
||||
"redux-thunk": "^2.1.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/responsive';
|
||||
@import '../App/rem';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import './variables';
|
||||
@import './theme';
|
||||
@import './rem';
|
||||
@import './font';
|
||||
|
||||
|
|
@ -28,7 +28,6 @@
|
|||
.button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
line-height: 1.25;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
|
|
@ -44,8 +43,6 @@
|
|||
cursor: pointer;
|
||||
|
||||
&:not(.button-no-ui) {
|
||||
padding: rem(8px) rem(16px);
|
||||
@include font-size('small');
|
||||
}
|
||||
|
||||
&:not(:disabled):hover {
|
||||
|
|
@ -67,9 +64,36 @@ button:disabled {
|
|||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.button-normal {
|
||||
@include font-size('small');
|
||||
padding: rem(8px) rem(16px);
|
||||
}
|
||||
|
||||
.button-large {
|
||||
&:not(.button-no-ui) {
|
||||
padding: rem(12px) rem(16px);
|
||||
@include font-size('medium');
|
||||
|
||||
padding: rem(8px) rem(24px);
|
||||
|
||||
@include breakpoint(md) {
|
||||
padding: rem(12px) rem(36px);
|
||||
}
|
||||
|
||||
@include breakpoint(lg) {
|
||||
padding: rem(12px) rem(48px);
|
||||
}
|
||||
}
|
||||
|
||||
.button-xlarge {
|
||||
@include font-size('x-large');
|
||||
|
||||
padding: rem(16px) rem(24px);
|
||||
|
||||
@include breakpoint(md) {
|
||||
padding: rem(12px) rem(36px);
|
||||
}
|
||||
|
||||
@include breakpoint(lg) {
|
||||
padding: rem(16px) rem(48px);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +131,7 @@ button:disabled {
|
|||
}
|
||||
|
||||
.button ~ .button {
|
||||
margin-left: rem(8px);
|
||||
margin-left: rem(12px);
|
||||
}
|
||||
|
||||
.button-no-ui {
|
||||
|
|
|
|||
|
|
@ -1,57 +1,81 @@
|
|||
@import './responsive';
|
||||
|
||||
$lowDecay: 0.1;
|
||||
$medDecay: 0.15;
|
||||
$highDecay: 0.2;
|
||||
|
||||
// font-size is a mixin for pre-defined font-size values in rem.
|
||||
// It also includes px as a fallback for older browsers.
|
||||
@mixin font-size($size, $responsive: true) {
|
||||
$sizeValue: 16;
|
||||
$smSizeValue: 16;
|
||||
$mdSizeValue: 16;
|
||||
$lgSizeValue: 16;
|
||||
|
||||
@if $size == 'x-small' {
|
||||
$sizeValue: 10;
|
||||
$mdSizeValue: 10;
|
||||
$lgSizeValue: 10;
|
||||
$baseSize: 10;
|
||||
|
||||
$smSizeValue: $baseSize;
|
||||
$mdSizeValue: $baseSize;
|
||||
$lgSizeValue: $baseSize;
|
||||
} @else if $size == 'small' {
|
||||
$sizeValue: 14;
|
||||
$mdSizeValue: 14;
|
||||
$lgSizeValue: 14;
|
||||
$baseSize: 14;
|
||||
|
||||
$smSizeValue: $baseSize;
|
||||
$mdSizeValue: $baseSize;
|
||||
$lgSizeValue: $baseSize;
|
||||
} @else if $size == 'regular' {
|
||||
$sizeValue: 16;
|
||||
$mdSizeValue: 16;
|
||||
$lgSizeValue: 16;
|
||||
$baseSize: 16;
|
||||
|
||||
$smSizeValue: $baseSize * (1 - $lowDecay);
|
||||
$mdSizeValue: $baseSize * (1 - $lowDecay);
|
||||
$lgSizeValue: $baseSize;
|
||||
} @else if $size == 'medium' {
|
||||
$sizeValue: 16;
|
||||
$mdSizeValue: 18;
|
||||
$lgSizeValue: 18;
|
||||
$baseSize: 18;
|
||||
|
||||
$smSizeValue: $baseSize;
|
||||
$mdSizeValue: $baseSize;
|
||||
$lgSizeValue: $baseSize;
|
||||
} @else if $size == 'large' {
|
||||
$sizeValue: 18;
|
||||
$mdSizeValue: 18;
|
||||
$lgSizeValue: 20;
|
||||
$baseSize: 20;
|
||||
|
||||
$smSizeValue: $baseSize;
|
||||
$mdSizeValue: $baseSize;
|
||||
$lgSizeValue: $baseSize;
|
||||
} @else if $size == 'x-large' {
|
||||
$sizeValue: 22;
|
||||
$mdSizeValue: 22;
|
||||
$lgSizeValue: 24;
|
||||
$baseSize: 24;
|
||||
|
||||
$smSizeValue: $baseSize * (1 - $lowDecay * 2);
|
||||
$mdSizeValue: $baseSize * (1 - $lowDecay);
|
||||
$lgSizeValue: $baseSize;
|
||||
} @else if $size == '2x-large' {
|
||||
$sizeValue: 20;
|
||||
$mdSizeValue: 24;
|
||||
$lgSizeValue: 32;
|
||||
$baseSize: 32;
|
||||
|
||||
$smSizeValue: $baseSize * (1 - $lowDecay * 2);
|
||||
$mdSizeValue: $baseSize * (1 - $lowDecay);
|
||||
$lgSizeValue: $baseSize;
|
||||
} @else if $size == '3x-large' {
|
||||
$sizeValue: 24;
|
||||
$mdSizeValue: 32;
|
||||
$lgSizeValue: 36;
|
||||
$baseSize: 36;
|
||||
|
||||
$smSizeValue: $baseSize * (1 - $medDecay * 2);
|
||||
$mdSizeValue: $baseSize * (1 - $medDecay);
|
||||
$lgSizeValue: $baseSize;
|
||||
} @else if $size == '4x-large' {
|
||||
$sizeValue: 32;
|
||||
$mdSizeValue: 32;
|
||||
$lgSizeValue: 48;
|
||||
$baseSize: 48;
|
||||
|
||||
$smSizeValue: $baseSize * (1 - $medDecay * 2);
|
||||
$mdSizeValue: $baseSize * (1 - $medDecay);
|
||||
$lgSizeValue: $baseSize;
|
||||
} @else if $size == '5x-large' {
|
||||
$sizeValue: 32;
|
||||
$mdSizeValue: 36;
|
||||
$lgSizeValue: 56;
|
||||
$baseSize: 56;
|
||||
|
||||
$smSizeValue: $baseSize * (1 - $highDecay * 2);
|
||||
$mdSizeValue: $baseSize * (1 - $highDecay);
|
||||
$lgSizeValue: $baseSize;
|
||||
}
|
||||
|
||||
@if $responsive == true {
|
||||
font-size: $sizeValue * 1px;
|
||||
font-size: $sizeValue * 0.1rem;
|
||||
font-size: $smSizeValue * 1px;
|
||||
font-size: $smSizeValue * 0.1rem;
|
||||
|
||||
@include breakpoint(md) {
|
||||
font-size: $mdSizeValue * 1px;
|
||||
|
|
@ -63,7 +87,7 @@
|
|||
font-size: $lgSizeValue * 0.1rem;
|
||||
}
|
||||
} @else {
|
||||
font-size: $sizeValue * 1px;
|
||||
font-size: $sizeValue * 0.1rem;
|
||||
font-size: $lgSizeValue * 1px;
|
||||
font-size: $lgSizeValue * 0.1rem;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,58 @@ html {
|
|||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.container-wide {
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.container-wide {
|
||||
max-width: 540px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container-wide {
|
||||
max-width: 720px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.container-wide {
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container-wide {
|
||||
max-width: 1040px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.container-wide {
|
||||
max-width: 1180px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1800px) {
|
||||
.container-wide {
|
||||
max-width: 1660px;
|
||||
}
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
|
|
@ -43,30 +95,10 @@ html {
|
|||
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
max-width: 1040px;
|
||||
max-width: 1140px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.container {
|
||||
max-width: 1180px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1800px) {
|
||||
.container {
|
||||
max-width: 1660px;
|
||||
}
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
@import './variables';
|
||||
|
||||
$xl-breakpoint: 1441px;
|
||||
$lg-breakpoint: 1280px;
|
||||
//$lg-breakpoint: 1280px;
|
||||
$lg-breakpoint: 992px;
|
||||
//$md-breakpoint: 600px;
|
||||
$md-breakpoint: 768px;
|
||||
$sm-breakpoint: 321px;
|
||||
|
|
@ -39,3 +42,11 @@ $sm-breakpoint: 321px;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// overSidebarThreshold is a mixin that applies the given style if and only if
|
||||
// the current viewport width is above the sidebar threshold
|
||||
@mixin overSidebarThreshold() {
|
||||
@media (min-width: $note-sidebar-threshold) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,111 +18,6 @@
|
|||
background: #f4f4f4;
|
||||
}
|
||||
|
||||
.alert {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// align-items: flex-start;
|
||||
margin-bottom: 0;
|
||||
|
||||
@include breakpoint(md) {
|
||||
// flex-direction: row;
|
||||
// align-items: center;
|
||||
// justify-content: space-between;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.alert-action {
|
||||
margin-top: 10px;
|
||||
|
||||
@include breakpoint(md) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// panels
|
||||
.panel {
|
||||
background-color: white;
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
border-radius: 3px;
|
||||
word-wrap: break-word;
|
||||
|
||||
.panel-heading {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-padded {
|
||||
padding: 16px 25px;
|
||||
}
|
||||
|
||||
.panel ~ .panel {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
// dropdown
|
||||
.dropdown-content {
|
||||
position: absolute;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15), 0 2px 4px rgba(0, 0, 0, 0.29);
|
||||
border-radius: 1.5px;
|
||||
z-index: 1;
|
||||
|
||||
.dropdown-content-header {
|
||||
padding: 8px 14px;
|
||||
display: block;
|
||||
margin-bottom: 0;
|
||||
font-size: 1.4rem;
|
||||
color: #868e96;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-content-divider {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
&::after {
|
||||
top: 0px;
|
||||
border: 7px solid transparent;
|
||||
border-bottom-color: #fff;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
content: '';
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: -2px;
|
||||
border: 8px solid transparent;
|
||||
border-bottom-color: rgba(27, 31, 35, 0.15);
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
// dismissable
|
||||
|
||||
.dismissable-overlay {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
// z-index: 2;
|
||||
}
|
||||
|
||||
input[type='text']:disabled,
|
||||
input[type='email']:disabled,
|
||||
input[type='number']:disabled,
|
||||
|
|
@ -131,14 +26,13 @@ textarea:disabled {
|
|||
background: #ececec;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// Replace the default browser outline with custom focus indicator
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// used to copy things to clipboard
|
||||
.copy-input {
|
||||
position: absolute;
|
||||
left: -999px;
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.list-unstyled {
|
||||
|
|
@ -160,13 +54,6 @@ textarea:focus {
|
|||
}
|
||||
|
||||
.page {
|
||||
// padding: rem(14px) 0;
|
||||
//
|
||||
// @include breakpoint(md) {
|
||||
// padding: rem(28px) 0;
|
||||
// }
|
||||
|
||||
// max-height: 100%;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
|
@ -179,33 +66,36 @@ button {
|
|||
}
|
||||
|
||||
.text-input {
|
||||
border: 1px solid $dark-light2;
|
||||
border: 2px solid $border-color;
|
||||
padding: rem(4px) rem(12px);
|
||||
position: relative;
|
||||
border-radius: 2px;
|
||||
display: block;
|
||||
|
||||
&::placeholder {
|
||||
color: $dark-light4;
|
||||
color: $gray;
|
||||
}
|
||||
&:focus {
|
||||
border: 1px solid $third;
|
||||
}
|
||||
|
||||
&:not(.text-input-transparent) {
|
||||
background: $dark-light3;
|
||||
}
|
||||
&.text-input-transparent {
|
||||
background: transparent;
|
||||
border: 2px solid $third;
|
||||
}
|
||||
|
||||
&.text-input-medium {
|
||||
padding: rem(8px) rem(12px);
|
||||
}
|
||||
|
||||
&.text-input-stretch {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.learning-count {
|
||||
@include font-size('medium');
|
||||
.label-full {
|
||||
width: 100%;
|
||||
}
|
||||
.learning-date {
|
||||
@include font-size('regular');
|
||||
|
||||
a {
|
||||
color: $link;
|
||||
|
||||
&:hover {
|
||||
color: $link-hover;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
web/src/components/App/_theme.scss
Normal file
23
web/src/components/App/_theme.scss
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// basic colors
|
||||
$black: #2a2a2a;
|
||||
$white: #ffffff;
|
||||
$light: #f7f9fa;
|
||||
$gray: #686868;
|
||||
$light-gray: #f3f3f3;
|
||||
|
||||
// primary colors
|
||||
$first: #333745;
|
||||
$second: #e7e7e7;
|
||||
$third: #4d4d8b;
|
||||
|
||||
// functional colors
|
||||
$border-color: #d8d8d8;
|
||||
$border-color-light: $light-gray;
|
||||
|
||||
$link: #6f53c0;
|
||||
$link-hover: darken($link, 5%);
|
||||
|
||||
$danger-text: #cb2431;
|
||||
$danger-background: #f8d7da;
|
||||
|
||||
$light-blue: #ecf4ff;
|
||||
|
|
@ -1,39 +1,12 @@
|
|||
$dark: #3d4c53;
|
||||
//$dark-light: #f5f5f5;
|
||||
$dark-light: #f7f9fa;
|
||||
$dark-light2: #e5e5e5;
|
||||
$dark-light3: #f7f7f7;
|
||||
$dark-light4: #929292;
|
||||
$dark-light5: #6e6e6e;
|
||||
$dark-bg: #f3f3f3;
|
||||
$black: #2a2a2a;
|
||||
$white: #ffffff;
|
||||
|
||||
$border-color: #d8d8d8;
|
||||
$border-color-light: #f3f3f3;
|
||||
|
||||
//$danger-text: #721c24;
|
||||
$danger-text: #cb2431;
|
||||
$danger-background: #f8d7da;
|
||||
|
||||
$first: #333745;
|
||||
$second: #e7e7e7;
|
||||
|
||||
$third: #245fc5;
|
||||
// $third: #6d6daa;
|
||||
// $third: #4d4d8b;
|
||||
|
||||
//$third: #18a0fb;
|
||||
//$third: #505061;
|
||||
$third-light: #ecf4ff;
|
||||
|
||||
$sidebar-width: 180px;
|
||||
$note-sidebar-width: 244px;
|
||||
$xl-note-sidebar-width: 320px;
|
||||
$footer-height: 28px;
|
||||
$note-header-height: 60px;
|
||||
|
||||
$note-sidebar-threshold: 1280px;
|
||||
$note-sidebar-threshold-value: 1280;
|
||||
$note-sidebar-threshold: '#{$note-sidebar-threshold-value}px';
|
||||
|
||||
$link: #0061ff;
|
||||
$link-hover: #0052d9;
|
||||
:export {
|
||||
noteSidebarThreshold: $note-sidebar-threshold-value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import render from '../../routes';
|
|||
import { footerPaths, isDemoPath, checkBoxedLayout } from '../../libs/paths';
|
||||
import SystemMessage from '../Common/SystemMessage';
|
||||
|
||||
import './module.scss';
|
||||
import './App.global.scss';
|
||||
import styles from './App.module.scss';
|
||||
|
||||
function checkIsEditor(location, prevLocation) {
|
||||
|
|
@ -145,7 +145,6 @@ function App({ location, history, doGetCurrentUser, user }) {
|
|||
}}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/subscriptions"
|
||||
render={() => {
|
||||
return <MainFooter />;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
|
||||
.wrapper {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
.active {
|
||||
.link {
|
||||
background: $third-light;
|
||||
background: $light-blue;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../App/responsive';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/rem';
|
||||
|
||||
.wrapper {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import React, { useState } from 'react';
|
|||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { homePath } from '../../../libs/paths';
|
||||
import { getHomePath } from '../../../libs/paths';
|
||||
import Actions from './Actions';
|
||||
import MobileActions from './MobileActions';
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ export default ({ book, demo, isFocused, setFocusedOptEl, onDeleteBook }) => {
|
|||
}}
|
||||
>
|
||||
<Link
|
||||
to={homePath({ book: book.uuid }, { demo })}
|
||||
to={getHomePath({ book: book.uuid }, { demo })}
|
||||
className={styles.link}
|
||||
tabIndex="-1"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../App/rem';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
|
||||
.list {
|
||||
background-color: white;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../App/rem';
|
||||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/font';
|
||||
|
||||
.new-book-button {
|
||||
|
|
@ -14,9 +14,9 @@
|
|||
.count {
|
||||
display: none;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
@include font-size('small');
|
||||
color: $dark;
|
||||
color: $black;
|
||||
display: inline-block;
|
||||
|
||||
&.hidden {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import Flash from '../Common/Flash';
|
|||
import Button from '../Common/Button';
|
||||
|
||||
import { escapesRegExp } from '../../libs/string';
|
||||
import { homePath } from '../../libs/paths';
|
||||
import { getHomePath } from '../../libs/paths';
|
||||
import DeleteBookModal from './DeleteBookModal';
|
||||
import { useSearchMenuKeydown, useScrollToFocused } from '../../libs/hooks/dom';
|
||||
import { getOptIdxByValue } from '../../helpers/accessibility';
|
||||
|
|
@ -48,7 +48,7 @@ function filterBooks(books, searchInput) {
|
|||
|
||||
function handleMenuKeydownSelect(demo, history) {
|
||||
return option => {
|
||||
const destination = homePath(
|
||||
const destination = getHomePath(
|
||||
{
|
||||
book: option.uuid
|
||||
},
|
||||
|
|
@ -154,6 +154,7 @@ function Content({
|
|||
id="T-create-book-btn"
|
||||
type="button"
|
||||
kind="third"
|
||||
size="normal"
|
||||
className={styles['create-book-button']}
|
||||
disabled={isFetching}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../App/rem';
|
||||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
|
||||
.actions {
|
||||
margin-top: rem(12px);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { withRouter } from 'react-router-dom';
|
|||
import Modal, { Header } from '../Common/Modal';
|
||||
import * as booksOperation from '../../operations/books';
|
||||
import { addBook } from '../../actions/books';
|
||||
import { homePath } from '../../libs/paths';
|
||||
import { getHomePath } from '../../libs/paths';
|
||||
import Button from '../Common/Button';
|
||||
import Flash from '../Common/Flash';
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ function CreateBookModal({
|
|||
doAddBook(book);
|
||||
setInProgress(false);
|
||||
|
||||
const dest = homePath({ book: book.uuid });
|
||||
const dest = getHomePath({ book: book.uuid });
|
||||
history.push(dest);
|
||||
})
|
||||
.catch(err => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../App/rem';
|
||||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
|
||||
.input {
|
||||
margin-top: rem(8px);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../App/rem';
|
||||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
|
||||
.input {
|
||||
margin-top: rem(8px);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
@import '../App/rem';
|
||||
@import '../App/font';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
|
||||
.wrapper {
|
||||
text-align: center;
|
||||
padding: rem(48px) 0;
|
||||
@include font-size('medium');
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ function Books({ demo, doGetBooks, userData, booksData }) {
|
|||
/>
|
||||
|
||||
<Body>
|
||||
<div className="container">
|
||||
<div className="container-wide">
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<ContentWrapper
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/font';
|
||||
@import '../App/rem';
|
||||
|
||||
.page {
|
||||
background: $dark-bg;
|
||||
background: $light-gray;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
padding: 50px 0;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ function Button({
|
|||
id,
|
||||
type,
|
||||
kind,
|
||||
size,
|
||||
children,
|
||||
className,
|
||||
isBusy,
|
||||
|
|
@ -40,10 +41,16 @@ function Button({
|
|||
<button
|
||||
id={id}
|
||||
type={type}
|
||||
className={classnames(className, 'button', `button-${kind}`, {
|
||||
[styles.busy]: isBusy,
|
||||
'button-stretch': stretch
|
||||
})}
|
||||
className={classnames(
|
||||
className,
|
||||
'button',
|
||||
`button-${kind}`,
|
||||
`button-${size}`,
|
||||
{
|
||||
[styles.busy]: isBusy,
|
||||
'button-stretch': stretch
|
||||
}
|
||||
)}
|
||||
disabled={isBusy || disabled}
|
||||
onClick={onClick}
|
||||
>
|
||||
|
|
@ -61,4 +68,8 @@ function Button({
|
|||
);
|
||||
}
|
||||
|
||||
Button.defaultProps = {
|
||||
size: 'normal'
|
||||
};
|
||||
|
||||
export default Button;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../App/responsive';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../App/responsive';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background: $dark-light;
|
||||
background: $light;
|
||||
height: rem(48px);
|
||||
padding-left: rem(16px);
|
||||
border-top-right-radius: 2px;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../App/responsive';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../App/responsive';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../../App/rem';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/font';
|
||||
|
||||
.wrapper {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import styles from './Header.module.scss';
|
|||
function Header({ doToggleSidebar, heading, leftContent, rightContent }) {
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className="container">
|
||||
<div className="container-wide">
|
||||
<div className={styles.content}>
|
||||
<div className={styles.left}>
|
||||
<SidebarToggle onClick={doToggleSidebar} />
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
@import '../../App/rem';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/font';
|
||||
|
||||
.header {
|
||||
background: $dark-light3;
|
||||
background: $light-gray;
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
background: none;
|
||||
padding: rem(44px) 0 0;
|
||||
border-bottom: none;
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
height: rem(52px);
|
||||
justify-content: space-between;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
@include font-size('large');
|
||||
font-weight: 400;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../App/font';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/rem';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../App/font';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/rem';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
@import '../../App/rem';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: $dark-light3;
|
||||
background: $light-gray;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
transform: translateY(-50%);
|
||||
|
||||
path {
|
||||
fill: $dark-light4;
|
||||
fill: $gray;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/font';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/responsive';
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
|
||||
&.active {
|
||||
background: $dark-light2;
|
||||
background: $light;
|
||||
}
|
||||
|
||||
&.focused {
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
display: block;
|
||||
@include font-size('small');
|
||||
font-weight: 600;
|
||||
background: $dark-light;
|
||||
background: $light;
|
||||
padding: rem(4px) rem(12px);
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
padding: rem(8px);
|
||||
//background: #e7f0ff;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
background: $dark-light;
|
||||
background: $light;
|
||||
}
|
||||
.textbox {
|
||||
@include font-size('small');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/rem';
|
||||
|
||||
.content {
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ import SafeNavLink from '../../Link/SafeNavLink';
|
|||
import SafeLink from '../../Link/SafeLink';
|
||||
|
||||
import {
|
||||
homePath,
|
||||
booksPath,
|
||||
notePath,
|
||||
digestsPath,
|
||||
subscriptionsPath
|
||||
getHomePath,
|
||||
getBooksPath,
|
||||
getNotePath,
|
||||
getDigestsPath,
|
||||
getSubscriptionPath
|
||||
} from '../../../../libs/paths';
|
||||
import { parseSearchString } from '../../../../libs/url';
|
||||
import {
|
||||
|
|
@ -130,7 +130,7 @@ async function handleCreateNote({
|
|||
doAddNote(note, year, month);
|
||||
|
||||
const queryObj = parseSearchString(location.search);
|
||||
const dest = notePath(note.uuid, queryObj, { isEditor: true });
|
||||
const dest = getNotePath(note.uuid, queryObj, { isEditor: true });
|
||||
history.push(dest);
|
||||
} catch (e) {
|
||||
console.log('err', e);
|
||||
|
|
@ -180,9 +180,9 @@ const Sidebar = ({
|
|||
return () => null;
|
||||
}, [layoutData.sidebar, doCloseSidebar]);
|
||||
|
||||
const pathHome = homePath({}, { demo });
|
||||
const pathBooks = booksPath({ demo });
|
||||
const pathDigests = digestsPath({ demo });
|
||||
const pathHome = getHomePath({}, { demo });
|
||||
const pathBooks = getBooksPath({ demo });
|
||||
const pathDigests = getDigestsPath({ demo });
|
||||
|
||||
const user = userData.data;
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ const Sidebar = ({
|
|||
id="T-create-note-btn"
|
||||
type="button"
|
||||
className={classnames(
|
||||
'button button-slim button-stretch button-third'
|
||||
'button button-normal button-slim button-stretch button-third'
|
||||
)}
|
||||
onClick={() => {
|
||||
handleCreateNote({
|
||||
|
|
@ -297,7 +297,7 @@ const Sidebar = ({
|
|||
})}
|
||||
>
|
||||
<SafeLink
|
||||
to={subscriptionsPath()}
|
||||
to={getSubscriptionPath()}
|
||||
onClick={handleLinkClick}
|
||||
className="button button-slim button-stretch button-first"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../../App/variables';
|
||||
@import '../../../App/theme';
|
||||
@import '../../../App/responsive';
|
||||
@import '../../../App/rem';
|
||||
@import '../../../App/font';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../../App/variables';
|
||||
@import '../../../App/theme';
|
||||
@import '../../../App/responsive';
|
||||
@import '../../../App/rem';
|
||||
@import '../../../App/font';
|
||||
|
|
@ -38,6 +38,6 @@ a.back-button {
|
|||
display: block;
|
||||
margin-top: rem(20px);
|
||||
padding-left: rem(12px);
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import classnames from 'classnames';
|
|||
import { withRouter, Link, NavLink } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { homePath, settingsPath } from '../../../../libs/paths';
|
||||
import { getHomePath, getSettingsPath } from '../../../../libs/paths';
|
||||
import {
|
||||
getWindowWidth,
|
||||
noteSidebarThreshold,
|
||||
|
|
@ -94,10 +94,10 @@ const SettingsSidebar = ({ location, layoutData, doCloseSidebar }) => {
|
|||
<div>
|
||||
<div className={styles['button-wrapper']}>
|
||||
<Link
|
||||
to={homePath()}
|
||||
to={getHomePath()}
|
||||
onClick={maybeCloseSidebar}
|
||||
className={classnames(
|
||||
'button button-slim button-stretch button-third-outline',
|
||||
'button button-normal button-slim button-stretch button-third-outline',
|
||||
styles['back-button']
|
||||
)}
|
||||
>
|
||||
|
|
@ -119,13 +119,13 @@ const SettingsSidebar = ({ location, layoutData, doCloseSidebar }) => {
|
|||
<NavLink
|
||||
onClick={maybeCloseSidebar}
|
||||
className={classnames(styles.link, sidebarStyles.link)}
|
||||
to={settingsPath('account')}
|
||||
to={getSettingsPath('account')}
|
||||
activeClassName={classnames(
|
||||
sidebarStyles['link-active'],
|
||||
styles['link-active']
|
||||
)}
|
||||
isActive={() => {
|
||||
return location.pathname === settingsPath('account');
|
||||
return location.pathname === getSettingsPath('account');
|
||||
}}
|
||||
>
|
||||
{/* <UserIcon width="16" height="16" fill="#6e6e6e" />
|
||||
|
|
@ -138,13 +138,15 @@ const SettingsSidebar = ({ location, layoutData, doCloseSidebar }) => {
|
|||
<NavLink
|
||||
onClick={maybeCloseSidebar}
|
||||
className={classnames(styles.link, sidebarStyles.link)}
|
||||
to={settingsPath('notification')}
|
||||
to={getSettingsPath('notification')}
|
||||
activeClassName={classnames(
|
||||
sidebarStyles['link-active'],
|
||||
styles['link-active']
|
||||
)}
|
||||
isActive={() => {
|
||||
return location.pathname === settingsPath('notification');
|
||||
return (
|
||||
location.pathname === getSettingsPath('notification')
|
||||
);
|
||||
}}
|
||||
>
|
||||
{/* <EmailIcon width="16" height="16" fill="#6e6e6e" />
|
||||
|
|
@ -159,13 +161,13 @@ const SettingsSidebar = ({ location, layoutData, doCloseSidebar }) => {
|
|||
<NavLink
|
||||
onClick={maybeCloseSidebar}
|
||||
className={classnames(styles.link, sidebarStyles.link)}
|
||||
to={settingsPath('billing')}
|
||||
to={getSettingsPath('billing')}
|
||||
activeClassName={classnames(
|
||||
sidebarStyles['link-active'],
|
||||
styles['link-active']
|
||||
)}
|
||||
isActive={() => {
|
||||
return location.pathname === settingsPath('billing');
|
||||
return location.pathname === getSettingsPath('billing');
|
||||
}}
|
||||
>
|
||||
{/* <CreditCardIcon width="16" height="16" fill="#6e6e6e" />
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
@import '../../App/theme';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/rem';
|
||||
|
|
@ -22,7 +23,7 @@
|
|||
bottom: auto;
|
||||
}
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
background: none;
|
||||
position: relative;
|
||||
left: auto;
|
||||
|
|
@ -33,7 +34,7 @@
|
|||
}
|
||||
|
||||
.sidebar {
|
||||
background: $dark-light;
|
||||
background: $light;
|
||||
min-width: $sidebar-width;
|
||||
width: $sidebar-width;
|
||||
transition: 0.25s cubic-bezier(0, 0, 0, 1);
|
||||
|
|
@ -94,16 +95,16 @@
|
|||
&:hover {
|
||||
color: $black;
|
||||
text-decoration: none;
|
||||
background: #eaeaea;
|
||||
background: darken($light, 5%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: #eaeaea;
|
||||
background: darken($light, 5%);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.link-active {
|
||||
background: $dark-light2;
|
||||
background: darken($light, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/theme';
|
||||
@import '../App/variables';
|
||||
|
||||
.button {
|
||||
padding: 0;
|
||||
|
||||
@media (min-width: $note-sidebar-threshold) {
|
||||
@include overSidebarThreshold {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import classnames from 'classnames';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import LockIcon from '../Icons/Lock';
|
||||
import { subscriptionsPath, homePath } from '../../libs/paths';
|
||||
import { getSubscriptionPath, getHomePath } from '../../libs/paths';
|
||||
|
||||
import styles from './SubscriberWall.module.scss';
|
||||
|
||||
|
|
@ -33,12 +33,15 @@ function SubscriberWall({ wrapperClassName }) {
|
|||
<div className={styles.lead}>Unlock Dnote Pro to get started.</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<Link to={subscriptionsPath()} className="button button-first">
|
||||
<Link
|
||||
to={getSubscriptionPath()}
|
||||
className="button button-normal button-first"
|
||||
>
|
||||
Get started
|
||||
</Link>
|
||||
<Link
|
||||
to={homePath({}, { demo: true })}
|
||||
className="button button-first-outline "
|
||||
to={getHomePath({}, { demo: true })}
|
||||
className="button button-normal button-first-outline "
|
||||
>
|
||||
Live demo
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/theme';
|
||||
@import '../App/variables';
|
||||
@import '../App/rem';
|
||||
|
||||
.wrapper {
|
||||
min-height: calc(100vh - #{$note-header-height});
|
||||
background: $dark-light3;
|
||||
background: $light-gray;
|
||||
text-align: center;
|
||||
padding-bottom: rem(32px);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
|
||||
.wrapper {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import moment from 'moment';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styles from './DigestItem.module.scss';
|
||||
import { digestPath } from '../../libs/paths';
|
||||
import { getDigestPath } from '../../libs/paths';
|
||||
|
||||
function DigestItem({ digest, demo }) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
|
@ -23,7 +23,7 @@ function DigestItem({ digest, demo }) {
|
|||
setIsHovered(false);
|
||||
}}
|
||||
>
|
||||
<Link className={styles.link} to={digestPath(digest.uuid, { demo })}>
|
||||
<Link className={styles.link} to={getDigestPath(digest.uuid, { demo })}>
|
||||
{moment(digest.created_at).format('YYYY MMM Do')}
|
||||
</Link>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
|
||||
.wrapper {
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
.active {
|
||||
.link {
|
||||
background: $third-light;
|
||||
background: $light-blue;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
|
||||
.list {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ function Digests({
|
|||
<Header heading="Digests" />
|
||||
|
||||
<Body>
|
||||
<div className="container">
|
||||
<div className="container-wide">
|
||||
{demo || user.cloud ? (
|
||||
<Content user={user} demo={demo} />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
@import '../App/font';
|
||||
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
text-align: center;
|
||||
height: 100vh;
|
||||
padding: rem(52px) 0;
|
||||
background: $dark-bg;
|
||||
background: $light-gray;
|
||||
}
|
||||
|
||||
.heading {
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
margin-top: rem(20px);
|
||||
|
||||
a {
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import Flash from '../Common/Flash';
|
|||
|
||||
import { parseSearchString } from '../../libs/url';
|
||||
import { getEmailPreference } from '../../actions/auth';
|
||||
import { loginPath } from '../../libs/paths';
|
||||
import { getLoginPath } from '../../libs/paths';
|
||||
|
||||
import styles from './EmailPreference.module.scss';
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ function EmailPreference({
|
|||
<Flash type="danger" wrapperClassName={styles.flash}>
|
||||
Error fetching email preference: {errorMessage}.{' '}
|
||||
<span>
|
||||
Please <Link to={loginPath()}>login</Link> and try again.
|
||||
Please <Link to={getLoginPath()}>login</Link> and try again.
|
||||
</span>
|
||||
</Flash>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { Link } from 'react-router-dom';
|
|||
import Lock from '../Icons/Lock';
|
||||
import Menu from '../Common/Menu';
|
||||
import { signout } from '../../services/users';
|
||||
import { settingsPath } from '../../libs/paths';
|
||||
import { getSettingsPath } from '../../libs/paths';
|
||||
|
||||
import styles from './AccountMenu.module.scss';
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ const AccountMenu = ({ triggerClassName, demo, user }) => {
|
|||
className={classnames(styles.link, {
|
||||
[styles.disabled]: demo
|
||||
})}
|
||||
to={settingsPath('account', { demo })}
|
||||
to={getSettingsPath('account', { demo })}
|
||||
onClick={e => {
|
||||
if (demo) {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../App/font';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
@import '../App/responsive';
|
||||
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
color: black;
|
||||
|
||||
&:hover {
|
||||
background: $dark-light3;
|
||||
background: $light-gray;
|
||||
text-decoration: none;
|
||||
color: #0056b3;
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
}
|
||||
|
||||
&:not(.disabled):focus {
|
||||
background: $dark-light3;
|
||||
background: $light-gray;
|
||||
color: #0056b3;
|
||||
outline: 1px dotted gray;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
@import '../App/font';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
@import '../App/variables';
|
||||
|
||||
.footer {
|
||||
@include font-size('small');
|
||||
border-top: 1px solid $border-color;
|
||||
background: $dark-light;
|
||||
background: $light;
|
||||
z-index: 4;
|
||||
height: $footer-height;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
@import '../App/font';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
|
||||
.footer {
|
||||
@include font-size('small');
|
||||
padding: rem(32px) 0;
|
||||
text-align: center;
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,16 +21,16 @@ import classnames from 'classnames';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { subscriptionsPath, joinPath } from '../../libs/paths';
|
||||
import { getSubscriptionPath, getJoinPath } from '../../libs/paths';
|
||||
|
||||
import styles from './DemoHeader.module.scss';
|
||||
|
||||
function getPricingPath(user) {
|
||||
if (user) {
|
||||
return subscriptionsPath();
|
||||
return getSubscriptionPath();
|
||||
}
|
||||
|
||||
return joinPath({ referrer: subscriptionsPath() });
|
||||
return getJoinPath({ referrer: getSubscriptionPath() });
|
||||
}
|
||||
|
||||
function DemoHeader({ user }) {
|
||||
|
|
@ -49,13 +49,19 @@ function DemoHeader({ user }) {
|
|||
<div className={styles.right}>
|
||||
<Link
|
||||
to={getPricingPath(user)}
|
||||
className={classnames(styles.cta, 'button button-second')}
|
||||
className={classnames(
|
||||
styles.cta,
|
||||
'button button-normal button-second'
|
||||
)}
|
||||
>
|
||||
Get started
|
||||
</Link>
|
||||
<a
|
||||
href="/"
|
||||
className={classnames(styles['quit-mobile'], 'button button-first')}
|
||||
className={classnames(
|
||||
styles['quit-mobile'],
|
||||
'button button-normal button-first'
|
||||
)}
|
||||
>
|
||||
Quit demo
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
@import '../App/font';
|
||||
|
||||
|
|
|
|||
|
|
@ -20,21 +20,21 @@ import React from 'react';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Logo from '../Icons/Logo';
|
||||
import { homePath } from '../../libs/paths';
|
||||
import { getHomePath } from '../../libs/paths';
|
||||
import styles from './NoteHeader.module.scss';
|
||||
|
||||
function NoteHeader({ demo }) {
|
||||
return (
|
||||
<header className={styles.wrapper}>
|
||||
<div className={styles.content}>
|
||||
<Link to={homePath({}, { demo })} className={styles.brand}>
|
||||
<Link to={getHomePath({}, { demo })} className={styles.brand}>
|
||||
<Logo width={32} height={32} fill="#909090" className="logo" />
|
||||
<span className={styles['brand-name']}>Dnote</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to={homePath({}, { demo })}
|
||||
className="button button-slim button-first-outline"
|
||||
to={getHomePath({}, { demo })}
|
||||
className="button button-normal button-slim button-first-outline"
|
||||
>
|
||||
Go to Dnote
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
@import '../App/font';
|
||||
@import '../App/variables';
|
||||
|
||||
.wrapper {
|
||||
background: $dark-light3;
|
||||
background: $light-gray;
|
||||
padding: rem(12px) rem(20px);
|
||||
height: $note-header-height;
|
||||
z-index: 2;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { connect } from 'react-redux';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Logo from '../Icons/LogoWithText';
|
||||
import { homePath } from '../../libs/paths';
|
||||
import { getHomePath } from '../../libs/paths';
|
||||
import styles from './SubscriptionsHeader.module.scss';
|
||||
|
||||
function SubscriptionsHeader({ userData }) {
|
||||
|
|
@ -30,7 +30,7 @@ function SubscriptionsHeader({ userData }) {
|
|||
return (
|
||||
<header className={styles.wrapper}>
|
||||
<div className={styles.content}>
|
||||
<Link to={homePath({})} className={styles.brand}>
|
||||
<Link to={getHomePath({})} className={styles.brand}>
|
||||
<Logo width={32} height={32} fill="black" className={styles.logo} />
|
||||
</Link>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
@import '../App/font';
|
||||
|
||||
|
|
@ -36,5 +36,5 @@
|
|||
|
||||
.email {
|
||||
@include font-size('regular');
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/font';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
|
||||
.wrapper {
|
||||
|
|
@ -28,10 +28,9 @@
|
|||
@include font-size('small');
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: $dark;
|
||||
color: white;
|
||||
padding: rem(8px) rem(12px);
|
||||
background: $dark-light;
|
||||
background: $light;
|
||||
color: $black;
|
||||
// border-top: 1px solid $border-color;
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/font';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
|
||||
.wrapper {
|
||||
|
|
@ -20,5 +20,5 @@
|
|||
.content {
|
||||
padding: rem(40px) rem(16px);
|
||||
text-align: center;
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/font';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
@import '../App/rem';
|
||||
|
||||
.wrapper {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { connect } from 'react-redux';
|
|||
|
||||
import SafeLink from '../Common/Link/SafeLink';
|
||||
import { closeNoteSidebar } from '../../actions/ui';
|
||||
import { notePath } from '../../libs/paths';
|
||||
import { getNotePath } from '../../libs/paths';
|
||||
import { nanosecToSec } from '../../helpers/time';
|
||||
import { excerpt } from '../../libs/string';
|
||||
import { getWindowWidth, noteSidebarThreshold } from '../../libs/ui';
|
||||
|
|
@ -104,7 +104,7 @@ function NoteItem({
|
|||
>
|
||||
<SafeLink
|
||||
className={styles.link}
|
||||
to={notePath(note.uuid, queryObj, { demo, isEditor: true })}
|
||||
to={getNotePath(note.uuid, queryObj, { demo, isEditor: true })}
|
||||
draggable="false"
|
||||
onClick={handleNoteItemClick}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
@import '../App/font';
|
||||
@import '../App/responsive';
|
||||
@import '../App/rem';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
|
||||
.wrapper {
|
||||
background: white;
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background: $third-light;
|
||||
background: $light-blue;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
|
||||
.active {
|
||||
.link {
|
||||
background: $third-light;
|
||||
background: $light-blue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import SearchInput from '../../Common/SearchInput';
|
|||
import Popover from '../../Common/Popover';
|
||||
import CaretIcon from '../../Icons/Caret';
|
||||
import CheckIcon from '../../Icons/Check';
|
||||
import { homePath, notePath, isNotePath } from '../../../libs/paths';
|
||||
import { getHomePath, getNotePath, isNotePath } from '../../../libs/paths';
|
||||
import { parseSearchString } from '../../../libs/url';
|
||||
import { getFacetsFromSearchStr } from '../../../libs/facets';
|
||||
|
||||
|
|
@ -77,12 +77,12 @@ function getOptionDestination({ demo, location, match, option }) {
|
|||
book: option.value
|
||||
};
|
||||
|
||||
ret = notePath(params.noteUUID, newSearchObj, {
|
||||
ret = getNotePath(params.noteUUID, newSearchObj, {
|
||||
demo,
|
||||
isEditor: true
|
||||
});
|
||||
} else {
|
||||
ret = homePath({ book: option.value }, { demo });
|
||||
ret = getHomePath({ book: option.value }, { demo });
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/font';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/responsive';
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
.trigger {
|
||||
padding: 0;
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
}
|
||||
|
||||
.button-content {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import { getCipherKey } from '../../../crypto';
|
|||
import BookFilter from './BookFilter';
|
||||
import { getFacetsFromSearchStr } from '../../../libs/facets';
|
||||
import { usePrevious } from '../../../libs/hooks';
|
||||
import { notePath, isHomePath } from '../../../libs/paths';
|
||||
import { getNotePath, isHomePath } from '../../../libs/paths';
|
||||
import SidebarToggle from '../../Common/SidebarToggle';
|
||||
import SubscriberWall from '../../Common/SubscriberWall';
|
||||
import { isEmptyObj } from '../../../libs/obj';
|
||||
|
|
@ -253,7 +253,7 @@ function useSelectFirstNote({ notesData, location, history, demo, user }) {
|
|||
const firstNote = firstItem.data;
|
||||
|
||||
const searchObj = parseSearchString(location.search);
|
||||
const dest = notePath(firstNote.uuid, searchObj, { demo, isEditor: true });
|
||||
const dest = getNotePath(firstNote.uuid, searchObj, { demo, isEditor: true });
|
||||
|
||||
history.replace(dest);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
@import '../../App/variables';
|
||||
|
||||
.sidebar {
|
||||
min-width: 100%;
|
||||
|
|
@ -18,7 +19,7 @@
|
|||
transform: translateX(0);
|
||||
height: 100%;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
position: relative;
|
||||
transform: initial;
|
||||
min-width: $note-sidebar-width;
|
||||
|
|
@ -86,11 +87,11 @@
|
|||
margin-left: rem(12px);
|
||||
margin-bottom: 0;
|
||||
@include font-size('large');
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/rem';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/font';
|
||||
|
||||
.wrapper {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/rem';
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { connect } from 'react-redux';
|
|||
|
||||
import DotsIcon from '../../Icons/Dots';
|
||||
import Menu from '../../Common/Menu';
|
||||
import { homePath } from '../../../libs/paths';
|
||||
import { getHomePath } from '../../../libs/paths';
|
||||
|
||||
import { removeNote } from '../../../actions/notes';
|
||||
import { resetNote } from '../../../actions/note';
|
||||
|
|
@ -66,7 +66,7 @@ function handleRemove({
|
|||
doResetEditor();
|
||||
|
||||
console.log('pushing');
|
||||
history.push(homePath());
|
||||
history.push(getHomePath());
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('err', err);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
display: block;
|
||||
|
||||
&:hover {
|
||||
background: $dark-light3;
|
||||
background: $light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
@import '../../App/variables';
|
||||
|
||||
.content {
|
||||
padding: rem(12px) rem(24px);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
@import '../../App/responsive';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
|
||||
|
|
@ -54,7 +55,7 @@
|
|||
min-height: rem(52px);
|
||||
display: flex;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +65,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -76,7 +77,7 @@
|
|||
|
||||
height: 100%;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +91,7 @@
|
|||
margin-left: rem(12px);
|
||||
margin-bottom: 0;
|
||||
@include font-size('large');
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
@ -98,7 +99,7 @@
|
|||
.desktop-book-selector {
|
||||
display: none;
|
||||
|
||||
@include breakpoint(lg) {
|
||||
@include overSidebarThreshold {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
@import '../App/responsive';
|
||||
@import '../App/rem';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
|
||||
.wrapper {
|
||||
// min-height: calc(100vh - 57px);
|
||||
background: $dark-light3;
|
||||
background: $light-gray;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ function Account({ userState }) {
|
|||
|
||||
<Header heading="Account" />
|
||||
<Body>
|
||||
<div className="container">
|
||||
<div className="container-wide">
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-12 col-lg-10">
|
||||
{successMsg && (
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import styles from './Placeholder.module.scss';
|
|||
|
||||
function Placeholder() {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="container-wide">
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-12 col-lg-10">
|
||||
<section className={settingsStyles.section}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
|
||||
.content1 {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../../App/rem';
|
||||
@import '../../App/font';
|
||||
@import '../../App/variables';
|
||||
@import '../../App/theme';
|
||||
|
||||
.wrapper {
|
||||
padding-top: rem(48px);
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
.desc {
|
||||
@include font-size('small');
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
max-width: rem(400px);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ function Content({
|
|||
doGetSubscription
|
||||
}) {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="container-wide">
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-12 col-lg-10">
|
||||
{successMsg && (
|
||||
|
|
@ -220,7 +220,7 @@ function Billing({
|
|||
|
||||
<Body>
|
||||
{subscriptionData.errorMessage && (
|
||||
<div className="container">
|
||||
<div className="container-wide">
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-12 col-lg-10">
|
||||
<Flash type="danger" wrapperClassName={settingsStyles.flash}>
|
||||
|
|
@ -232,7 +232,7 @@ function Billing({
|
|||
</div>
|
||||
)}
|
||||
{sourceData.errorMessage && (
|
||||
<div className="container">
|
||||
<div className="container-wide">
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-12 col-lg-10">
|
||||
<Flash type="danger" wrapperClassName={settingsStyles.flash}>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import Flash from '../../Common/Flash';
|
|||
import { getEmailPreference } from '../../../actions/auth';
|
||||
import FrequencyModal from './FrequencyModal';
|
||||
import SettingRow from '../SettingRow';
|
||||
import { settingsPath } from '../../../libs/paths';
|
||||
import { getSettingsPath } from '../../../libs/paths';
|
||||
|
||||
import settingsStyles from '../Settings.module.scss';
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ function Email({ emailPreferenceData, doGetEmailPreference }) {
|
|||
<Header heading="Notification" />
|
||||
|
||||
<Body>
|
||||
<div className="container">
|
||||
<div className="container-wide">
|
||||
{successMsg && (
|
||||
<div className="row">
|
||||
<div className="col-12 col-lg-10">
|
||||
|
|
@ -116,9 +116,9 @@ function Email({ emailPreferenceData, doGetEmailPreference }) {
|
|||
digests.
|
||||
</div>
|
||||
<Link
|
||||
to={settingsPath('account')}
|
||||
to={getSettingsPath('account')}
|
||||
className={classnames(
|
||||
'button button-second',
|
||||
'button button-normal button-second',
|
||||
settingsStyles['verification-banner-cta']
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '../App/rem';
|
||||
@import '../App/font';
|
||||
@import '../App/variables';
|
||||
@import '../App/theme';
|
||||
|
||||
.row {
|
||||
padding: rem(12px) rem(12px);
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
.desc {
|
||||
margin-bottom: 0;
|
||||
@include font-size('small');
|
||||
color: $dark-light5;
|
||||
color: $gray;
|
||||
}
|
||||
.action {
|
||||
display: flex;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue