Merge 'master' branch

Change-Id: Iee99400c7095770e61cde94b3b2c8eb0ddec463d
This commit is contained in:
Michal Niewrzal 2020-12-10 15:42:36 +01:00
commit b3acc1101a
97 changed files with 1234 additions and 2444 deletions

View File

@ -64,6 +64,7 @@
"sixcorners",
"alexottoboni",
"dominickmarino",
"hectorj2f"
"hectorj2f",
"nergdron"
]
}

View File

@ -8,6 +8,7 @@ COPY web/marketing/ /app/marketing
RUN npm install
RUN npm run build
# Fetch ca-certificates file for arch independent builds below
FROM alpine as ca-cert
RUN apk -U add ca-certificates

View File

@ -1,3 +1,7 @@
# Fetch ca-certificates file for arch independent builds below
FROM alpine as ca-cert
RUN apk -U add ca-certificates
ARG DOCKER_ARCH
FROM ${DOCKER_ARCH:-amd64}/alpine
ARG TAG
@ -5,7 +9,7 @@ ARG GOARCH
ENV GOARCH ${GOARCH}
EXPOSE 28967
WORKDIR /app
COPY resources/certs.pem /etc/ssl/certs/ca-certificates.crt
COPY --from=ca-cert /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY release/${TAG}/storagenode_linux_${GOARCH:-amd64} /app/storagenode
COPY cmd/storagenode/entrypoint /entrypoint
COPY cmd/storagenode/dashboard.sh /app/dashboard.sh

View File

@ -6,6 +6,7 @@ package main
import (
"context"
"fmt"
"math"
"os"
"time"
@ -132,7 +133,7 @@ func main() {
target := args[0]
if *retries <= 0 {
*retries = 1 << 31
*retries = math.MaxInt32
}
for try := 0; try < *retries; try++ {
if tryConnect(target) {

View File

@ -1,3 +1,7 @@
# Fetch ca-certificates file for arch independent builds below
FROM alpine as ca-cert
RUN apk -U add ca-certificates
ARG DOCKER_ARCH
FROM ${DOCKER_ARCH:-amd64}/alpine
ARG TAG
@ -9,6 +13,7 @@ ENV CONF_PATH=/root/.local/storj/uplink \
SATELLITE_ADDR=
WORKDIR /app
VOLUME /root/.local/storj/uplink
COPY --from=ca-cert /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY release/${TAG}/uplink_linux_${GOARCH:-amd64} /app/uplink
COPY cmd/uplink/entrypoint /entrypoint
ENTRYPOINT ["/entrypoint"]

4
go.mod
View File

@ -42,9 +42,9 @@ require (
golang.org/x/sys v0.0.0-20200929083018-4d22bbb62b3c
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
google.golang.org/api v0.20.0 // indirect
storj.io/common v0.0.0-20201204143755-a03c37168cb1
storj.io/common v0.0.0-20201207172416-78f4e59925c3
storj.io/drpc v0.0.16
storj.io/monkit-jaeger v0.0.0-20200518165323-80778fc3f91b
storj.io/private v0.0.0-20201026143115-bc926bfa3bca
storj.io/private v0.0.0-20201126162939-6fbb1e924f51
storj.io/uplink v1.3.2-0.20201204110139-e7cbbe88e717
)

8
go.sum
View File

@ -903,15 +903,15 @@ sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3
storj.io/common v0.0.0-20200424175742-65ac59022f4f/go.mod h1:pZyXiIE7bGETIRXtfs0nICqMwp7PM8HqnDuyUeldNA0=
storj.io/common v0.0.0-20201026135900-1aaeec90670b/go.mod h1:GqdmNf3fLm2UZX/7Zr0BLFCJ4gFjgm6eHrk/fnmr5jQ=
storj.io/common v0.0.0-20201124202331-31c1d1dc486d/go.mod h1:ocAfQaE1dpflrdTr8hXRZTWP1bq2jXz7ieGSBVCmHEc=
storj.io/common v0.0.0-20201204143755-a03c37168cb1 h1:SwSIESeyaX3kOhZN1jeNPbegSraFTdxtWD+Dn0dT7y4=
storj.io/common v0.0.0-20201204143755-a03c37168cb1/go.mod h1:6sepaQTRLuygvA+GNPzdgRPOB1+wFfjde76KBWofbMY=
storj.io/common v0.0.0-20201207172416-78f4e59925c3 h1:D+rAQBzjl0Mw3VQ+1Sjv5/53I7JaIymMrkDW5DYBgRE=
storj.io/common v0.0.0-20201207172416-78f4e59925c3/go.mod h1:6sepaQTRLuygvA+GNPzdgRPOB1+wFfjde76KBWofbMY=
storj.io/drpc v0.0.11/go.mod h1:TiFc2obNjL9/3isMW1Rpxjy8V9uE0B2HMeMFGiiI7Iw=
storj.io/drpc v0.0.14/go.mod h1:82nfl+6YwRwF6UG31cEWWUqv/FaKvP5SGqUvoqTxCMA=
storj.io/drpc v0.0.16 h1:9sxypc5lKi/0D69cR21BR0S21+IvXfON8L5nXMVNTwQ=
storj.io/drpc v0.0.16/go.mod h1:zdmQ93nx4Z35u11pQ+GAnBy4DGOK3HJCSOfeh2RryTo=
storj.io/monkit-jaeger v0.0.0-20200518165323-80778fc3f91b h1:Bbg9JCtY6l3HrDxs3BXzT2UYnYCBLqNi6i84Y8QIPUs=
storj.io/monkit-jaeger v0.0.0-20200518165323-80778fc3f91b/go.mod h1:gj4vuCeyCRjRmH8LIrgoyU9Dc9uR6H+/GcDUXmTbf80=
storj.io/private v0.0.0-20201026143115-bc926bfa3bca h1:ekR7vtUYC5+cDyim0ZJaSZeXidyzQqDYsnFPYXgTozc=
storj.io/private v0.0.0-20201026143115-bc926bfa3bca/go.mod h1:EaLnIyNyqWQUJB+7+KWVez0In9czl0nHHlm2WobebuA=
storj.io/private v0.0.0-20201126162939-6fbb1e924f51 h1:3aNbTNJeZ00cnzgYFdGyBUxtBEYqBCEzvk6Svh0gIwc=
storj.io/private v0.0.0-20201126162939-6fbb1e924f51/go.mod h1:3KcGiA7phL3a0HUCe5ar90SlIU3iFb8hKInaEZQ5P7o=
storj.io/uplink v1.3.2-0.20201204110139-e7cbbe88e717 h1:a1VgBKF3XKs8NY4AMFyVMjWYCskExlN8OUmCBKHEjbc=
storj.io/uplink v1.3.2-0.20201204110139-e7cbbe88e717/go.mod h1:OmI3WhL5JFxBUt3v+q7RtW1RMndMvtHB5Dp6YN8ro3c=

View File

@ -180,6 +180,16 @@ func (client *Uplink) DialPiecestore(ctx context.Context, destination Peer) (*pi
return piecestore.DialNodeURL(ctx, client.Dialer, destination.NodeURL(), client.Log.Named("uplink>piecestore"), piecestore.DefaultConfig)
}
// OpenProject opens project with predefined access grant and gives access to pure uplink API.
func (client *Uplink) OpenProject(ctx context.Context, satellite *Satellite) (*uplink.Project, error) {
_, found := testuplink.GetMaxSegmentSize(ctx)
if !found {
ctx = testuplink.WithMaxSegmentSize(ctx, satellite.Config.Metainfo.MaxSegmentSize)
}
return uplink.OpenProject(ctx, client.Access[satellite.ID()])
}
// Upload data to specific satellite.
func (client *Uplink) Upload(ctx context.Context, satellite *Satellite, bucket string, path storj.Path, data []byte) error {
return client.UploadWithExpiration(ctx, satellite, bucket, path, data, time.Time{})

View File

@ -276,3 +276,16 @@ func TestDeleteWithOfflineStoragenode(t *testing.T) {
require.Equal(t, 0, len(objects))
})
}
func TestUplinkOpenProject(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
project, err := planet.Uplinks[0].OpenProject(ctx, planet.Satellites[0])
require.NoError(t, err)
defer ctx.Check(project.Close)
_, err = project.EnsureBucket(ctx, "bucket-name")
require.NoError(t, err)
})
}

View File

@ -1,27 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----

View File

@ -12,6 +12,18 @@ import (
"storj.io/storj/satellite/metainfo/metabase"
)
// ListAllBucketsCursor defines cursor for ListAllBuckets listing.
type ListAllBucketsCursor struct {
ProjectID uuid.UUID
BucketName []byte
}
// ListAllBucketsOptions defines ListAllBuckets listing options.
type ListAllBucketsOptions struct {
Cursor ListAllBucketsCursor
Limit int
}
// BucketsDB is the interface for the database to interact with buckets.
//
// architecture: Database
@ -28,6 +40,8 @@ type BucketsDB interface {
DeleteBucket(ctx context.Context, bucketName []byte, projectID uuid.UUID) (err error)
// List returns all buckets for a project
ListBuckets(ctx context.Context, projectID uuid.UUID, listOpts storj.BucketListOptions, allowedBuckets macaroon.AllowedBuckets) (bucketList storj.BucketList, err error)
// ListAllBuckets returns a list of all buckets.
ListAllBuckets(ctx context.Context, listOpts ListAllBucketsOptions) (bucketList storj.BucketList, err error)
// CountBuckets returns the number of buckets a project currently has
CountBuckets(ctx context.Context, projectID uuid.UUID) (int, error)
}

View File

@ -11,6 +11,7 @@ import (
"storj.io/common/macaroon"
"storj.io/common/storj"
"storj.io/common/uuid"
"storj.io/storj/satellite/metainfo"
"storj.io/storj/satellite/metainfo/metabase"
"storj.io/storj/satellite/satellitedb/dbx"
)
@ -205,6 +206,63 @@ func (db *bucketsDB) ListBuckets(ctx context.Context, projectID uuid.UUID, listO
return bucketList, nil
}
// ListAllBuckets returns a list of all buckets.
func (db *bucketsDB) ListAllBuckets(ctx context.Context, listOpts metainfo.ListAllBucketsOptions) (bucketList storj.BucketList, err error) {
defer mon.Task()(&ctx)(&err)
const defaultListLimit = 10000
if listOpts.Limit < 1 || listOpts.Limit > defaultListLimit {
listOpts.Limit = defaultListLimit
}
limit := listOpts.Limit + 1 // add one to detect More
if listOpts.Cursor.BucketName == nil {
listOpts.Cursor.BucketName = []byte{}
}
for {
dbxBuckets, err := db.db.Limited_BucketMetainfo_By_ProjectId_GreaterOrEqual_And_Name_Greater_OrderBy_Asc_ProjectId_Name(ctx,
dbx.BucketMetainfo_ProjectId(listOpts.Cursor.ProjectID[:]),
dbx.BucketMetainfo_Name(listOpts.Cursor.BucketName),
limit,
0,
)
if err != nil {
return bucketList, storj.ErrBucket.Wrap(err)
}
bucketList.More = len(dbxBuckets) > listOpts.Limit
if bucketList.More {
// If there are more buckets than listOpts.limit returned,
// then remove the extra buckets so that we do not return
// more then the limit
dbxBuckets = dbxBuckets[0:listOpts.Limit]
}
if bucketList.Items == nil {
bucketList.Items = make([]storj.Bucket, 0, len(dbxBuckets))
}
for _, dbxBucket := range dbxBuckets {
item, err := convertDBXtoBucket(dbxBucket)
if err != nil {
return bucketList, storj.ErrBucket.Wrap(err)
}
bucketList.Items = append(bucketList.Items, item)
}
if len(bucketList.Items) < listOpts.Limit && bucketList.More {
lastBucket := bucketList.Items[len(bucketList.Items)-1]
listOpts.Cursor.ProjectID = lastBucket.ProjectID
listOpts.Cursor.BucketName = []byte(lastBucket.Name)
continue
}
break
}
return bucketList, nil
}
// CountBuckets returns the number of buckets a project currently has.
func (db *bucketsDB) CountBuckets(ctx context.Context, projectID uuid.UUID) (count int, err error) {
count64, err := db.db.Count_BucketMetainfo_Name_By_ProjectId(ctx, dbx.BucketMetainfo_ProjectId(projectID[:]))

View File

@ -0,0 +1,103 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package satellitedb_test
import (
"bytes"
"strconv"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"storj.io/common/storj"
"storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/storj/satellite"
"storj.io/storj/satellite/console"
"storj.io/storj/satellite/metainfo"
"storj.io/storj/satellite/satellitedb/satellitedbtest"
)
func TestListAllBuckets(t *testing.T) {
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
// no buckets
list, err := db.Buckets().ListAllBuckets(ctx, metainfo.ListAllBucketsOptions{})
require.NoError(t, err)
require.Equal(t, 0, len(list.Items))
first, err := db.Console().Projects().Insert(ctx, &console.Project{
Name: "first",
ID: testrand.UUID(),
})
require.NoError(t, err)
second, err := db.Console().Projects().Insert(ctx, &console.Project{
Name: "second",
ID: testrand.UUID(),
})
require.NoError(t, err)
projects := []*console.Project{first, second}
if bytes.Compare(first.ID[:], second.ID[:]) > 0 {
projects = []*console.Project{second, first}
}
buckets := make([]storj.Bucket, 10)
for i, project := range projects {
for index := 0; index < (len(buckets) / 2); index++ {
var err error
buckets[index+(i*5)], err = db.Buckets().CreateBucket(ctx, storj.Bucket{
ID: testrand.UUID(),
Name: "bucket-test-" + strconv.Itoa(index),
ProjectID: project.ID,
})
require.NoError(t, err)
}
}
list, err = db.Buckets().ListAllBuckets(ctx, metainfo.ListAllBucketsOptions{})
require.NoError(t, err)
require.Equal(t, len(buckets), len(list.Items))
require.False(t, list.More)
require.Zero(t, cmp.Diff(buckets, list.Items))
list, err = db.Buckets().ListAllBuckets(ctx, metainfo.ListAllBucketsOptions{
Cursor: metainfo.ListAllBucketsCursor{
ProjectID: projects[1].ID,
},
})
require.NoError(t, err)
require.Equal(t, len(buckets)/2, len(list.Items))
require.Zero(t, cmp.Diff(buckets[len(buckets)/2:], list.Items))
list, err = db.Buckets().ListAllBuckets(ctx, metainfo.ListAllBucketsOptions{
Cursor: metainfo.ListAllBucketsCursor{
ProjectID: projects[1].ID,
BucketName: []byte("bucket-test-2"),
},
})
require.NoError(t, err)
require.Equal(t, 2, len(list.Items))
require.False(t, list.More)
require.Zero(t, cmp.Diff(buckets[8:], list.Items))
list, err = db.Buckets().ListAllBuckets(ctx, metainfo.ListAllBucketsOptions{
Cursor: metainfo.ListAllBucketsCursor{
ProjectID: projects[1].ID,
BucketName: []byte("bucket-test-4"),
},
})
require.NoError(t, err)
require.Equal(t, 0, len(list.Items))
list, err = db.Buckets().ListAllBuckets(ctx, metainfo.ListAllBucketsOptions{
Limit: 2,
})
require.NoError(t, err)
require.Equal(t, 2, len(list.Items))
require.True(t, list.More)
require.Zero(t, cmp.Diff(buckets[:2], list.Items))
})
}

View File

@ -1002,6 +1002,13 @@ read limitoffset ( // After
orderby asc bucket_metainfo.name
)
read limitoffset ( // After
select bucket_metainfo
where bucket_metainfo.project_id >= ?
where bucket_metainfo.name > ?
orderby asc bucket_metainfo.project_id bucket_metainfo.name
)
read count (
select bucket_metainfo.name
where bucket_metainfo.project_id = ?

View File

@ -12747,6 +12747,56 @@ func (obj *pgxImpl) Limited_BucketMetainfo_By_ProjectId_And_Name_Greater_OrderBy
}
func (obj *pgxImpl) Limited_BucketMetainfo_By_ProjectId_GreaterOrEqual_And_Name_Greater_OrderBy_Asc_ProjectId_Name(ctx context.Context,
bucket_metainfo_project_id_greater_or_equal BucketMetainfo_ProjectId_Field,
bucket_metainfo_name_greater BucketMetainfo_Name_Field,
limit int, offset int64) (
rows []*BucketMetainfo, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("SELECT bucket_metainfos.id, bucket_metainfos.project_id, bucket_metainfos.name, bucket_metainfos.partner_id, bucket_metainfos.path_cipher, bucket_metainfos.created_at, bucket_metainfos.default_segment_size, bucket_metainfos.default_encryption_cipher_suite, bucket_metainfos.default_encryption_block_size, bucket_metainfos.default_redundancy_algorithm, bucket_metainfos.default_redundancy_share_size, bucket_metainfos.default_redundancy_required_shares, bucket_metainfos.default_redundancy_repair_shares, bucket_metainfos.default_redundancy_optimal_shares, bucket_metainfos.default_redundancy_total_shares FROM bucket_metainfos WHERE bucket_metainfos.project_id >= ? AND bucket_metainfos.name > ? ORDER BY bucket_metainfos.project_id, bucket_metainfos.name LIMIT ? OFFSET ?")
var __values []interface{}
__values = append(__values, bucket_metainfo_project_id_greater_or_equal.value(), bucket_metainfo_name_greater.value())
__values = append(__values, limit, offset)
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
for {
rows, err = func() (rows []*BucketMetainfo, err error) {
__rows, err := obj.driver.QueryContext(ctx, __stmt, __values...)
if err != nil {
return nil, err
}
defer __rows.Close()
for __rows.Next() {
bucket_metainfo := &BucketMetainfo{}
err = __rows.Scan(&bucket_metainfo.Id, &bucket_metainfo.ProjectId, &bucket_metainfo.Name, &bucket_metainfo.PartnerId, &bucket_metainfo.PathCipher, &bucket_metainfo.CreatedAt, &bucket_metainfo.DefaultSegmentSize, &bucket_metainfo.DefaultEncryptionCipherSuite, &bucket_metainfo.DefaultEncryptionBlockSize, &bucket_metainfo.DefaultRedundancyAlgorithm, &bucket_metainfo.DefaultRedundancyShareSize, &bucket_metainfo.DefaultRedundancyRequiredShares, &bucket_metainfo.DefaultRedundancyRepairShares, &bucket_metainfo.DefaultRedundancyOptimalShares, &bucket_metainfo.DefaultRedundancyTotalShares)
if err != nil {
return nil, err
}
rows = append(rows, bucket_metainfo)
}
err = __rows.Err()
if err != nil {
return nil, err
}
return rows, nil
}()
if err != nil {
if obj.shouldRetry(err) {
continue
}
return nil, obj.makeErr(err)
}
return rows, nil
}
}
func (obj *pgxImpl) Count_BucketMetainfo_Name_By_ProjectId(ctx context.Context,
bucket_metainfo_project_id BucketMetainfo_ProjectId_Field) (
count int64, err error) {
@ -19534,6 +19584,56 @@ func (obj *pgxcockroachImpl) Limited_BucketMetainfo_By_ProjectId_And_Name_Greate
}
func (obj *pgxcockroachImpl) Limited_BucketMetainfo_By_ProjectId_GreaterOrEqual_And_Name_Greater_OrderBy_Asc_ProjectId_Name(ctx context.Context,
bucket_metainfo_project_id_greater_or_equal BucketMetainfo_ProjectId_Field,
bucket_metainfo_name_greater BucketMetainfo_Name_Field,
limit int, offset int64) (
rows []*BucketMetainfo, err error) {
defer mon.Task()(&ctx)(&err)
var __embed_stmt = __sqlbundle_Literal("SELECT bucket_metainfos.id, bucket_metainfos.project_id, bucket_metainfos.name, bucket_metainfos.partner_id, bucket_metainfos.path_cipher, bucket_metainfos.created_at, bucket_metainfos.default_segment_size, bucket_metainfos.default_encryption_cipher_suite, bucket_metainfos.default_encryption_block_size, bucket_metainfos.default_redundancy_algorithm, bucket_metainfos.default_redundancy_share_size, bucket_metainfos.default_redundancy_required_shares, bucket_metainfos.default_redundancy_repair_shares, bucket_metainfos.default_redundancy_optimal_shares, bucket_metainfos.default_redundancy_total_shares FROM bucket_metainfos WHERE bucket_metainfos.project_id >= ? AND bucket_metainfos.name > ? ORDER BY bucket_metainfos.project_id, bucket_metainfos.name LIMIT ? OFFSET ?")
var __values []interface{}
__values = append(__values, bucket_metainfo_project_id_greater_or_equal.value(), bucket_metainfo_name_greater.value())
__values = append(__values, limit, offset)
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
for {
rows, err = func() (rows []*BucketMetainfo, err error) {
__rows, err := obj.driver.QueryContext(ctx, __stmt, __values...)
if err != nil {
return nil, err
}
defer __rows.Close()
for __rows.Next() {
bucket_metainfo := &BucketMetainfo{}
err = __rows.Scan(&bucket_metainfo.Id, &bucket_metainfo.ProjectId, &bucket_metainfo.Name, &bucket_metainfo.PartnerId, &bucket_metainfo.PathCipher, &bucket_metainfo.CreatedAt, &bucket_metainfo.DefaultSegmentSize, &bucket_metainfo.DefaultEncryptionCipherSuite, &bucket_metainfo.DefaultEncryptionBlockSize, &bucket_metainfo.DefaultRedundancyAlgorithm, &bucket_metainfo.DefaultRedundancyShareSize, &bucket_metainfo.DefaultRedundancyRequiredShares, &bucket_metainfo.DefaultRedundancyRepairShares, &bucket_metainfo.DefaultRedundancyOptimalShares, &bucket_metainfo.DefaultRedundancyTotalShares)
if err != nil {
return nil, err
}
rows = append(rows, bucket_metainfo)
}
err = __rows.Err()
if err != nil {
return nil, err
}
return rows, nil
}()
if err != nil {
if obj.shouldRetry(err) {
continue
}
return nil, obj.makeErr(err)
}
return rows, nil
}
}
func (obj *pgxcockroachImpl) Count_BucketMetainfo_Name_By_ProjectId(ctx context.Context,
bucket_metainfo_project_id BucketMetainfo_ProjectId_Field) (
count int64, err error) {
@ -24069,6 +24169,18 @@ func (rx *Rx) Limited_BucketMetainfo_By_ProjectId_And_Name_Greater_OrderBy_Asc_N
return tx.Limited_BucketMetainfo_By_ProjectId_And_Name_Greater_OrderBy_Asc_Name(ctx, bucket_metainfo_project_id, bucket_metainfo_name_greater, limit, offset)
}
func (rx *Rx) Limited_BucketMetainfo_By_ProjectId_GreaterOrEqual_And_Name_Greater_OrderBy_Asc_ProjectId_Name(ctx context.Context,
bucket_metainfo_project_id_greater_or_equal BucketMetainfo_ProjectId_Field,
bucket_metainfo_name_greater BucketMetainfo_Name_Field,
limit int, offset int64) (
rows []*BucketMetainfo, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.Limited_BucketMetainfo_By_ProjectId_GreaterOrEqual_And_Name_Greater_OrderBy_Asc_ProjectId_Name(ctx, bucket_metainfo_project_id_greater_or_equal, bucket_metainfo_name_greater, limit, offset)
}
func (rx *Rx) Limited_CoinpaymentsTransaction_By_CreatedAt_LessOrEqual_And_Status_OrderBy_Desc_CreatedAt(ctx context.Context,
coinpayments_transaction_created_at_less_or_equal CoinpaymentsTransaction_CreatedAt_Field,
coinpayments_transaction_status CoinpaymentsTransaction_Status_Field,
@ -25146,6 +25258,12 @@ type Methods interface {
limit int, offset int64) (
rows []*BucketMetainfo, err error)
Limited_BucketMetainfo_By_ProjectId_GreaterOrEqual_And_Name_Greater_OrderBy_Asc_ProjectId_Name(ctx context.Context,
bucket_metainfo_project_id_greater_or_equal BucketMetainfo_ProjectId_Field,
bucket_metainfo_name_greater BucketMetainfo_Name_Field,
limit int, offset int64) (
rows []*BucketMetainfo, err error)
Limited_CoinpaymentsTransaction_By_CreatedAt_LessOrEqual_And_Status_OrderBy_Desc_CreatedAt(ctx context.Context,
coinpayments_transaction_created_at_less_or_equal CoinpaymentsTransaction_CreatedAt_Field,
coinpayments_transaction_status CoinpaymentsTransaction_Status_Field,

View File

@ -8,4 +8,5 @@ type DiskSpaceInfo struct {
Used int64 `json:"used"`
Available int64 `json:"available"`
Trash int64 `json:"trash"`
Overused int64 `json:"overused"`
}

View File

@ -5,6 +5,7 @@ package console
import (
"context"
"math"
"time"
"github.com/spacemonkeygo/monkit/v3"
@ -210,6 +211,11 @@ func (s *Service) GetDashboardData(ctx context.Context) (_ *Dashboard, err error
Trash: trash,
}
overused := s.allocatedDiskSpace.Int64() - pieceTotal - trash
if overused < 0 {
data.DiskSpace.Overused = int64(math.Abs(float64(overused)))
}
data.Bandwidth = BandwidthInfo{
Used: bandwidthUsage,
}

View File

@ -36,28 +36,28 @@ export default class ProgressBar extends Vue {
* Indicates if current route is on name step.
*/
public get isNameStep(): boolean {
return this.$route.name === RouteConfig.NameStep.name;
return this.$route.name === RouteConfig.NameStep.name || this.$route.name === RouteConfig.AccessGrantName.name;
}
/**
* Indicates if current route is on permissions step.
*/
public get isPermissionsStep(): boolean {
return this.$route.name === RouteConfig.PermissionsStep.name;
return this.$route.name === RouteConfig.PermissionsStep.name || this.$route.name === RouteConfig.AccessGrantPermissions.name;
}
/**
* Indicates if current route is on passphrase step.
*/
public get isPassphraseStep(): boolean {
return this.$route.name === RouteConfig.CreatePassphraseStep.name || this.$route.name === RouteConfig.EnterPassphraseStep.name;
return this.$route.name === RouteConfig.CreatePassphraseStep.name || this.$route.name === RouteConfig.EnterPassphraseStep.name || this.$route.name === RouteConfig.AccessGrantPassphrase.name;
}
/**
* Indicates if current route is on result step.
*/
public get isResultStep(): boolean {
return this.$route.name === RouteConfig.ResultStep.name;
return this.$route.name === RouteConfig.ResultStep.name || this.$route.name === RouteConfig.AccessGrantResult.name;
}
}
</script>

View File

@ -5,6 +5,7 @@
<div class="buckets-selection">
<div
class="buckets-selection__toggle-container"
:class="{ disabled: isOnboardingTour }"
@click.stop="toggleDropdown"
>
<h1 class="buckets-selection__toggle-container__name">{{ selectionLabel }}</h1>
@ -28,6 +29,8 @@ import BucketsDropdown from '@/components/accessGrants/permissions/BucketsDropdo
import ExpandIcon from '@/../static/images/common/BlackArrowExpand.svg';
import { RouteConfig } from '@/router';
@Component({
components: {
ExpandIcon,
@ -41,6 +44,8 @@ export default class BucketsSelection extends Vue {
* Toggles dropdown visibility.
*/
public toggleDropdown(): void {
if (this.isOnboardingTour) return;
this.isDropdownShown = !this.isDropdownShown;
}
@ -51,6 +56,13 @@ export default class BucketsSelection extends Vue {
this.isDropdownShown = false;
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
/**
* Returns selection options (all or items count).
*/
@ -90,6 +102,7 @@ export default class BucketsSelection extends Vue {
justify-content: space-between;
padding: 15px 20px;
width: calc(100% - 40px);
border-radius: 6px;
&__name {
font-style: normal;
@ -101,4 +114,10 @@ export default class BucketsSelection extends Vue {
}
}
}
.disabled {
pointer-events: none;
background: #f5f6fa;
border: 1px solid rgba(56, 75, 101, 0.4);
}
</style>

View File

@ -2,7 +2,7 @@
// See LICENSE for copying information.
<template>
<div class="create-passphrase">
<div class="create-passphrase" :class="{ 'border-radius': isOnboardingTour }">
<BackIcon class="create-passphrase__back-icon" @click="onBackClick"/>
<h1 class="create-passphrase__title">Encryption Passphrase</h1>
<div class="create-passphrase__warning">
@ -195,6 +195,18 @@ export default class CreatePassphraseStep extends Vue {
setTimeout(() => {
this.isLoading = false;
if (this.isOnboardingTour) {
this.$router.push({
name: RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant.with(RouteConfig.AccessGrantResult)).name,
params: {
access: this.access,
key: this.key,
},
});
return;
}
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.ResultStep)).name,
params: {
@ -210,6 +222,17 @@ export default class CreatePassphraseStep extends Vue {
* Redirects to previous step.
*/
public onBackClick(): void {
if (this.isOnboardingTour) {
this.$router.push({
name: RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant.with(RouteConfig.AccessGrantPermissions)).name,
params: {
key: this.key,
},
});
return;
}
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).name,
params: {
@ -217,6 +240,13 @@ export default class CreatePassphraseStep extends Vue {
},
});
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
</script>
@ -363,6 +393,10 @@ export default class CreatePassphraseStep extends Vue {
border-bottom: 3px solid #0068dc;
}
.border-radius {
border-radius: 6px;
}
/deep/ .label-container {
&__main {

View File

@ -2,7 +2,7 @@
// See LICENSE for copying information.
<template>
<div class="name-step">
<div class="name-step" :class="{ 'border-radius': isOnboardingTour }">
<h1 class="name-step__title">Name Your Access Grant</h1>
<p class="name-step__sub-title">Enter a name for your new Access grant to get started.</p>
<HeaderedInput
@ -14,6 +14,7 @@
/>
<div class="name-step__buttons-area">
<VButton
v-if="!isOnboardingTour"
class="cancel-button"
label="Cancel"
width="50%"
@ -84,7 +85,7 @@ export default class NameStep extends Vue {
}
if (!this.name) {
this.errorMessage = 'Access Grant name can`t be empty';
this.errorMessage = 'Access Grant name can\'t be empty';
return;
}
@ -113,6 +114,18 @@ export default class NameStep extends Vue {
}
this.isLoading = false;
if (this.isOnboardingTour) {
await this.$router.push({
name: RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant.with(RouteConfig.AccessGrantPermissions)).name,
params: {
key: this.key,
},
});
return;
}
await this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.PermissionsStep)).name,
params: {
@ -120,6 +133,13 @@ export default class NameStep extends Vue {
},
});
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
</script>
@ -161,6 +181,7 @@ export default class NameStep extends Vue {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: 130px;
}
}
@ -168,4 +189,8 @@ export default class NameStep extends Vue {
.cancel-button {
margin-right: 15px;
}
.border-radius {
border-radius: 6px;
}
</style>

View File

@ -2,7 +2,7 @@
// See LICENSE for copying information.
<template>
<div class="permissions">
<div class="permissions" :class="{ 'border-radius': isOnboardingTour }">
<BackIcon class="permissions__back-icon" @click="onBackClick"/>
<h1 class="permissions__title">Access Permissions</h1>
<p class="permissions__sub-title">
@ -55,7 +55,7 @@
:on-press="onContinueInBrowserClick"
:is-disabled="isLoading"
/>
<p class="permissions__cli-link" @click.stop="onContinueInCLIClick">
<p v-if="!isOnboardingTour" class="permissions__cli-link" @click.stop="onContinueInCLIClick">
Continue in CLI
</p>
</div>
@ -179,6 +179,17 @@ export default class PermissionsStep extends Vue {
setTimeout(() => {
this.isLoading = false;
if (this.isOnboardingTour) {
this.$router.push({
name: RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant.with(RouteConfig.AccessGrantPassphrase)).name,
params: {
key: this.restrictedKey,
},
});
return;
}
if (this.accessGrantsAmount > 1) {
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.EnterPassphraseStep)).name,
@ -199,6 +210,13 @@ export default class PermissionsStep extends Vue {
}, 1000);
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
/**
* Returns stored selected bucket names.
*/
@ -359,4 +377,8 @@ export default class PermissionsStep extends Vue {
color: #0068dc;
}
}
.border-radius {
border-radius: 6px;
}
</style>

View File

@ -2,7 +2,7 @@
// See LICENSE for copying information.
<template>
<div class="generate-grant">
<div class="generate-grant" :class="{ 'border-radius': isOnboardingTour }">
<BackIcon class="generate-grant__back-icon" @click="onBackClick"/>
<h1 class="generate-grant__title">Generate Access Grant</h1>
<div class="generate-grant__warning">
@ -192,6 +192,17 @@ export default class ResultStep extends Vue {
* Redirects to previous step.
*/
public onBackClick(): void {
if (this.isOnboardingTour) {
this.$router.push({
name: RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant.with(RouteConfig.AccessGrantPassphrase)).name,
params: {
key: this.$route.params.key,
},
});
return;
}
if (this.accessGrantsAmount > 1) {
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.EnterPassphraseStep)).name,
@ -216,6 +227,12 @@ export default class ResultStep extends Vue {
* Proceed to upload data step.
*/
public onDoneClick(): void {
if (this.isOnboardingTour) {
this.$router.push(RouteConfig.ProjectDashboard.path);
return;
}
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessGrant.with(RouteConfig.UploadStep)).name,
params: {
@ -243,6 +260,13 @@ export default class ResultStep extends Vue {
}
}
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
/**
* Returns generated gateway credentials from store.
*/
@ -292,7 +316,7 @@ export default class ResultStep extends Vue {
&__warning {
padding: 20px;
width: calc(100% - 40px);
width: calc(100% - 42px);
background: #fff9f7;
border: 1px solid #f84b00;
border-radius: 8px;
@ -336,7 +360,7 @@ export default class ResultStep extends Vue {
align-items: center;
border-radius: 9px;
padding: 10px;
width: calc(100% - 20px);
width: calc(100% - 22px);
border: 1px solid rgba(56, 75, 101, 0.4);
&__value {
@ -446,4 +470,8 @@ export default class ResultStep extends Vue {
margin-top: 30px;
}
}
.border-radius {
border-radius: 6px;
}
</style>

View File

@ -199,7 +199,7 @@ export default class TokenDepositSelection extends Vue {
* Indicates if app state is in onboarding tour state.
*/
private get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name;
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
</script>

View File

@ -20,7 +20,7 @@ import { Component, Vue } from 'vue-property-decorator';
import { AuthHttpApi } from '@/api/auth';
import { RouteConfig } from '@/router';
import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { USER_ACTIONS } from '@/store/modules/users';
@ -58,7 +58,7 @@ export default class AccountDropdown extends Vue {
await this.$store.dispatch(PM_ACTIONS.CLEAR);
await this.$store.dispatch(PROJECTS_ACTIONS.CLEAR);
await this.$store.dispatch(USER_ACTIONS.CLEAR);
await this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR);
await this.$store.dispatch(NOTIFICATION_ACTIONS.CLEAR);
await this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
await this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);

View File

@ -39,7 +39,7 @@ import { Component, Vue } from 'vue-property-decorator';
import SelectionIcon from '@/../static/images/header/selection.svg';
import { RouteConfig } from '@/router';
import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
@ -68,7 +68,7 @@ export default class ProjectDropdown extends Vue {
try {
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await this.$store.dispatch(PM_ACTIONS.FETCH, this.FIRST_PAGE);
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH, this.FIRST_PAGE);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, this.FIRST_PAGE);
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, this.FIRST_PAGE);
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.$store.getters.selectedProject.id);
} catch (error) {

View File

@ -60,7 +60,7 @@ export default class ProjectSelection extends Vue {
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name;
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
/**

View File

@ -24,7 +24,7 @@ export default class ResourcesDropdown extends Vue {
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name;
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
</script>

View File

@ -43,7 +43,7 @@ export default class ResourcesSelection extends Vue {
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name;
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
/**

View File

@ -43,7 +43,7 @@ export default class SettingsSelection extends Vue {
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name;
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
/**

View File

@ -24,7 +24,7 @@ import { Component, Vue } from 'vue-property-decorator';
import EditProjectDropdown from '@/components/navigation/EditProjectDropdown.vue';
import ApiKeysIcon from '@/../static/images/navigation/apiKeys.svg';
import AccessGrantsIcon from '@/../static/images/navigation/apiKeys.svg';
import DashboardIcon from '@/../static/images/navigation/dashboard.svg';
import TeamIcon from '@/../static/images/navigation/team.svg';
@ -34,7 +34,7 @@ import { NavigationLink } from '@/types/navigation';
@Component({
components: {
DashboardIcon,
ApiKeysIcon,
AccessGrantsIcon,
TeamIcon,
EditProjectDropdown,
},
@ -45,7 +45,7 @@ export default class NavigationArea extends Vue {
*/
public readonly navigation: NavigationLink[] = [
RouteConfig.ProjectDashboard.withIcon(DashboardIcon),
RouteConfig.ApiKeys.withIcon(ApiKeysIcon),
RouteConfig.AccessGrants.withIcon(AccessGrantsIcon),
RouteConfig.Users.withIcon(TeamIcon),
];
@ -67,7 +67,7 @@ export default class NavigationArea extends Vue {
* Indicates if current route is onboarding tour.
*/
private get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name;
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
</script>

View File

@ -11,34 +11,7 @@
<CloseImage class="tour-area__info-bar__close-img" @click="disableInfoBar"/>
</div>
<div class="tour-area__content">
<ProgressBar
:is-paywall-enabled="isPaywallEnabled"
:is-add-payment-step="isAddPaymentState"
:is-create-project-step="isCreateProjectState"
:is-create-api-key-step="isCreatApiKeyState"
:is-upload-data-step="isUploadDataState"
/>
<OverviewStep
v-if="isDefaultState && isPaywallEnabled"
@setAddPaymentState="setAddPaymentState"
/>
<OverviewStepNoPaywall
v-if="isDefaultState && !isPaywallEnabled"
@setCreateProjectState="setCreateProjectState"
/>
<AddPaymentStep
v-if="isAddPaymentState"
@setProjectState="setCreateProjectState"
/>
<CreateProjectStep
v-if="isCreateProjectState"
@setApiKeyState="setCreateApiKeyState"
/>
<CreateApiKeyStep
v-if="isCreatApiKeyState"
@setUploadDataState="setUploadDataState"
/>
<UploadDataStep v-if="isUploadDataState"/>
<router-view/>
<img
v-if="isAddPaymentState"
class="tour-area__content__tardigrade"
@ -52,36 +25,16 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ProgressBar from '@/components/onboardingTour/ProgressBar.vue';
import AddPaymentStep from '@/components/onboardingTour/steps/AddPaymentStep.vue';
import CreateApiKeyStep from '@/components/onboardingTour/steps/CreateApiKeyStep.vue';
import CreateProjectStep from '@/components/onboardingTour/steps/CreateProjectStep.vue';
import OverviewStep from '@/components/onboardingTour/steps/OverviewStep.vue';
import OverviewStepNoPaywall from '@/components/onboardingTour/steps/OverviewStepNoPaywall.vue';
import UploadDataStep from '@/components/onboardingTour/steps/UploadDataStep.vue';
import CheckedImage from '@/../static/images/common/checked.svg';
import CloseImage from '@/../static/images/onboardingTour/close.svg';
import { RouteConfig } from '@/router';
import { TourState } from '@/utils/constants/onboardingTourEnums';
@Component({
components: {
OverviewStepNoPaywall,
UploadDataStep,
CreateApiKeyStep,
CreateProjectStep,
AddPaymentStep,
ProgressBar,
OverviewStep,
CheckedImage,
CloseImage,
},
})
export default class OnboardingTourArea extends Vue {
public areaState: number = TourState.DEFAULT;
public isInfoBarVisible: boolean = true;
/**
@ -89,7 +42,7 @@ export default class OnboardingTourArea extends Vue {
* Sets area to needed state.
*/
public mounted(): void {
if (this.userHasProject && this.userHasApiKeys) {
if (this.userHasProject && this.userHasAccessGrants) {
try {
this.$router.push(RouteConfig.ProjectDashboard.path);
} catch (error) {
@ -99,22 +52,8 @@ export default class OnboardingTourArea extends Vue {
return;
}
if (this.userHasProject && !this.userHasApiKeys) {
if (this.$route.name === RouteConfig.AccessGrant.name) {
this.disableInfoBar();
this.setCreateApiKeyState();
return;
}
if (this.$store.state.paymentsModule.creditCards.length > 0) {
this.disableInfoBar();
this.setCreateProjectState();
return;
}
if (this.$store.getters.isTransactionProcessing || this.$store.getters.isBalancePositive) {
this.setAddPaymentState();
}
}
@ -126,67 +65,10 @@ export default class OnboardingTourArea extends Vue {
}
/**
* Indicates if area is in default state.
* Sets area's state to creating access grant step.
*/
public get isDefaultState(): boolean {
return this.areaState === TourState.DEFAULT;
}
/**
* Indicates if area is in adding payment method state.
*/
public get isAddPaymentState(): boolean {
return this.areaState === TourState.ADDING_PAYMENT;
}
/**
* Indicates if area is in creating project state.
*/
public get isCreateProjectState(): boolean {
return this.areaState === TourState.PROJECT;
}
/**
* Indicates if area is in api key state.
*/
public get isCreatApiKeyState(): boolean {
return this.areaState === TourState.API_KEY;
}
/**
* Indicates if area is in upload data state.
*/
public get isUploadDataState(): boolean {
return this.areaState === TourState.UPLOAD;
}
/**
* Sets area's state to adding payment method state.
*/
public setAddPaymentState(): void {
this.areaState = TourState.ADDING_PAYMENT;
}
/**
* Sets area's state to creating project state.
*/
public setCreateProjectState(): void {
this.disableInfoBar();
this.areaState = TourState.PROJECT;
}
/**
* Sets area's state to creating api key state.
*/
public setCreateApiKeyState(): void {
this.areaState = TourState.API_KEY;
}
/**
* Sets area's state to upload data state.
*/
public setUploadDataState(): void {
this.areaState = TourState.UPLOAD;
public setCreateAccessGrantStep(): void {
this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant).path);
}
/**
@ -196,6 +78,13 @@ export default class OnboardingTourArea extends Vue {
this.isInfoBarVisible = false;
}
/**
* Indicates if area is on adding payment method step.
*/
public get isAddPaymentState(): boolean {
return this.$route.name === RouteConfig.PaymentStep.name;
}
/**
* Indicates if user has at least one project.
*/
@ -204,10 +93,10 @@ export default class OnboardingTourArea extends Vue {
}
/**
* Indicates if user has at least one API key.
* Indicates if user has at least one access grant.
*/
private get userHasApiKeys(): boolean {
return this.$store.state.apiKeysModule.page.apiKeys.length > 0;
private get userHasAccessGrants(): boolean {
return this.$store.state.accessGrantsModule.page.accessGrants.length > 0;
}
}
</script>
@ -246,7 +135,10 @@ export default class OnboardingTourArea extends Vue {
}
&__content {
padding: 0 100px 80px 100px;
display: flex;
flex-direction: column;
align-items: center;
padding: 110px 0 80px 0;
position: relative;
&__tardigrade {
@ -257,24 +149,4 @@ export default class OnboardingTourArea extends Vue {
}
}
}
@media screen and (max-width: 1550px) {
.tour-area {
&__content {
padding: 0 50px 80px 50px;
}
}
}
@media screen and (max-width: 1000px) {
.tour-area {
&__content {
padding: 0 25px 80px 25px;
}
}
}
</style>

View File

@ -1,187 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="progress-bar-container">
<div class="progress-bar-container__progress-area">
<div
v-if="isPaywallEnabled"
class="progress-bar-container__progress-area__circle"
:class="{ 'completed-step': isAddPaymentStep || isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
>
<CheckedImage/>
</div>
<div
v-if="isPaywallEnabled"
class="progress-bar-container__progress-area__bar"
:class="{ 'completed-step': isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
/>
<div
class="progress-bar-container__progress-area__circle"
:class="{ 'completed-step': isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
>
<CheckedImage/>
</div>
<div
class="progress-bar-container__progress-area__bar"
:class="{ 'completed-step': isCreateApiKeyStep || isUploadDataStep }"
/>
<div
class="progress-bar-container__progress-area__circle"
:class="{ 'completed-step': isCreateApiKeyStep || isUploadDataStep }"
>
<CheckedImage/>
</div>
<div
class="progress-bar-container__progress-area__bar"
:class="{ 'completed-step': isUploadDataStep }"
/>
<div
class="progress-bar-container__progress-area__circle"
:class="{ 'completed-step': isUploadDataStep }"
>
<CheckedImage/>
</div>
</div>
<div class="progress-bar-container__titles-area" :class="{ 'titles-area-no-paywall': !isPaywallEnabled }">
<span
v-if="isPaywallEnabled"
class="progress-bar-container__titles-area__title"
:class="{ 'completed-font-color': isAddPaymentStep || isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
>
Add Payment
</span>
<span
class="progress-bar-container__titles-area__title name-your-project-title"
:class="{ 'completed-font-color': isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep, 'title-no-paywall': !isPaywallEnabled }"
>
Name Your Project
</span>
<span
class="progress-bar-container__titles-area__title api-key-title"
:class="{ 'completed-font-color': isCreateApiKeyStep || isUploadDataStep }"
>
Create an API Key
</span>
<span
class="progress-bar-container__titles-area__title"
:class="{ 'completed-font-color': isUploadDataStep }"
>
Upload Data
</span>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import CheckedImage from '@/../static/images/common/checked.svg';
@Component({
components: {
CheckedImage,
},
})
export default class ProgressBar extends Vue {
@Prop({ default: false })
public readonly isPaywallEnabled: boolean;
@Prop({ default: false })
public readonly isAddPaymentStep: boolean;
@Prop({ default: false })
public readonly isCreateProjectStep: boolean;
@Prop({ default: false })
public readonly isCreateApiKeyStep: boolean;
@Prop({ default: false })
public readonly isUploadDataStep: boolean;
}
</script>
<style scoped lang="scss">
.progress-bar-container {
width: 100%;
&__progress-area {
width: calc(100% - 420px);
display: flex;
justify-content: space-between;
align-items: center;
padding: 25px 210px 6px 210px;
&__circle {
display: flex;
justify-content: center;
align-items: center;
min-width: 20px;
height: 20px;
background-color: #c5cbdb;
border-radius: 10px;
}
&__bar {
width: 100%;
height: 4px;
background-color: #c5cbdb;
}
}
&__titles-area {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 188px 0 188px;
&__title {
font-family: 'font_regular', sans-serif;
font-size: 10px;
line-height: 15px;
color: rgba(0, 0, 0, 0.4);
text-align: center;
}
}
}
.name-your-project-title {
padding: 0 0 0 10px;
}
.api-key-title {
padding: 0 15px 0 0;
}
.completed-step {
background-color: #2683ff;
}
.completed-font-color {
color: #2683ff;
}
.titles-area-no-paywall {
padding: 0 188px 0 178px;
}
.title-no-paywall {
padding: 0;
}
@media screen and (max-width: 800px) {
.progress-bar-container {
&__progress-area {
width: calc(100% - 300px);
padding: 25px 150px 6px 150px;
}
&__titles-area {
padding: 0 128px 0 128px;
}
}
.titles-area-no-paywall {
padding: 0 128px 0 118px;
}
}
</style>

View File

@ -5,7 +5,8 @@
<div class="payment-step">
<h1 class="payment-step__title">Get Started with 50 GB Free</h1>
<p class="payment-step__sub-title">
Adding a payment method ensures your project wont be interrupted after your <b>free</b> credit is used.
Experience the decentralized cloud for free! If you find our network isnt for you, <b class="bold">cancel
any time before your credit runs out and you wont be billed.</b>
</p>
<div class="payment-step__methods-container">
<div class="payment-step__methods-container__title-area">
@ -32,12 +33,12 @@
<AddCardState
v-if="isAddCardState"
@toggleIsLoading="toggleIsLoading"
@setProjectState="setProjectState"
@setCreateGrantStep="setCreateGrantStep"
/>
<AddStorjState
v-if="isAddStorjState"
@toggleIsLoading="toggleIsLoading"
@setProjectState="setProjectState"
@setCreateGrantStep="setCreateGrantStep"
/>
<h1 class="payment-step__title second-title">Transparent Monthly Pricing</h1>
<p class="payment-step__sub-title">
@ -84,6 +85,7 @@ import { Component, Vue } from 'vue-property-decorator';
import AddCardState from '@/components/onboardingTour/steps/paymentStates/AddCardState.vue';
import AddStorjState from '@/components/onboardingTour/steps/paymentStates/AddStorjState.vue';
import { RouteConfig } from '@/router';
import { AddingPaymentState } from '@/utils/constants/onboardingTourEnums';
@Component({
@ -102,6 +104,12 @@ export default class AddPaymentStep extends Vue {
* Sets area to needed state.
*/
public mounted(): void {
if (this.userHasProject) {
this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant).path);
return;
}
if (this.$store.getters.isTransactionProcessing || this.$store.getters.isBalancePositive) {
this.setAddStorjState();
}
@ -143,10 +151,17 @@ export default class AddPaymentStep extends Vue {
}
/**
* Sets tour area to creating project state.
* Sets tour area to creating access grant state.
*/
public setProjectState(): void {
this.$emit('setProjectState');
public setCreateGrantStep(): void {
this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant).with(RouteConfig.AccessGrantName).path);
}
/**
* Indicates if user has at least one project.
*/
private get userHasProject(): boolean {
return this.$store.state.projectsModule.projects.length > 0;
}
}
</script>
@ -160,15 +175,16 @@ export default class AddPaymentStep extends Vue {
.payment-step {
font-family: 'font_regular', sans-serif;
margin-top: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 0 140px 200px 140px;
padding: 0 0 200px 0;
max-width: 750px;
position: relative;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
@ -182,6 +198,10 @@ export default class AddPaymentStep extends Vue {
margin-bottom: 35px;
text-align: center;
word-break: break-word;
.bold {
font-family: 'font_medium', sans-serif;
}
}
&__methods-container {
@ -302,18 +322,4 @@ export default class AddPaymentStep extends Vue {
border-top: 1px solid #afb7c1;
border-bottom: 1px solid #afb7c1;
}
@media screen and (max-width: 1550px) {
.payment-step {
padding: 0 70px 200px 70px;
}
}
@media screen and (max-width: 800px) {
.payment-step {
padding: 0 25px 200px 25px;
}
}
</style>

View File

@ -0,0 +1,99 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="onboarding-access">
<h1 class="onboarding-access__title">Create an Access Grant</h1>
<p class="onboarding-access__sub-title">
Access Grants are keys that allow access to upload, delete, and view your projects data.
</p>
<div
class="onboarding-access__content"
:class="{
'permissions-margin': isPermissionsStep,
'passphrase-margin': isPassphraseStep,
'result-margin': isResultStep,
}"
>
<ProgressBar/>
<router-view/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ProgressBar from '@/components/accessGrants/ProgressBar.vue';
import { RouteConfig } from '@/router';
@Component({
components: {
ProgressBar,
},
})
export default class CreateAccessGrantStep extends Vue {
/**
* Indicates if current route is access grant permissions step.
*/
public get isPermissionsStep(): boolean {
return this.$route.name === RouteConfig.AccessGrantPermissions.name;
}
/**
* Indicates if current route is access grant passphrase step.
*/
public get isPassphraseStep(): boolean {
return this.$route.name === RouteConfig.AccessGrantPassphrase.name;
}
/**
* Indicates if current route is access grant result step.
*/
public get isResultStep(): boolean {
return this.$route.name === RouteConfig.AccessGrantResult.name;
}
}
</script>
<style scoped lang="scss">
.onboarding-access {
font-family: 'font_regular', sans-serif;
font-style: normal;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
text-align: center;
color: #1b2533;
margin: 0 0 15px 0;
}
&__sub-title {
font-size: 16px;
line-height: 21px;
color: #000;
margin: 0 0 50px 0;
}
&__content {
display: flex;
align-items: center;
margin-left: -145px;
}
}
.permissions-margin {
margin-left: -210px;
}
.passphrase-margin {
margin-left: -180px;
}
.result-margin {
margin-left: -175px;
}
</style>

View File

@ -1,385 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="create-api-key-step">
<h1 class="create-api-key-step__title">Create an API Key</h1>
<p class="create-api-key-step__sub-title">
API keys provide access to the project for creating buckets and uploading objects through the command line
interface. This will be your first API key, and you can always create more keys later on.
</p>
<div class="create-api-key-step__container">
<div class="create-api-key-step__container__title-area">
<h2 class="create-api-key-step__container__title-area__title">Create API Key</h2>
<img
v-if="isLoading"
class="create-api-key-step__container__title-area__loading-image"
src="@/../static/images/account/billing/loading.gif"
alt="loading gif"
>
</div>
<HeaderedInput
label="API Key Name"
placeholder="Enter API Key Name (i.e. Dans Key)"
class="full-input"
width="calc(100% - 4px)"
:error="errorMessage"
@setData="setApiKeyName"
/>
<div class="create-api-key-step__container__create-key-area" v-if="isCreatingState">
<VButton
class="generate-button"
width="100%"
height="40px"
label="Generate API Key"
:is-blue-white="true"
:on-press="createApiKey"
/>
</div>
<div class="create-api-key-step__container__copy-key-area" v-else>
<div class="create-api-key-step__container__copy-key-area__header">
<InfoImage/>
<span class="create-api-key-step__container__copy-key-area__header__title">
API Keys only appear here once. Copy and paste this key to your preferred method of storing secrets.
</span>
</div>
<div class="create-api-key-step__container__copy-key-area__key-container">
<span class="create-api-key-step__container__copy-key-area__key-container__key">{{ key }}</span>
<div class="create-api-key-step__container__copy-key-area__key-container__copy-button">
<VButton
width="81px"
height="40px"
label="Copy"
:is-blue-white="true"
:on-press="onCopyClick"
/>
</div>
</div>
</div>
<p class="create-api-key-step__container__info" v-if="isCopyState">
We dont record your API Keys, which are only displayed once when generated. If you loose this
key, it cannot be recovered but you can always create new API Keys when needed.
</p>
<div class="create-api-key-step__container__blur" v-if="isLoading"/>
</div>
<VButton
class="done-button"
width="156px"
height="48px"
label="Done"
:on-press="onDoneClick"
:is-disabled="isCreatingState"
/>
<SaveApiKeyModal
v-if="isSaveApiKeyModalShown"
@confirmSave="onConfirmClick"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import VButton from '@/components/common/VButton.vue';
import SaveApiKeyModal from '@/components/onboardingTour/steps/SaveApiKeyModal.vue';
import InfoImage from '@/../static/images/onboardingTour/info.svg';
import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
import { ApiKey } from '@/types/apiKeys';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
import { AddingApiKeyState } from '@/utils/constants/onboardingTourEnums';
const {
CREATE,
FETCH,
} = API_KEYS_ACTIONS;
@Component({
components: {
VButton,
HeaderedInput,
SaveApiKeyModal,
InfoImage,
},
})
export default class CreateApiKeyStep extends Vue {
private name: string = '';
private addingState: number = AddingApiKeyState.CREATE;
private readonly FIRST_PAGE = 1;
public key: string = '';
public errorMessage: string = '';
public isLoading: boolean = false;
/**
* Indicates if save API key modal is shown.
*/
public get isSaveApiKeyModalShown(): boolean {
return this.$store.state.appStateModule.appState.isSaveApiKeyModalShown;
}
/**
* Indicates if view is in creating state.
*/
public get isCreatingState(): boolean {
return this.addingState === AddingApiKeyState.CREATE;
}
/**
* Indicates if view is in copy state.
*/
public get isCopyState(): boolean {
return this.addingState === AddingApiKeyState.COPY;
}
/**
* Indicates view state to copy state.
*/
public setCopyState(): void {
this.addingState = AddingApiKeyState.COPY;
}
/**
* Sets api key name from input value.
*/
public setApiKeyName(value: string): void {
this.name = value.trim();
this.errorMessage = '';
}
/**
* Creates api key and refreshes store.
*/
public async createApiKey(): Promise<void> {
if (this.isLoading) {
return;
}
if (!this.name) {
this.errorMessage = 'API Key name can`t be empty';
return;
}
this.isLoading = true;
let createdApiKey: ApiKey;
try {
createdApiKey = await this.$store.dispatch(CREATE, this.name);
} catch (error) {
await this.$notify.error(error.message);
this.isLoading = false;
return;
}
await this.$notify.success('Successfully created new api key');
this.key = createdApiKey.secret;
this.$segment.track(SegmentEvent.API_KEY_CREATED, {
project_id: this.$store.getters.selectedProject.id,
});
try {
await this.$store.dispatch(FETCH, this.FIRST_PAGE);
} catch (error) {
await this.$notify.error(`Unable to fetch API keys. ${error.message}`);
}
this.setCopyState();
this.isLoading = false;
}
/**
* Copies api key secret to buffer.
*/
public onCopyClick(): void {
this.$copyText(this.key);
this.$notify.success('Key saved to clipboard');
}
/**
* Toggles save API key modal visibility.
*/
public onDoneClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SAVE_API_KEY_MODAL);
}
/**
* Sets tour state to last step.
*/
public onConfirmClick(): void {
this.onDoneClick();
this.$emit('setUploadDataState');
}
}
</script>
<style scoped lang="scss">
h1,
h2,
p {
margin: 0;
}
.create-api-key-step {
font-family: 'font_regular', sans-serif;
margin-top: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 0 200px;
&__title {
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 25px;
}
&__sub-title {
font-size: 16px;
line-height: 19px;
color: #354049;
margin-bottom: 35px;
text-align: center;
word-break: break-word;
}
&__container {
padding: 50px;
width: calc(100% - 100px);
border-radius: 8px;
background-color: #fff;
position: relative;
margin-bottom: 30px;
&__title-area {
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 10px;
&__title {
font-family: 'font_medium', sans-serif;
font-size: 22px;
line-height: 27px;
color: #354049;
margin-right: 15px;
}
&__loading-image {
width: 18px;
height: 18px;
}
}
&__create-key-area {
width: calc(100% - 90px);
padding: 40px 45px;
margin-top: 30px;
background-color: #0c2546;
border-radius: 8px;
}
&__copy-key-area {
width: 100%;
margin-top: 30px;
&__header {
padding: 10px;
width: calc(100% - 20px);
background-color: #ce3030;
display: flex;
align-items: center;
justify-content: flex-start;
border-radius: 8px 8px 0 0;
&__title {
font-size: 12px;
line-height: 16px;
color: #fff;
}
}
&__key-container {
background-color: #0c2546;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 25px;
width: calc(100% - 50px);
border-radius: 0 0 8px 8px;
&__key {
font-size: 15px;
line-height: 23px;
color: #fff;
margin-right: 50px;
word-break: break-all;
}
&__copy-button {
min-width: 85px;
}
}
}
&__info {
width: 100%;
margin-top: 30px;
font-size: 12px;
line-height: 18px;
text-align: center;
color: #354049;
word-break: break-word;
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(229, 229, 229, 0.2);
z-index: 100;
}
}
}
.full-input {
width: 100%;
}
.info-svg {
min-width: 18px;
margin-right: 5px;
}
@media screen and (max-width: 1650px) {
.create-api-key-step {
padding: 0 100px;
}
}
@media screen and (max-width: 1450px) {
.create-api-key-step {
padding: 0 60px;
}
}
@media screen and (max-width: 900px) {
.create-api-key-step {
padding: 0 50px;
}
}
</style>

View File

@ -1,280 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="new-project-step">
<h1 class="new-project-step__title">Name Your Project</h1>
<p class="new-project-step__sub-title">
Projects are where buckets are created for storing data. Within a Project, usage is tracked at the bucket
level and aggregated for billing.
</p>
<div class="new-project-step__container">
<div class="new-project-step__container__title-area">
<h2 class="new-project-step__container__title-area__title">Project Details</h2>
<img
v-if="isLoading"
class="new-project-step__container__title-area__loading-image"
src="@/../static/images/account/billing/loading.gif"
alt="loading gif"
>
</div>
<HeaderedInput
label="Project Name"
additional-label="Up To 20 Characters"
placeholder="Enter Project Name"
class="full-input project-name-input"
width="100%"
is-limit-shown="true"
:current-limit="projectName.length"
:max-symbols="20"
:error="nameError"
@setData="setProjectName"
/>
<HeaderedInput
label="Description"
placeholder="Enter Project Description"
additional-label="Optional"
class="full-input"
is-multiline="true"
is-limit-shown="true"
:current-limit="description.length"
:max-symbols="100"
height="60px"
width="calc(100% - 42px)"
@setData="setProjectDescription"
/>
<div class="new-project-step__container__blur" v-if="isLoading"/>
</div>
<VButton
class="create-project-button"
width="156px"
height="48px"
label="Create Project"
:on-press="createProjectClick"
:is-disabled="!projectName"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import VButton from '@/components/common/VButton.vue';
import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectFields } from '@/types/projects';
import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@Component({
components: {
VButton,
HeaderedInput,
},
})
export default class CreateProjectStep extends Vue {
private description: string = '';
public projectName: string = '';
public isLoading: boolean = false;
public nameError: string = '';
/**
* Sets project name from input value.
*/
public setProjectName(value: string): void {
this.projectName = value;
this.nameError = '';
}
/**
* Sets project description from input value.
*/
public setProjectDescription(value: string): void {
this.description = value;
}
/**
* Creates project and refreshes store.
*/
public async createProjectClick(): Promise<void> {
if (this.isLoading) {
return;
}
this.isLoading = true;
this.projectName = this.projectName.trim();
const project = new ProjectFields(
this.projectName,
this.description,
this.$store.getters.user.id,
);
try {
project.checkName();
} catch (error) {
this.isLoading = false;
this.nameError = error.message;
return;
}
let createdProjectId: string = '';
try {
const createdProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
createdProjectId = createdProject.id;
this.$segment.track(SegmentEvent.PROJECT_CREATED, {
project_id: createdProjectId,
});
} catch (error) {
this.isLoading = false;
await this.$notify.error(error.message);
return;
}
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, createdProjectId);
try {
await this.fetchProjectMembers();
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PAYMENTS_HISTORY);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BALANCE);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, createdProjectId);
} catch (error) {
await this.$notify.error(`Unable to create project. ${error.message}`);
}
this.clearApiKeys();
this.clearBucketUsage();
await this.$notify.success('Project created successfully!');
this.isLoading = false;
this.$emit('setApiKeyState');
}
/**
* Clears project members store and fetches new.
*/
private async fetchProjectMembers(): Promise<void> {
await this.$store.dispatch(PM_ACTIONS.CLEAR);
const fistPage = 1;
await this.$store.dispatch(PM_ACTIONS.FETCH, fistPage);
}
/**
* Clears api keys store.
*/
private clearApiKeys(): void {
this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
}
/**
* Clears bucket usage store.
*/
private clearBucketUsage(): void {
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
}
}
</script>
<style scoped lang="scss">
h1,
h2,
p {
margin: 0;
}
.new-project-step {
font-family: 'font_regular', sans-serif;
margin-top: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 0 200px;
&__title {
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 25px;
}
&__sub-title {
font-size: 16px;
line-height: 19px;
color: #354049;
margin-bottom: 35px;
text-align: center;
word-break: break-word;
}
&__container {
padding: 50px;
width: calc(100% - 100px);
border-radius: 8px;
background-color: #fff;
position: relative;
margin-bottom: 30px;
&__title-area {
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 10px;
&__title {
font-family: 'font_medium', sans-serif;
font-size: 22px;
line-height: 27px;
color: #354049;
margin-right: 15px;
}
&__loading-image {
width: 18px;
height: 18px;
}
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(229, 229, 229, 0.2);
z-index: 100;
}
}
}
.full-input {
width: 100%;
margin-top: 25px;
}
@media screen and (max-width: 1450px) {
.new-project-step {
padding: 0 150px;
}
}
@media screen and (max-width: 900px) {
.new-project-step {
padding: 0 50px;
}
}
</style>

View File

@ -8,27 +8,103 @@ import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import FourthStepIcon from '@/../static/images/common/four.svg';
import FirstStepIcon from '@/../static/images/common/one.svg';
import ThirdStepIcon from '@/../static/images/common/three.svg';
import SecondStepIcon from '@/../static/images/common/two.svg';
import { RouteConfig } from '@/router';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectFields } from '@/types/projects';
import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@Component({
components: {
VButton,
FirstStepIcon,
ThirdStepIcon,
SecondStepIcon,
FourthStepIcon,
},
})
export default class OverviewStep extends Vue {
public isLoading: boolean = false;
/**
* Lifecycle hook after initial render.
* Sets area to needed state.
*/
public mounted(): void {
if (this.userHasProject || this.$store.state.paymentsModule.creditCards.length > 0) {
this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant).path);
return;
}
if (this.$store.getters.isTransactionProcessing || this.$store.getters.isBalancePositive) {
this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.PaymentStep).path);
}
}
/**
* Holds button click logic.
* Sets tour state to adding payment method state.
* Redirects to next step (adding payment method).
*/
public onClick(): void {
this.$emit('setAddPaymentState');
public onAddPaymentClick(): void {
this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.PaymentStep).path);
}
/**
* Holds button click logic.
* Creates untitled project and redirects to next step (creating access grant).
*/
public async onCreateGrantClick(): Promise<void> {
if (this.isLoading) return;
this.isLoading = true;
try {
const FIRST_PAGE = 1;
const UNTITLED_PROJECT_NAME = 'Untitled Project';
const UNTITLED_PROJECT_DESCRIPTION = '___';
const project = new ProjectFields(
UNTITLED_PROJECT_NAME,
UNTITLED_PROJECT_DESCRIPTION,
this.$store.getters.user.id,
);
const createdProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
const createdProjectId = createdProject.id;
this.$segment.track(SegmentEvent.PROJECT_CREATED, {
project_id: createdProjectId,
});
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, createdProjectId);
await this.$store.dispatch(PM_ACTIONS.CLEAR);
await this.$store.dispatch(PM_ACTIONS.FETCH, FIRST_PAGE);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PAYMENTS_HISTORY);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BALANCE);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, createdProjectId);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR);
await this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
this.isLoading = false;
await this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant).with(RouteConfig.AccessGrantName).path);
} catch (error) {
await this.$notify.error(error.message);
this.isLoading = false;
}
}
/**
* Indicates if paywall is enabled.
*/
public get isPaywallEnabled(): boolean {
return this.$store.state.paymentsModule.isPaywallEnabled;
}
/**
* Indicates if user has at least one project.
*/
private get userHasProject(): boolean {
return this.$store.state.projectsModule.projects.length > 0;
}
}
</script>

View File

@ -1,34 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template src="./overviewStepNoPaywall.html"></template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import FirstStepIcon from '@/../static/images/common/one.svg';
import ThirdStepIcon from '@/../static/images/common/three.svg';
import SecondStepIcon from '@/../static/images/common/two.svg';
@Component({
components: {
VButton,
FirstStepIcon,
ThirdStepIcon,
SecondStepIcon,
},
})
export default class OverviewStepNoPaywall extends Vue {
/**
* Holds button click logic.
* Sets tour state to adding project state.
*/
public onClick(): void {
this.$emit('setCreateProjectState');
}
}
</script>
<style scoped lang="scss" src="./overviewStepNoPaywall.scss"></style>

View File

@ -1,116 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="save-api-modal">
<div class="save-api-modal__container">
<OrangeExclamation/>
<h1 class="save-api-modal__container__title">Is Your API Key Saved?</h1>
<p class="save-api-modal__container__message">
API Keys are only displayed once when generated. If you havent saved your key, go back to copy and
paste the API key to your preferred method of storing secrets (i.e. TextEdit, Keybase, etc.)
</p>
<div class="save-api-modal__container__buttons-area">
<VButton
class="back-button"
width="186px"
height="45px"
label="Go Back"
:on-press="onBackClick"
:is-blue-white="true"
/>
<VButton
width="186px"
height="45px"
label="Yes, it's Saved!"
:on-press="onConfirmClick"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import OrangeExclamation from '@/../static/images/onboardingTour/orange-exclamation.svg';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
@Component({
components: {
OrangeExclamation,
VButton,
},
})
export default class SaveApiKeyModal extends Vue {
/**
* Toggles modal visibility.
*/
public onBackClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SAVE_API_KEY_MODAL);
}
/**
* Proceeds to tour's last step.
*/
public onConfirmClick(): void {
this.$emit('confirmSave');
}
}
</script>
<style scoped lang="scss">
.save-api-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(9, 21, 35, 0.85);
display: flex;
align-items: center;
justify-content: center;
font-family: 'font_regular', sans-serif;
&__container {
background-color: #fff;
z-index: 1;
padding: 35px;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 6px;
max-width: 460px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 10px 0;
}
&__message {
font-size: 16px;
line-height: 24px;
color: #000;
word-break: break-word;
text-align: center;
margin: 0 0 10px 0;
}
&__buttons-area {
display: flex;
align-items: center;
}
}
}
.back-button {
margin-right: 10px;
}
</style>

View File

@ -1,250 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="upload-data-area">
<div class="upload-data-area__container">
<h1 class="upload-data-area__container__title">Upload Data</h1>
<p class="upload-data-area__container__sub-title">
From here, youll set up Tardigrade to store data for your project using our S3 Gateway, Uplink CLI, or
select from our growing library of connectors to build apps on Tardigrade.
</p>
<div class="upload-data-area__container__docs-area">
<div class="upload-data-area__container__docs-area__option">
<h2 class="upload-data-area__container__docs-area__option__title">
Migrate Data from your Existing AWS buckets
</h2>
<img src="@/../static/images/onboardingTour/s3.png" alt="s3 gateway image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
S3 Gateway
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Make the switch with Tardigrades S3 Gateway.
</p>
<a
class="upload-data-area__container__docs-area__option__link"
href="https://documentation.tardigrade.io/api-reference/s3-gateway"
target="_blank"
rel="noopener noreferrer"
>
S3 Gateway Docs
</a>
</div>
<div class="upload-data-area__container__docs-area__option uplink-option">
<h2 class="upload-data-area__container__docs-area__option__title">
Upload Data from Your Local Environment
</h2>
<img src="@/../static/images/onboardingTour/uplinkcli.png" alt="uplink cli image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
Uplink CLI
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Start uploading data from the command line.
</p>
<a
class="upload-data-area__container__docs-area__option__link"
href="https://documentation.tardigrade.io/getting-started/uploading-your-first-object/set-up-uplink-cli"
target="_blank"
rel="noopener noreferrer"
>
Uplink CLI Docs
</a>
</div>
<div class="upload-data-area__container__docs-area__option">
<h2 class="upload-data-area__container__docs-area__option__title">
Use Tardigrade for your apps storage layer
</h2>
<img src="@/../static/images/onboardingTour/connectors.png" alt="connectors image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
App Connectors
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Integrate Tardigrade into your existing stack.
</p>
<a
class="upload-data-area__container__docs-area__option__link"
href="https://tardigrade.io/connectors/"
target="_blank"
rel="noopener noreferrer"
>
App Connectors
</a>
</div>
</div>
</div>
<VButton
class="go-to-button"
width="276px"
height="40px"
label="Go to Dashboard"
:on-press="onButtonClick"
/>
<span class="upload-data-area__support-info">
Need help?
<a
class="upload-data-area__support-info__link"
href="https://support.tardigrade.io/hc/en-us"
target="_blank"
rel="noopener noreferrer"
>
Contact support
</a>
</span>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import { RouteConfig } from '@/router';
@Component({
components: {
VButton,
},
})
export default class UploadDataStep extends Vue {
/**
* Holds button click logic.
* Ends onboarding tour and redirects to project dashboard.
*/
public onButtonClick(): void {
this.$router.push(RouteConfig.ProjectDashboard.path);
}
}
</script>
<style scoped lang="scss">
h1,
h2,
h3,
p {
margin: 0;
}
.upload-data-area {
display: flex;
flex-direction: column;
align-items: center;
width: auto;
font-family: 'font_regular', sans-serif;
margin-top: 25px;
&__container {
padding: 60px;
display: flex;
flex-direction: column;
align-items: center;
background-color: rgba(255, 255, 255, 0.4);
border-radius: 8px;
margin-bottom: 50px;
&__title {
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 25px;
}
&__sub-title {
font-size: 16px;
line-height: 19px;
color: #354049;
margin-bottom: 35px;
text-align: center;
word-break: break-word;
max-width: 800px;
}
&__docs-area {
display: flex;
align-items: center;
justify-content: space-between;
&__option {
padding: 30px;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid rgba(144, 155, 168, 0.4);
border-radius: 8px;
&__title {
font-size: 10px;
line-height: 15px;
text-align: center;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #909ba8;
margin-bottom: 25px;
max-width: 181px;
word-break: break-word;
}
&__sub-title {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 26px;
text-align: center;
color: #354049;
margin: 25px 0 5px 0;
}
&__info {
font-size: 14px;
line-height: 17px;
text-align: center;
color: #61666b;
max-width: 181px;
word-break: break-word;
margin-bottom: 15px;
}
&__link {
width: 181px;
height: 40px;
border: 1px solid #2683ff;
border-radius: 6px;
background-color: #fff;
color: #2683ff;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background-color: #2683ff;
color: #fff;
}
}
}
}
}
&__support-info {
margin-top: 50px;
font-family: 'font_bold', sans-serif;
font-size: 15px;
line-height: 22px;
text-align: center;
color: #909ba8;
&__link {
text-decoration: underline;
}
}
}
.uplink-option {
margin: 0 45px;
}
@media screen and (max-width: 1350px) {
.uplink-option {
margin: 0 15px;
}
}
</style>

View File

@ -2,50 +2,49 @@
<!--See LICENSE for copying information.-->
<div class="overview-area">
<h1 class="overview-area__title">Welcome to Storj</h1>
<h1 class="overview-area__title">
Welcome to Storj.
<br/>
Lets Get Started.
</h1>
<p class="overview-area__sub-title">
Youre just a few steps away from uploading your first object to the 100% secure, decentralized cloud. After
adding payment, youll create a project, API key, get set up with Storj, and start uploading objects.
Follow the docs to start storing data using method below.
</p>
<div class="overview-area__steps-area">
<div class="overview-area__steps-area__step">
<FirstStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/card.png" alt="card image">
<h2 class="overview-area__steps-area__step__title">Add Payment</h2>
<span class="overview-area__steps-area__step__subtitle">
Get 50 GB free to try Storj. Cancel anytime.
</span>
</div>
<div class="overview-area__steps-area__step second-step">
<SecondStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/project.jpg" alt="project image">
<h2 class="overview-area__steps-area__step__title">Name Your Project</h2>
<span class="overview-area__steps-area__step__subtitle">
Projects are where buckets are created for storing data.
</span>
</div>
<div class="overview-area__steps-area__step third-step">
<ThirdStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/api-key.jpg" alt="api keys image">
<h2 class="overview-area__steps-area__step__title">Create an API Key</h2>
<span class="overview-area__steps-area__step__subtitle">
Generate access to your project to upload data.
</span>
</div>
<div class="overview-area__steps-area__step">
<FourthStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/uplink.jpg" alt="uplink image">
<h2 class="overview-area__steps-area__step__title">Upload Data</h2>
<span class="overview-area__steps-area__step__subtitle">
Store your data on the secure, decentralized cloud.
</span>
<img class="overview-area__steps-area__step__image" src="@/../static/images/onboardingTour/cli.png" alt="cli image">
<h2 class="overview-area__steps-area__step__title">CLI Tool</h2>
<p class="overview-area__steps-area__step__info">
Quickly upload data directly through the command line interface.
</p>
<a
class="overview-area__steps-area__step__link"
href="https://documentation.storj.io/setup/cli"
target="_blank"
ref="noopener noreferrer"
>
CLI Docs >
</a>
</div>
</div>
<div class="overview-area__divider"/>
<p class="overview-area__next-label">Next</p>
<VButton
class="get-started-button"
label="Get Started"
width="251px"
height="56px"
:on-press="onClick"
v-if="isPaywallEnabled"
class="overview-area__cta"
label="Add a Payment Method"
width="252px"
height="48px"
:is-disabled="isLoading"
:on-press="onAddPaymentClick"
/>
<VButton
v-else
class="overview-area__cta"
label="Create an Access Grant"
width="252px"
height="48px"
:is-disabled="isLoading"
:on-press="onCreateGrantClick"
/>
</div>

View File

@ -8,8 +8,7 @@ h2 {
}
.overview-area {
width: auto;
padding: 75px;
padding: 60px 75px;
display: flex;
flex-direction: column;
align-items: center;
@ -17,47 +16,38 @@ h2 {
font-family: 'font_regular', sans-serif;
background-color: #fff;
border-radius: 6px;
margin-top: 25px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 25px;
margin: 0 0 15px 0;
font-weight: bold;
font-size: 32px;
line-height: 47px;
text-align: center;
}
&__sub-title {
font-size: 16px;
line-height: 26px;
color: #354049;
margin-bottom: 60px;
margin: 0 0 60px 0;
text-align: center;
max-width: 815px;
font-size: 14px;
line-height: 22px;
color: #000;
}
&__steps-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
margin-bottom: 50px;
margin-bottom: 60px;
&__step {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
position: relative;
max-width: 190px;
width: 190px;
&__icon {
position: absolute;
top: -15px;
left: 80px;
}
&__title {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 26px;
text-align: center;
@ -65,51 +55,50 @@ h2 {
margin: 15px 0 5px 0;
}
&__subtitle {
font-family: 'font_regular', sans-serif;
&__info {
font-weight: 300;
font-size: 14px;
line-height: 17px;
text-align: center;
color: #61666b;
color: #1b2533;
word-break: break-word;
}
&__link {
margin-top: 30px;
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 26px;
color: #0068dc;
justify-self: flex-end;
}
}
}
&__divider {
width: 100%;
height: 1px;
background-color: rgba(118, 131, 148, 0.4);
}
&__next-label {
font-weight: normal;
font-size: 16px;
line-height: 26px;
color: #768394;
margin: 30px 0;
}
}
.second-step {
margin: 0 25px 0 50px;
}
.third-step {
margin: 0 50px 0 25px;
}
@media screen and (max-width: 1450px) {
@media screen and (max-width: 1050px) {
.overview-area {
padding: 75px 30px;
}
.second-step {
margin: 0 10px 0 20px;
}
.third-step {
margin: 0 20px 0 10px;
padding: 60px 30px;
}
}
@media screen and (max-width: 900px) {
.overview-area {
&__steps-area {
flex-direction: column;
&__step {
margin: 0 0 30px 0;
}
}
padding: 30px 20px;
}
}

View File

@ -1,43 +0,0 @@
<!--Copyright (C) 2020 Storj Labs, Inc.-->
<!--See LICENSE for copying information.-->
<div class="overview-area">
<h1 class="overview-area__title">Welcome to Storj</h1>
<p class="overview-area__sub-title">
Youre just a few steps away from uploading your first object to the 100% secure, decentralized cloud. Simply
add a payment method any time before your credit runs out to keep using Storj.
</p>
<div class="overview-area__steps-area">
<div class="overview-area__steps-area__step">
<FirstStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/project.jpg" alt="project image">
<h2 class="overview-area__steps-area__step__title">Name Your Project</h2>
<span class="overview-area__steps-area__step__subtitle">
Projects are where buckets are created for storing data.
</span>
</div>
<div class="overview-area__steps-area__step second-step">
<SecondStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/api-key.jpg" alt="api keys image">
<h2 class="overview-area__steps-area__step__title">Create an API Key</h2>
<span class="overview-area__steps-area__step__subtitle">
Generate access to your project to upload data.
</span>
</div>
<div class="overview-area__steps-area__step">
<ThirdStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/uplink.jpg" alt="uplink image">
<h2 class="overview-area__steps-area__step__title">Upload Data</h2>
<span class="overview-area__steps-area__step__subtitle">
Store your data on the secure, decentralized cloud.
</span>
</div>
</div>
<VButton
class="get-started-button"
label="Get Started"
width="251px"
height="56px"
:on-press="onClick"
/>
</div>

View File

@ -1,107 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
p,
h1,
h2 {
margin: 0;
}
.overview-area {
width: auto;
padding: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-family: 'font_regular', sans-serif;
background-color: #fff;
border-radius: 6px;
margin-top: 25px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 25px;
}
&__sub-title {
font-size: 16px;
line-height: 26px;
color: #354049;
margin-bottom: 60px;
text-align: center;
max-width: 815px;
}
&__steps-area {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 50px;
&__step {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
position: relative;
max-width: 190px;
width: 190px;
&__icon {
position: absolute;
top: -15px;
left: 80px;
}
&__title {
font-size: 16px;
line-height: 26px;
text-align: center;
color: #354049;
margin: 15px 0 5px 0;
}
&__subtitle {
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 17px;
text-align: center;
color: #61666b;
word-break: break-word;
}
}
}
}
.second-step {
margin: 0 50px 0 50px;
}
@media screen and (max-width: 1450px) {
.overview-area {
padding: 75px 30px;
}
.second-step {
margin: 0 20px 0 20px;
}
}
@media screen and (max-width: 900px) {
.overview-area {
&__steps-area {
flex-direction: column;
&__step {
margin: 0 0 30px 0;
}
}
}
}

View File

@ -16,15 +16,14 @@
Your card is secured by 128-bit SSL and AES-256 encryption. Your information is secure.
</span>
</div>
<div class="add-card-state__button" :class="{ loading: isLoading }" @click="onConfirmAddStripe">
<img
v-if="isLoading"
class="add-card-state__button__loading-image"
src="@/../static/images/account/billing/loading.gif"
alt="loading gif"
>
<span class="add-card-state__button__label">{{ isLoading ? 'Adding' : 'Add Payment' }}</span>
</div>
<p class="add-card-state__next-label">Next</p>
<VButton
label="Create an Access Grant"
width="252px"
height="48px"
:is-disabled="isLoading"
:on-press="onCreateGrantClick"
/>
</div>
</template>
@ -32,10 +31,16 @@
import { Component, Vue } from 'vue-property-decorator';
import StripeCardInput from '@/components/account/billing/paymentMethods/StripeCardInput.vue';
import VButton from '@/components/common/VButton.vue';
import LockImage from '@/../static/images/account/billing/lock.svg';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectFields } from '@/types/projects';
import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
const {
@ -52,25 +57,67 @@ interface StripeForm {
components: {
StripeCardInput,
LockImage,
VButton,
},
})
export default class AddCardState extends Vue {
public isLoading: boolean = false;
private readonly TOGGLE_IS_LOADING: string = 'toggleIsLoading';
private readonly SET_CREATE_GRANT_STEP: string = 'setCreateGrantStep';
public isLoading: boolean = false;
public $refs!: {
stripeCardInput: StripeCardInput & StripeForm;
};
/**
* Provides card information to Stripe.
* Provides card information to Stripe, creates untitled project and redirects to next step.
*/
public async onConfirmAddStripe(): Promise<void> {
await this.$refs.stripeCardInput.onSubmit();
public async onCreateGrantClick(): Promise<void> {
if (this.isLoading) return;
this.$segment.track(SegmentEvent.PAYMENT_METHOD_ADDED, {
project_id: this.$store.getters.selectedProject.id,
});
this.isLoading = true;
this.$emit(this.TOGGLE_IS_LOADING);
try {
await this.$refs.stripeCardInput.onSubmit();
const FIRST_PAGE = 1;
const UNTITLED_PROJECT_NAME = 'Untitled Project';
const UNTITLED_PROJECT_DESCRIPTION = '___';
const project = new ProjectFields(
UNTITLED_PROJECT_NAME,
UNTITLED_PROJECT_DESCRIPTION,
this.$store.getters.user.id,
);
const createdProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
const createdProjectId = createdProject.id;
this.$segment.track(SegmentEvent.PROJECT_CREATED, {
project_id: createdProjectId,
});
this.$segment.track(SegmentEvent.PAYMENT_METHOD_ADDED, {
project_id: createdProjectId,
});
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, createdProjectId);
await this.$store.dispatch(PM_ACTIONS.CLEAR);
await this.$store.dispatch(PM_ACTIONS.FETCH, FIRST_PAGE);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PAYMENTS_HISTORY);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BALANCE);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, createdProjectId);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR);
await this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
this.setDefaultState();
this.$emit(this.SET_CREATE_GRANT_STEP);
} catch (error) {
await this.$notify.error(error.message);
this.setDefaultState();
}
}
/**
@ -79,11 +126,6 @@ export default class AddCardState extends Vue {
* @param token from Stripe
*/
public async addCard(token: string) {
if (this.isLoading) return;
this.isLoading = true;
this.$emit('toggleIsLoading');
try {
await this.$store.dispatch(ADD_CREDIT_CARD, token);
} catch (error) {
@ -98,11 +140,8 @@ export default class AddCardState extends Vue {
await this.$store.dispatch(GET_CREDIT_CARDS);
} catch (error) {
await this.$notify.error(error.message);
this.setDefaultState();
}
this.setDefaultState();
this.$emit('setProjectState');
}
/**
@ -110,7 +149,7 @@ export default class AddCardState extends Vue {
*/
private setDefaultState(): void {
this.isLoading = false;
this.$emit('toggleIsLoading');
this.$emit(this.TOGGLE_IS_LOADING);
}
}
</script>
@ -147,7 +186,6 @@ export default class AddCardState extends Vue {
padding: 15px 35px;
background-color: #cef0e3;
border-radius: 0 0 8px 8px;
margin-bottom: 45px;
display: flex;
align-items: center;
justify-content: center;
@ -160,34 +198,12 @@ export default class AddCardState extends Vue {
}
}
&__button {
display: flex;
justify-content: center;
align-items: center;
width: 156px;
height: 48px;
cursor: pointer;
border-radius: 6px;
background-color: #2683ff;
&__loading-image {
margin-right: 5px;
width: 18px;
height: 18px;
}
&__label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 23px;
color: #fff;
word-break: keep-all;
white-space: nowrap;
}
&:hover {
background-color: #0059d0;
}
&__next-label {
font-weight: normal;
font-size: 16px;
line-height: 26px;
color: #768394;
margin: 35px 0;
}
}

View File

@ -34,11 +34,12 @@
</div>
<div class="add-storj-state__container__blur" v-if="isLoading"/>
</div>
<p class="add-storj-state__next-label">Next</p>
<VButton
width="222px"
width="252px"
height="48px"
label="Name Your Project"
:on-press="createProject"
label="Create an Acess Grant"
:on-press="createAccessGrant"
:is-disabled="isButtonDisabled"
/>
</div>
@ -52,6 +53,13 @@ import PayingStep from '@/components/onboardingTour/steps/paymentStates/tokenSub
import VerifiedStep from '@/components/onboardingTour/steps/paymentStates/tokenSubSteps/VerifiedStep.vue';
import VerifyingStep from '@/components/onboardingTour/steps/paymentStates/tokenSubSteps/VerifyingStep.vue';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectFields } from '@/types/projects';
import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
import { AddingStorjState } from '@/utils/constants/onboardingTourEnums';
@Component({
@ -64,6 +72,9 @@ import { AddingStorjState } from '@/utils/constants/onboardingTourEnums';
})
export default class AddStorjState extends Vue {
private readonly TOGGLE_IS_LOADING: string = 'toggleIsLoading';
private readonly SET_CREATE_GRANT_STEP: string = 'setCreateGrantStep';
public isLoading: boolean = false;
public addingTokenState: number = AddingStorjState.DEFAULT;
@ -86,6 +97,78 @@ export default class AddStorjState extends Vue {
}
}
/**
* Create untitled project and starts creating access grant process.
*/
public async createAccessGrant(): Promise<void> {
if (this.isLoading) return;
this.toggleIsLoading();
try {
const FIRST_PAGE = 1;
const UNTITLED_PROJECT_NAME = 'Untitled Project';
const UNTITLED_PROJECT_DESCRIPTION = '___';
const project = new ProjectFields(
UNTITLED_PROJECT_NAME,
UNTITLED_PROJECT_DESCRIPTION,
this.$store.getters.user.id,
);
const createdProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
const createdProjectId = createdProject.id;
this.$segment.track(SegmentEvent.PROJECT_CREATED, {
project_id: createdProjectId,
});
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, createdProjectId);
await this.$store.dispatch(PM_ACTIONS.CLEAR);
await this.$store.dispatch(PM_ACTIONS.FETCH, FIRST_PAGE);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PAYMENTS_HISTORY);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BALANCE);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, createdProjectId);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR);
await this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
this.toggleIsLoading();
this.$emit(this.SET_CREATE_GRANT_STEP);
} catch (error) {
await this.$notify.error(error.message);
this.toggleIsLoading();
}
}
/**
* Sets area to default state.
*/
public setDefaultState(): void {
this.addingTokenState = AddingStorjState.DEFAULT;
}
/**
* Sets area to verifying state.
*/
public setVerifyingState(): void {
this.addingTokenState = AddingStorjState.VERIFYING;
}
/**
* Sets area to verified state.
*/
public setVerifiedState(): void {
this.addingTokenState = AddingStorjState.VERIFIED;
}
/**
* Toggles area's loading state.
*/
public toggleIsLoading(): void {
this.isLoading = !this.isLoading;
this.$emit(this.TOGGLE_IS_LOADING);
}
/**
* Indicates if area is in default state.
*/
@ -113,42 +196,6 @@ export default class AddStorjState extends Vue {
public get isButtonDisabled(): boolean {
return !this.$store.getters.canUserCreateFirstProject;
}
/**
* Sets area to default state.
*/
public setDefaultState(): void {
this.addingTokenState = AddingStorjState.DEFAULT;
}
/**
* Sets area to verifying state.
*/
public setVerifyingState(): void {
this.addingTokenState = AddingStorjState.VERIFYING;
}
/**
* Sets area to verified state.
*/
public setVerifiedState(): void {
this.addingTokenState = AddingStorjState.VERIFIED;
}
/**
* Toggles area's loading state.
*/
public toggleIsLoading(): void {
this.isLoading = !this.isLoading;
this.$emit('toggleIsLoading');
}
/**
* Starts creating project process.
*/
public createProject(): void {
this.$emit('setProjectState');
}
}
</script>
@ -172,7 +219,6 @@ export default class AddStorjState extends Vue {
padding: 20px 45px 45px 45px;
background-color: #fff;
border-radius: 0 0 8px 8px;
margin-bottom: 35px;
position: relative;
&__bonus-info {
@ -218,5 +264,13 @@ export default class AddStorjState extends Vue {
z-index: 100;
}
}
&__next-label {
font-weight: normal;
font-size: 16px;
line-height: 26px;
color: #768394;
margin: 35px 0;
}
}
</style>

View File

@ -20,7 +20,7 @@
@onChangeTokenValue="onChangeTokenValue"
/>
<VButton
width="100%"
width="calc(100% - 4px)"
height="48px"
label="Continue to Coin Payments"
:on-press="onConfirmAddSTORJ"
@ -145,7 +145,7 @@ export default class PayingStep extends Vue {
}
&__form {
width: 100%;
width: calc(100% - 2px);
margin-bottom: 20px;
/deep/ .selected-container,

View File

@ -5,8 +5,8 @@
<VerifyingImage/>
<h2 class="verifying-step__title">Verifying Payment</h2>
<span class="verifying-step__sub-title">
Verification may take up to an hour. In the meantime, see how easy it is to get started visiting documentation
page, or check coin payment status.
Verification may take up to a few hours. In the meantime, see how easy it is to get started visiting
documentation page, or check coin payment status.
</span>
<div class="verifying-step__buttons-area">
<a

View File

@ -65,7 +65,7 @@ import HeaderedInput from '@/components/common/HeaderedInput.vue';
import VButton from '@/components/common/VButton.vue';
import { RouteConfig } from '@/router';
import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
@ -200,7 +200,7 @@ export default class NewProjectPopup extends Vue {
* Clears api keys store.
*/
private clearApiKeys(): void {
this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR);
}
/**

View File

@ -58,7 +58,7 @@ import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import DeleteProjectIcon from '@/../static/images/project/deleteProject.svg';
import ErrorIcon from '@/../static/images/register/ErrorInfo.svg';
import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import {
@ -146,7 +146,7 @@ export default class DeleteProjectPopup extends Vue {
private async selectProject(): Promise<void> {
if (this.$store.state.projectsModule.projects.length === 0) {
await this.$store.dispatch(PM_ACTIONS.CLEAR);
await this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR);
await this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
return;
@ -155,7 +155,7 @@ export default class DeleteProjectPopup extends Vue {
// TODO: reuse select project functionality
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, this.$store.state.projectsModule.projects[0].id);
await this.$store.dispatch(PM_ACTIONS.FETCH, 1);
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH, 1);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, 1);
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, 1);
}
}

View File

@ -3,7 +3,14 @@
<template>
<div class="dashboard-area">
<h1 class="dashboard-area__title">{{projectName}} Dashboard</h1>
<div class="dashboard-area__header-wrapper">
<h1 class="dashboard-area__title">{{projectName}} Dashboard</h1>
<VInfo
class="dashboard-area__tooltip__wrapper"
bold-text="Expect a delay of a few hours between network activity and the latest dashboard stats.">
<InfoIcon class="dashboard-area__tooltip__icon"/>
</VInfo>
</div>
<ProjectUsage/>
<ProjectSummary/>
<BucketArea/>
@ -13,10 +20,13 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VInfo from '@/components/common/VInfo.vue';
import BucketArea from '@/components/project/buckets/BucketArea.vue';
import ProjectSummary from '@/components/project/summary/ProjectSummary.vue';
import ProjectUsage from '@/components/project/usage/ProjectUsage.vue';
import InfoIcon from '@/../static/images/common/infoTooltipSm.svg';
import { RouteConfig } from '@/router';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@ -25,8 +35,10 @@ import { MetaUtils } from '@/utils/meta';
@Component({
components: {
BucketArea,
InfoIcon,
ProjectUsage,
ProjectSummary,
VInfo,
},
})
export default class ProjectDashboard extends Vue {
@ -36,7 +48,7 @@ export default class ProjectDashboard extends Vue {
*/
public mounted(): void {
if (!this.$store.getters.selectedProject.id) {
this.$router.push(RouteConfig.OnboardingTour.path);
this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.OverviewStep).path);
return;
}
@ -79,5 +91,34 @@ export default class ProjectDashboard extends Vue {
color: #384b65;
margin: 0 0 30px 0;
}
&__header-wrapper {
display: flex;
margin-top: 10px;
}
&__tooltip {
&__wrapper {
margin: 7px 0 0 10px;
/deep/ .info__message-box {
background-image: url('../../../static/images/tooltipMessageBg.png');
min-width: 300px;
text-align: left;
left: 195px;
bottom: 15px;
padding: 10px 10px 10px 35px;
&__text {
&__bold-text {
font-family: 'font_medium', sans-serif;
color: #354049;
}
}
}
}
}
}
</style>

View File

@ -18,8 +18,8 @@
background-color="#f5f6fa"
title-color="#1b2533"
value-color="#000"
title="API Keys"
:value="apiKeysAmount"
title="Access Grants"
:value="accessGrantsAmount"
/>
<SummaryItem
class="right-indent"
@ -80,10 +80,10 @@ export default class ProjectSummary extends Vue {
}
/**
* apiKeysAmount returns API keys amount for selected project.
* accessGrantsAmount returns access grants' amount for selected project.
*/
public get apiKeysAmount(): number {
return this.$store.state.apiKeysModule.page.totalCount;
public get accessGrantsAmount(): number {
return this.$store.state.accessGrantsModule.page.totalCount;
}
/**

View File

@ -18,9 +18,11 @@ import AccountBilling from '@/components/account/billing/BillingArea.vue';
import DetailedHistory from '@/components/account/billing/depositAndBillingHistory/DetailedHistory.vue';
import CreditsHistory from '@/components/account/billing/freeCredits/CreditsHistory.vue';
import SettingsArea from '@/components/account/SettingsArea.vue';
import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue';
import Page404 from '@/components/errors/Page404.vue';
import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
import AddPaymentStep from '@/components/onboardingTour/steps/AddPaymentStep.vue';
import CreateAccessGrantStep from '@/components/onboardingTour/steps/CreateAccessGrantStep.vue';
import OverviewStep from '@/components/onboardingTour/steps/OverviewStep.vue';
import CreateProject from '@/components/project/CreateProject.vue';
import EditProjectDetails from '@/components/project/EditProjectDetails.vue';
import ProjectDashboard from '@/components/project/ProjectDashboard.vue';
@ -47,18 +49,19 @@ export abstract class RouteConfig {
public static Account = new NavigationLink('/account', 'Account');
public static ProjectDashboard = new NavigationLink('/project-dashboard', 'Dashboard');
public static Users = new NavigationLink('/project-members', 'Users');
public static ApiKeys = new NavigationLink('/api-keys', 'API Keys');
public static OnboardingTour = new NavigationLink('/onboarding-tour', 'Onboarding Tour');
public static CreateProject = new NavigationLink('/create-project', 'Create Project');
public static EditProjectDetails = new NavigationLink('/edit-project-details', 'Edit Project Details');
public static AccessGrants = new NavigationLink('/access-grants', 'Access Grants');
// child paths
// account child paths
public static Settings = new NavigationLink('settings', 'Settings');
public static Billing = new NavigationLink('billing', 'Billing');
public static BillingHistory = new NavigationLink('billing-history', 'Billing History');
public static DepositHistory = new NavigationLink('deposit-history', 'Deposit History');
public static CreditsHistory = new NavigationLink('credits-history', 'Credits History');
// access grant child paths
public static CreateAccessGrant = new NavigationLink('create-grant', 'Create Access Grant');
public static NameStep = new NavigationLink('name', 'Name Access Grant');
public static PermissionsStep = new NavigationLink('permissions', 'Access Grant Permissions');
@ -68,6 +71,15 @@ export abstract class RouteConfig {
public static CLIStep = new NavigationLink('cli', 'Access Grant In CLI');
public static UploadStep = new NavigationLink('upload', 'Access Grant Upload Data');
// onboarding tour child paths
public static OverviewStep = new NavigationLink('overview', 'Onboarding Overview');
public static PaymentStep = new NavigationLink('payment', 'Onboarding Payment');
public static AccessGrant = new NavigationLink('access', 'Onboarding Access Grant');
public static AccessGrantName = new NavigationLink('name', 'Onboarding Name Access Grant');
public static AccessGrantPermissions = new NavigationLink('permissions', 'Onboarding Access Grant Permissions');
public static AccessGrantPassphrase = new NavigationLink('create-passphrase', 'Onboarding Access Grant Create Passphrase');
public static AccessGrantResult = new NavigationLink('result', 'Onboarding Access Grant Result');
// TODO: disabled until implementation
// public static Referral = new NavigationLink('referral', 'Referral');
@ -165,15 +177,50 @@ export const router = new Router({
name: RouteConfig.Users.name,
component: ProjectMembersArea,
},
{
path: RouteConfig.ApiKeys.path,
name: RouteConfig.ApiKeys.name,
component: ApiKeysArea,
},
{
path: RouteConfig.OnboardingTour.path,
name: RouteConfig.OnboardingTour.name,
component: OnboardingTourArea,
children: [
{
path: RouteConfig.OverviewStep.path,
name: RouteConfig.OverviewStep.name,
component: OverviewStep,
},
{
path: RouteConfig.PaymentStep.path,
name: RouteConfig.PaymentStep.name,
component: AddPaymentStep,
},
{
path: RouteConfig.AccessGrant.path,
name: RouteConfig.AccessGrant.name,
component: CreateAccessGrantStep,
children: [
{
path: RouteConfig.AccessGrantName.path,
name: RouteConfig.AccessGrantName.name,
component: NameStep,
},
{
path: RouteConfig.AccessGrantPermissions.path,
name: RouteConfig.AccessGrantPermissions.name,
component: PermissionsStep,
props: true,
},
{
path: RouteConfig.AccessGrantPassphrase.path,
name: RouteConfig.AccessGrantPassphrase.name,
component: CreatePassphraseStep,
},
{
path: RouteConfig.AccessGrantResult.path,
name: RouteConfig.AccessGrantResult.name,
component: ResultStep,
},
],
},
],
},
{
path: RouteConfig.CreateProject.path,
@ -258,6 +305,18 @@ router.beforeEach((to, from, next) => {
return;
}
if (navigateToDefaultSubTab(to.matched, RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant))) {
next(RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant).with(RouteConfig.NameStep).path);
return;
}
if (navigateToDefaultSubTab(to.matched, RouteConfig.OnboardingTour)) {
next(RouteConfig.OnboardingTour.with(RouteConfig.OverviewStep).path);
return;
}
if (to.name === 'default') {
next(RouteConfig.ProjectDashboard.path);

View File

@ -1,14 +1,6 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
export enum TourState {
DEFAULT = 1,
ADDING_PAYMENT,
PROJECT,
API_KEY,
UPLOAD,
}
export enum AddingPaymentState {
ADD_CARD = 1,
ADD_STORJ,
@ -19,8 +11,3 @@ export enum AddingStorjState {
VERIFYING,
VERIFIED,
}
export enum AddingApiKeyState {
CREATE = 1,
COPY,
}

View File

@ -52,12 +52,10 @@ import NoPaywallInfoBar from '@/components/noPaywallInfoBar/NoPaywallInfoBar.vue
import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized';
import { RouteConfig } from '@/router';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { USER_ACTIONS } from '@/store/modules/users';
import { ApiKeysPage } from '@/types/apiKeys';
import { Project } from '@/types/projects';
import { User } from '@/types/users';
import { Size } from '@/utils/bytesSize';
@ -166,7 +164,7 @@ export default class DashboardArea extends Vue {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED);
try {
await this.$router.push(RouteConfig.OnboardingTour.path);
await this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.OverviewStep).path);
} catch (error) {
return;
}
@ -176,30 +174,10 @@ export default class DashboardArea extends Vue {
this.selectProject(projects);
let apiKeysPage: ApiKeysPage = new ApiKeysPage();
try {
apiKeysPage = await this.$store.dispatch(API_KEYS_ACTIONS.FETCH, this.FIRST_PAGE);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
await this.$notify.error(`Unable to fetch api keys. ${error.message}`);
}
if (projects.length === 1 && projects[0].ownerId === user.id && apiKeysPage.apiKeys.length === 0) {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED);
try {
await this.$router.push(RouteConfig.OnboardingTour.path);
} catch (error) {
return;
}
return;
}
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
await this.$notify.error(`Unable to fetch api keys. ${error.message}`);
await this.$notify.error(`Unable to fetch access grants. ${error.message}`);
}
try {
@ -234,7 +212,7 @@ export default class DashboardArea extends Vue {
* Indicates if no paywall info bar is shown.
*/
public get isNoPaywallInfoBarShown(): boolean {
const isOnboardingTour: boolean = this.$route.name === RouteConfig.OnboardingTour.name;
const isOnboardingTour: boolean = this.$route.path.includes(RouteConfig.OnboardingTour.path);
return !this.isPaywallEnabled && !isOnboardingTour &&
this.$store.state.paymentsModule.balance.coins === 0 &&

View File

@ -0,0 +1,4 @@
<svg width="15" height="15" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect class="team-header-svg-rect" x="0.5" y="0.5" width="19" height="19" rx="9.5" stroke="#AFB7C1"/>
<path class="team-header-svg-path" d="M7 7.25177C7.00959 6.23527 7.28777 5.44177 7.83453 4.87129C8.38129 4.29043 9.1199 4 10.0504 4C10.952 4 11.6667 4.22819 12.1942 4.68458C12.7314 5.14097 13 5.79444 13 6.64498C13 7.03913 12.9376 7.38661 12.8129 7.68741C12.6882 7.98821 12.5396 8.24234 12.3669 8.44979C12.1942 8.65724 11.9592 8.90099 11.6619 9.18105C11.2686 9.54408 10.9712 9.876 10.7698 10.1768C10.5779 10.4672 10.482 10.8303 10.482 11.2659H9.04317C9.04317 10.851 9.10072 10.488 9.21583 10.1768C9.33094 9.86563 9.46523 9.6115 9.61871 9.41443C9.78177 9.20698 10.0024 8.96841 10.2806 8.69873C10.6067 8.37718 10.8465 8.09712 11 7.85856C11.1535 7.61999 11.2302 7.31919 11.2302 6.95615C11.2302 6.55163 11.1103 6.25082 10.8705 6.05375C10.6403 5.8463 10.3141 5.74257 9.89209 5.74257C9.45084 5.74257 9.10552 5.87223 8.85611 6.13154C8.60671 6.38048 8.47242 6.75389 8.45324 7.25177H7ZM9.73381 12.7595C10.0216 12.7595 10.2566 12.8633 10.4388 13.0707C10.6307 13.2782 10.7266 13.5427 10.7266 13.8642C10.7266 14.1961 10.6307 14.471 10.4388 14.6888C10.2566 14.8963 10.0216 15 9.73381 15C9.45564 15 9.22062 14.8911 9.02878 14.6733C8.84652 14.4554 8.7554 14.1858 8.7554 13.8642C8.7554 13.5427 8.84652 13.2782 9.02878 13.0707C9.22062 12.8633 9.45564 12.7595 9.73381 12.7595Z" fill="#354049"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,3 +0,0 @@
<svg class="info-svg" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 -7.86805e-07C4.02944 -1.22135e-06 1.22135e-06 4.02944 7.86805e-07 9C3.52265e-07 13.9706 4.02944 18 9 18C13.9706 18 18 13.9706 18 9C18 4.02944 13.9706 -3.52265e-07 9 -7.86805e-07ZM10 13C10 13.5523 9.55229 14 9 14C8.44772 14 8 13.5523 8 13C8 12.4477 8.44772 12 9 12C9.55229 12 10 12.4477 10 13ZM10 9C10 9.55228 9.55228 10 9 10C8.44771 10 8 9.55228 8 9L8 5C8 4.44771 8.44772 4 9 4C9.55229 4 10 4.44772 10 5L10 9Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 602 B

View File

@ -1,3 +0,0 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 24C1.15877e-06 10.7452 10.7452 -1.15877e-06 24 0C37.2548 2.09815e-06 48 10.7452 48 24C48 37.2548 37.2548 48 24 48C10.7452 48 -2.09815e-06 37.2548 0 24ZM26.6669 24.0003C26.6669 25.473 25.473 26.6669 24.0003 26.6669C22.5275 26.6669 21.3336 25.473 21.3336 24.0003L21.3336 13.3336C21.3336 11.8609 22.5275 10.6669 24.0003 10.6669C25.473 10.6669 26.6669 11.8609 26.6669 13.3336L26.6669 24.0003ZM24.0003 37.3335C25.473 37.3335 26.6669 36.1396 26.6669 34.6668C26.6669 33.194 25.473 32.0001 24.0003 32.0001C22.5275 32.0001 21.3336 33.194 21.3336 34.6668C21.3336 36.1396 22.5275 37.3335 24.0003 37.3335Z" fill="#F4B000"/>
</svg>

Before

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

View File

@ -28,7 +28,7 @@ const store = new Vuex.Store({ modules: { projectsModule } });
const expectedLinks: NavigationLink[] = [
RouteConfig.ProjectDashboard,
RouteConfig.ApiKeys,
RouteConfig.AccessGrants,
RouteConfig.Users,
];
@ -37,11 +37,14 @@ describe('NavigationArea', () => {
const router = new Router({
mode: 'history',
routes: [{
path: '/',
path: '/onboarding-tour',
name: RouteConfig.OnboardingTour.name,
component: OnboardingTourArea,
}],
});
router.push('/onboarding-tour');
const wrapper = shallowMount(NavigationArea, {
store,
localVue,
@ -67,6 +70,8 @@ describe('NavigationArea', () => {
const projects = await store.dispatch('fetchProjects');
store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, projects[0].id);
router.push('/');
const wrapper = shallowMount(NavigationArea, {
store,
localVue,

View File

@ -11,10 +11,10 @@ exports[`NavigationArea snapshot not changed with project 1`] = `
<h1 class="navigation-area__item-container__link__title">Dashboard</h1>
</div>
</router-link-stub>
<router-link-stub to="/api-keys" tag="a" ariacurrentvalue="page" event="click" aria-label="API Keys" class="navigation-area__item-container">
<router-link-stub to="/access-grants" tag="a" ariacurrentvalue="page" event="click" aria-label="Access Grants" class="navigation-area__item-container">
<div class="navigation-area__item-container__link">
<anonymous-stub></anonymous-stub>
<h1 class="navigation-area__item-container__link__title">API Keys</h1>
<h1 class="navigation-area__item-container__link__title">Access Grants</h1>
</div>
</router-link-stub>
<router-link-stub to="/project-members" tag="a" ariacurrentvalue="page" event="click" aria-label="Users" class="navigation-area__item-container">

View File

@ -1,76 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import ProgressBar from '@/components/onboardingTour/ProgressBar.vue';
import { mount } from '@vue/test-utils';
describe('ProgressBar.vue', () => {
it('renders correctly if paywall is enabled', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isPaywallEnabled: true,
},
});
expect(wrapper).toMatchSnapshot();
});
it('renders correctly if paywall is disabled', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isPaywallEnabled: false,
},
});
expect(wrapper).toMatchSnapshot();
});
it('renders correctly if add payment step is completed', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isAddPaymentStep: true,
isPaywallEnabled: true,
},
});
expect(wrapper.findAll('.completed-step').length).toBe(1);
expect(wrapper.findAll('.completed-font-color').length).toBe(1);
});
it('renders correctly if create project step is completed', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isCreateProjectStep: true,
isPaywallEnabled: true,
},
});
expect(wrapper.findAll('.completed-step').length).toBe(3);
expect(wrapper.findAll('.completed-font-color').length).toBe(2);
});
it('renders correctly if create api key step is completed', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isCreateApiKeyStep: true,
isPaywallEnabled: true,
},
});
expect(wrapper.findAll('.completed-step').length).toBe(5);
expect(wrapper.findAll('.completed-font-color').length).toBe(3);
});
it('renders correctly if upload data step is completed', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isUploadDataStep: true,
isPaywallEnabled: true,
},
});
expect(wrapper.findAll('.completed-step').length).toBe(7);
expect(wrapper.findAll('.completed-font-color').length).toBe(4);
});
});

View File

@ -1,61 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ProgressBar.vue renders correctly if paywall is disabled 1`] = `
<div class="progress-bar-container">
<div class="progress-bar-container__progress-area">
<!---->
<!---->
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
<div class="progress-bar-container__progress-area__bar"></div>
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
<div class="progress-bar-container__progress-area__bar"></div>
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
</div>
<div class="progress-bar-container__titles-area titles-area-no-paywall">
<!----> <span class="progress-bar-container__titles-area__title name-your-project-title title-no-paywall">
Name Your Project
</span> <span class="progress-bar-container__titles-area__title api-key-title">
Create an API Key
</span> <span class="progress-bar-container__titles-area__title">
Upload Data
</span>
</div>
</div>
`;
exports[`ProgressBar.vue renders correctly if paywall is enabled 1`] = `
<div class="progress-bar-container">
<div class="progress-bar-container__progress-area">
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
<div class="progress-bar-container__progress-area__bar"></div>
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
<div class="progress-bar-container__progress-area__bar"></div>
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
<div class="progress-bar-container__progress-area__bar"></div>
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
</div>
<div class="progress-bar-container__titles-area"><span class="progress-bar-container__titles-area__title">
Add Payment
</span> <span class="progress-bar-container__titles-area__title name-your-project-title">
Name Your Project
</span> <span class="progress-bar-container__titles-area__title api-key-title">
Create an API Key
</span> <span class="progress-bar-container__titles-area__title">
Upload Data
</span></div>
</div>
`;

View File

@ -9,13 +9,7 @@ exports[`OnboardingTourArea.vue renders correctly 1`] = `
<closeimage-stub class="tour-area__info-bar__close-img"></closeimage-stub>
</div>
<div class="tour-area__content">
<progressbar-stub ispaywallenabled="true"></progressbar-stub>
<overviewstep-stub></overviewstep-stub>
<!---->
<!---->
<!---->
<!---->
<!---->
<router-view-stub name="default"></router-view-stub>
<!---->
</div>
</div>

View File

@ -6,6 +6,7 @@ import Vuex from 'vuex';
import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
import { PaymentsHttpApi } from '@/api/payments';
import { router } from '@/router';
import { makePaymentsModule } from '@/store/modules/payments';
import { makeProjectsModule } from '@/store/modules/projects';
import { createLocalVue, shallowMount } from '@vue/test-utils';
@ -27,6 +28,7 @@ describe('OnboardingTourArea.vue', () => {
const wrapper = shallowMount(OnboardingTourArea, {
store,
localVue,
router,
});
expect(wrapper).toMatchSnapshot();

View File

@ -7,14 +7,19 @@ import AddPaymentStep from '@/components/onboardingTour/steps/AddPaymentStep.vue
import { PaymentsHttpApi } from '@/api/payments';
import { makePaymentsModule } from '@/store/modules/payments';
import { makeProjectsModule } from '@/store/modules/projects';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { ProjectsApiMock } from '../../mock/api/projects';
const localVue = createLocalVue();
localVue.use(Vuex);
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const paymentsApi = new PaymentsHttpApi();
const paymentsModule = makePaymentsModule(paymentsApi);
const store = new Vuex.Store({ modules: { paymentsModule }});
const store = new Vuex.Store({ modules: { projectsModule, paymentsModule }});
describe('AddPaymentStep.vue', () => {
it('renders correctly', async (): Promise<void> => {

View File

@ -1,61 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import sinon from 'sinon';
import Vuex from 'vuex';
import CreateApiKeyStep from '@/components/onboardingTour/steps/CreateApiKeyStep.vue';
import { makeApiKeysModule } from '@/store/modules/apiKeys';
import { appStateModule } from '@/store/modules/appState';
import { makeProjectsModule } from '@/store/modules/projects';
import { ApiKeysPage } from '@/types/apiKeys';
import { Project } from '@/types/projects';
import { NotificatorPlugin } from '@/utils/plugins/notificator';
import { SegmentioPlugin } from '@/utils/plugins/segment';
import { createLocalVue, mount } from '@vue/test-utils';
import { ApiKeysMock } from '../../mock/api/apiKeys';
import { ProjectsApiMock } from '../../mock/api/projects';
const localVue = createLocalVue();
const notificationPlugin = new NotificatorPlugin();
const segmentioPlugin = new SegmentioPlugin();
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const apiKeysApi = new ApiKeysMock();
const apiKeysModule = makeApiKeysModule(apiKeysApi);
apiKeysApi.setMockApiKeysPage(new ApiKeysPage());
const project = new Project('id', 'projectName', 'projectDescription', 'test', 'testOwnerId', true);
projectsApi.setMockProjects([project]);
localVue.use(Vuex);
localVue.use(notificationPlugin);
localVue.use(segmentioPlugin);
const store = new Vuex.Store({ modules: { projectsModule, apiKeysModule, appStateModule }});
describe('CreateApiKeyStep.vue', () => {
it('renders correctly', (): void => {
const wrapper = mount(CreateApiKeyStep, {
store,
localVue,
});
expect(wrapper.findAll('.disabled').length).toBe(1);
expect(wrapper).toMatchSnapshot();
});
it('create api key works correctly correctly', async (): Promise<void> => {
const wrapper = mount(CreateApiKeyStep, {
store,
localVue,
});
await wrapper.vm.setApiKeyName('testName');
await wrapper.vm.createApiKey();
expect(wrapper.findAll('.disabled').length).toBe(0);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -1,54 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import sinon from 'sinon';
import Vuex from 'vuex';
import CreateProjectStep from '@/components/onboardingTour/steps/CreateProjectStep.vue';
import { PaymentsHttpApi } from '@/api/payments';
import { makePaymentsModule } from '@/store/modules/payments';
import { makeProjectsModule } from '@/store/modules/projects';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import { ProjectsApiMock } from '../../mock/api/projects';
const localVue = createLocalVue();
localVue.use(Vuex);
const paymentsApi = new PaymentsHttpApi();
const paymentsModule = makePaymentsModule(paymentsApi);
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const store = new Vuex.Store({ modules: { paymentsModule, projectsModule }});
describe('CreateProjectStep.vue', () => {
it('renders correctly', (): void => {
const wrapper = shallowMount(CreateProjectStep, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
it('click works correctly', async (): Promise<void> => {
const clickSpy = sinon.spy();
const wrapper = mount(CreateProjectStep, {
store,
localVue,
});
wrapper.vm.createProjectClick = clickSpy;
expect(wrapper.findAll('.disabled').length).toBe(1);
await wrapper.vm.setProjectName('test');
expect(wrapper.findAll('.disabled').length).toBe(0);
await wrapper.find('.create-project-button').trigger('click');
expect(clickSpy.callCount).toBe(1);
});
});

View File

@ -1,18 +1,41 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import OverviewStep from '@/components/onboardingTour/steps/OverviewStep.vue';
import { mount } from '@vue/test-utils';
import { PaymentsHttpApi } from '@/api/payments';
import { router } from '@/router';
import { makePaymentsModule, PAYMENTS_MUTATIONS } from '@/store/modules/payments';
import { makeProjectsModule } from '@/store/modules/projects';
import { createLocalVue, mount } from '@vue/test-utils';
describe('OverviewStep.vue', () => {
import { ProjectsApiMock } from '../../mock/api/projects';
const localVue = createLocalVue();
localVue.use(Vuex);
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const paymentsApi = new PaymentsHttpApi();
const paymentsModule = makePaymentsModule(paymentsApi);
const store = new Vuex.Store({ modules: { projectsModule, paymentsModule }});
describe('OverviewStep.vue', (): void => {
it('renders correctly', async (): Promise<void> => {
const wrapper = mount(OverviewStep);
const wrapper = mount(OverviewStep, {
localVue,
router,
store,
});
await store.commit(PAYMENTS_MUTATIONS.SET_PAYWALL_ENABLED_STATUS, true);
expect(wrapper).toMatchSnapshot();
await wrapper.find('.get-started-button').trigger('click');
await store.commit(PAYMENTS_MUTATIONS.SET_PAYWALL_ENABLED_STATUS, false);
expect(wrapper.emitted()).toHaveProperty('setAddPaymentState');
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -1,14 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import SaveApiKeyModal from '@/components/onboardingTour/steps/SaveApiKeyModal.vue';
import { mount } from '@vue/test-utils';
describe('SaveApiKeyModal.vue', () => {
it('renders correctly', (): void => {
const wrapper = mount(SaveApiKeyModal);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -1,18 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import UploadDataStep from '@/components/onboardingTour/steps/UploadDataStep.vue';
import { createLocalVue, mount } from '@vue/test-utils';
const localVue = createLocalVue();
describe('UploadDataStep.vue', () => {
it('renders correctly', (): void => {
const wrapper = mount(UploadDataStep, {
localVue,
});
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -4,8 +4,8 @@ exports[`AddPaymentStep.vue renders correctly 1`] = `
<div class="payment-step">
<h1 class="payment-step__title">Get Started with 50 GB Free</h1>
<p class="payment-step__sub-title">
Adding a payment method ensures your project wont be interrupted after your <b>free</b> credit is used.
</p>
Experience the decentralized cloud for free! If you find our network isnt for you, <b class="bold">cancel
any time before your credit runs out and you wont be billed.</b></p>
<div class="payment-step__methods-container">
<div class="payment-step__methods-container__title-area">
<h2 class="payment-step__methods-container__title-area__title">Payment Method</h2>
@ -44,8 +44,8 @@ exports[`AddPaymentStep.vue renders correctly 2`] = `
<div class="payment-step">
<h1 class="payment-step__title">Get Started with 50 GB Free</h1>
<p class="payment-step__sub-title">
Adding a payment method ensures your project wont be interrupted after your <b>free</b> credit is used.
</p>
Experience the decentralized cloud for free! If you find our network isnt for you, <b class="bold">cancel
any time before your credit runs out and you wont be billed.</b></p>
<div class="payment-step__methods-container">
<div class="payment-step__methods-container__title-area">
<h2 class="payment-step__methods-container__title-area__title">Payment Method</h2>

View File

@ -1,85 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CreateApiKeyStep.vue create api key works correctly correctly 1`] = `
<div class="create-api-key-step">
<h1 class="create-api-key-step__title">Create an API Key</h1>
<p class="create-api-key-step__sub-title">
API keys provide access to the project for creating buckets and uploading objects through the command line
interface. This will be your first API key, and you can always create more keys later on.
</p>
<div class="create-api-key-step__container">
<div class="create-api-key-step__container__title-area">
<h2 class="create-api-key-step__container__title-area__title">Create API Key</h2>
<!---->
</div>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">API Key Name</h3>
<h3 class="label-container__main__label add-label"></h3>
<!---->
</div>
<!---->
</div>
<!---->
<!----> <input id="API Key Name" placeholder="Enter API Key Name (i.e. Dans Key)" type="text" class="headered-input" style="height: 48px;">
</div>
<div class="create-api-key-step__container__copy-key-area">
<div class="create-api-key-step__container__copy-key-area__header"><svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" class="info-svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 -7.86805e-07C4.02944 -1.22135e-06 1.22135e-06 4.02944 7.86805e-07 9C3.52265e-07 13.9706 4.02944 18 9 18C13.9706 18 18 13.9706 18 9C18 4.02944 13.9706 -3.52265e-07 9 -7.86805e-07ZM10 13C10 13.5523 9.55229 14 9 14C8.44772 14 8 13.5523 8 13C8 12.4477 8.44772 12 9 12C9.55229 12 10 12.4477 10 13ZM10 9C10 9.55228 9.55228 10 9 10C8.44771 10 8 9.55228 8 9L8 5C8 4.44771 8.44772 4 9 4C9.55229 4 10 4.44772 10 5L10 9Z" fill="white"></path>
</svg> <span class="create-api-key-step__container__copy-key-area__header__title">
API Keys only appear here once. Copy and paste this key to your preferred method of storing secrets.
</span></div>
<div class="create-api-key-step__container__copy-key-area__key-container"><span class="create-api-key-step__container__copy-key-area__key-container__key">testKey</span>
<div class="create-api-key-step__container__copy-key-area__key-container__copy-button">
<div class="container blue-white" style="width: 81px; height: 40px;"><span class="label">Copy</span></div>
</div>
</div>
</div>
<p class="create-api-key-step__container__info">
We dont record your API Keys, which are only displayed once when generated. If you loose this
key, it cannot be recovered but you can always create new API Keys when needed.
</p>
<!---->
</div>
<div class="done-button container" style="width: 156px; height: 48px;"><span class="label">Done</span></div>
<!---->
</div>
`;
exports[`CreateApiKeyStep.vue renders correctly 1`] = `
<div class="create-api-key-step">
<h1 class="create-api-key-step__title">Create an API Key</h1>
<p class="create-api-key-step__sub-title">
API keys provide access to the project for creating buckets and uploading objects through the command line
interface. This will be your first API key, and you can always create more keys later on.
</p>
<div class="create-api-key-step__container">
<div class="create-api-key-step__container__title-area">
<h2 class="create-api-key-step__container__title-area__title">Create API Key</h2>
<!---->
</div>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">API Key Name</h3>
<h3 class="label-container__main__label add-label"></h3>
<!---->
</div>
<!---->
</div>
<!---->
<!----> <input id="API Key Name" placeholder="Enter API Key Name (i.e. Dans Key)" type="text" class="headered-input" style="height: 48px;">
</div>
<div class="create-api-key-step__container__create-key-area">
<div class="generate-button container blue-white" style="width: 100%; height: 40px;"><span class="label">Generate API Key</span></div>
</div>
<!---->
<!---->
</div>
<div class="done-button container disabled" style="width: 156px; height: 48px;"><span class="label">Done</span></div>
<!---->
</div>
`;

View File

@ -1,21 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CreateProjectStep.vue renders correctly 1`] = `
<div class="new-project-step">
<h1 class="new-project-step__title">Name Your Project</h1>
<p class="new-project-step__sub-title">
Projects are where buckets are created for storing data. Within a Project, usage is tracked at the bucket
level and aggregated for billing.
</p>
<div class="new-project-step__container">
<div class="new-project-step__container__title-area">
<h2 class="new-project-step__container__title-area__title">Project Details</h2>
<!---->
</div>
<headeredinput-stub label="Project Name" placeholder="Enter Project Name" height="48px" width="100%" error="" maxsymbols="20" initvalue="" additionallabel="Up To 20 Characters" currentlimit="0" islimitshown="true" class="full-input project-name-input"></headeredinput-stub>
<headeredinput-stub label="Description" placeholder="Enter Project Description" height="60px" width="calc(100% - 42px)" error="" maxsymbols="100" initvalue="" additionallabel="Optional" currentlimit="0" islimitshown="true" ismultiline="true" class="full-input"></headeredinput-stub>
<!---->
</div>
<vbutton-stub label="Create Project" width="156px" height="48px" isdisabled="true" onpress="function () { [native code] }" class="create-project-button"></vbutton-stub>
</div>
`;

View File

@ -2,45 +2,52 @@
exports[`OverviewStep.vue renders correctly 1`] = `
<div class="overview-area">
<h1 class="overview-area__title">Welcome to Storj</h1>
<h1 class="overview-area__title">
Welcome to Storj.
<br>
Lets Get Started.
</h1>
<p class="overview-area__sub-title">
Youre just a few steps away from uploading your first object to the 100% secure, decentralized cloud. After
adding payment, youll create a project, API key, get set up with Storj, and start uploading objects.
Follow the docs to start storing data using method below.
</p>
<div class="overview-area__steps-area">
<div class="overview-area__steps-area__step"><svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__step__icon">
<circle cx="15" cy="15" r="15" fill="#519CFF"></circle>
<path d="M17.0916 9.36364H14.7791L11.8984 11.1875V13.3693L14.5632 11.6989H14.6314V21H17.0916V9.36364Z" fill="white"></path>
</svg> <img src="@/../static/images/onboardingTour/card.png" alt="card image" class="overview-step-image">
<h2 class="overview-area__steps-area__step__title">Add Payment</h2> <span class="overview-area__steps-area__step__subtitle">
Get 50 GB free to try Storj. Cancel anytime.
</span>
</div>
<div class="overview-area__steps-area__step second-step"><svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__step__icon">
<circle cx="15" cy="15" r="15" fill="#519CFF"></circle>
<path d="M11.4041 21H19.6996V18.9886H14.8132V18.9091L16.5121 17.2443C18.9041 15.0625 19.5462 13.9716 19.5462 12.6477C19.5462 10.6307 17.8984 9.20455 15.4041 9.20455C12.9609 9.20455 11.2848 10.6648 11.2905 12.9489H13.6257C13.62 11.8352 14.3246 11.1534 15.3871 11.1534C16.4098 11.1534 17.1712 11.7898 17.1712 12.8125C17.1712 13.7386 16.603 14.375 15.5462 15.392L11.4041 19.2273V21Z" fill="white"></path>
</svg> <img src="@/../static/images/onboardingTour/project.jpg" alt="project image" class="overview-step-image">
<h2 class="overview-area__steps-area__step__title">Name Your Project</h2> <span class="overview-area__steps-area__step__subtitle">
Projects are where buckets are created for storing data.
</span>
</div>
<div class="overview-area__steps-area__step third-step"><svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__step__icon">
<circle cx="15" cy="15" r="15" fill="#519CFF"></circle>
<path d="M15.4709 21.1591C18.0845 21.1591 19.9538 19.7216 19.9482 17.733C19.9538 16.2841 19.0334 15.25 17.3232 15.0341V14.9432C18.6243 14.7102 19.522 13.7898 19.5163 12.483C19.522 10.6477 17.9141 9.20455 15.505 9.20455C13.1186 9.20455 11.3232 10.6023 11.2891 12.6136H13.647C13.6754 11.7273 14.4879 11.1534 15.4936 11.1534C16.4879 11.1534 17.1527 11.7557 17.147 12.6307C17.1527 13.5455 16.3743 14.1648 15.255 14.1648H14.1697V15.9716H15.255C16.5732 15.9716 17.397 16.6307 17.3913 17.5682C17.397 18.4943 16.6016 19.1307 15.4766 19.1307C14.3913 19.1307 13.5788 18.5625 13.5334 17.7102H11.0561C11.0959 19.7443 12.9141 21.1591 15.4709 21.1591Z" fill="white"></path>
</svg> <img src="@/../static/images/onboardingTour/api-key.jpg" alt="api keys image" class="overview-step-image">
<h2 class="overview-area__steps-area__step__title">Create an API Key</h2> <span class="overview-area__steps-area__step__subtitle">
Generate access to your project to upload data.
</span>
</div>
<div class="overview-area__steps-area__step"><svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__step__icon">
<circle cx="15" cy="15" r="15" fill="#2582FF"></circle>
<path d="M10.8452 18.9545H16.4304V21H18.7827V18.9545H20.2259V16.983H18.7827V9.36364H15.7031L10.8452 17.017V18.9545ZM16.4759 16.983H13.3224V16.892L16.3849 12.0455H16.4759V16.983Z" fill="white"></path>
</svg> <img src="@/../static/images/onboardingTour/uplink.jpg" alt="uplink image" class="overview-step-image">
<h2 class="overview-area__steps-area__step__title">Upload Data</h2> <span class="overview-area__steps-area__step__subtitle">
Store your data on the secure, decentralized cloud.
</span>
<div class="overview-area__steps-area__step"><img src="@/../static/images/onboardingTour/cli.png" alt="cli image" class="overview-area__steps-area__step__image">
<h2 class="overview-area__steps-area__step__title">CLI Tool</h2>
<p class="overview-area__steps-area__step__info">
Quickly upload data directly through the command line interface.
</p> <a href="https://documentation.storj.io/setup/cli" target="_blank" class="overview-area__steps-area__step__link">
CLI Docs &gt;
</a>
</div>
</div>
<div class="get-started-button container" style="width: 251px; height: 56px;"><span class="label">Get Started</span></div>
<div class="overview-area__divider"></div>
<p class="overview-area__next-label">Next</p>
<div class="overview-area__cta container" style="width: 252px; height: 48px;"><span class="label">Add a Payment Method</span></div>
</div>
`;
exports[`OverviewStep.vue renders correctly 2`] = `
<div class="overview-area">
<h1 class="overview-area__title">
Welcome to Storj.
<br>
Lets Get Started.
</h1>
<p class="overview-area__sub-title">
Follow the docs to start storing data using method below.
</p>
<div class="overview-area__steps-area">
<div class="overview-area__steps-area__step"><img src="@/../static/images/onboardingTour/cli.png" alt="cli image" class="overview-area__steps-area__step__image">
<h2 class="overview-area__steps-area__step__title">CLI Tool</h2>
<p class="overview-area__steps-area__step__info">
Quickly upload data directly through the command line interface.
</p> <a href="https://documentation.storj.io/setup/cli" target="_blank" class="overview-area__steps-area__step__link">
CLI Docs &gt;
</a>
</div>
</div>
<div class="overview-area__divider"></div>
<p class="overview-area__next-label">Next</p>
<div class="overview-area__cta container" style="width: 252px; height: 48px;"><span class="label">Create an Access Grant</span></div>
</div>
`;

View File

@ -1,19 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SaveApiKeyModal.vue renders correctly 1`] = `
<div class="save-api-modal">
<div class="save-api-modal__container"><svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 24C1.15877e-06 10.7452 10.7452 -1.15877e-06 24 0C37.2548 2.09815e-06 48 10.7452 48 24C48 37.2548 37.2548 48 24 48C10.7452 48 -2.09815e-06 37.2548 0 24ZM26.6669 24.0003C26.6669 25.473 25.473 26.6669 24.0003 26.6669C22.5275 26.6669 21.3336 25.473 21.3336 24.0003L21.3336 13.3336C21.3336 11.8609 22.5275 10.6669 24.0003 10.6669C25.473 10.6669 26.6669 11.8609 26.6669 13.3336L26.6669 24.0003ZM24.0003 37.3335C25.473 37.3335 26.6669 36.1396 26.6669 34.6668C26.6669 33.194 25.473 32.0001 24.0003 32.0001C22.5275 32.0001 21.3336 33.194 21.3336 34.6668C21.3336 36.1396 22.5275 37.3335 24.0003 37.3335Z" fill="#F4B000"></path>
</svg>
<h1 class="save-api-modal__container__title">Is Your API Key Saved?</h1>
<p class="save-api-modal__container__message">
API Keys are only displayed once when generated. If you havent saved your key, go back to copy and
paste the API key to your preferred method of storing secrets (i.e. TextEdit, Keybase, etc.)
</p>
<div class="save-api-modal__container__buttons-area">
<div class="back-button container blue-white" style="width: 186px; height: 45px;"><span class="label">Go Back</span></div>
<div class="container" style="width: 186px; height: 45px;"><span class="label">Yes, it's Saved!</span></div>
</div>
</div>
</div>
`;

View File

@ -1,59 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UploadDataStep.vue renders correctly 1`] = `
<div class="upload-data-area">
<div class="upload-data-area__container">
<h1 class="upload-data-area__container__title">Upload Data</h1>
<p class="upload-data-area__container__sub-title">
From here, youll set up Tardigrade to store data for your project using our S3 Gateway, Uplink CLI, or
select from our growing library of connectors to build apps on Tardigrade.
</p>
<div class="upload-data-area__container__docs-area">
<div class="upload-data-area__container__docs-area__option">
<h2 class="upload-data-area__container__docs-area__option__title">
Migrate Data from your Existing AWS buckets
</h2> <img src="@/../static/images/onboardingTour/s3.png" alt="s3 gateway image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
S3 Gateway
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Make the switch with Tardigrades S3 Gateway.
</p> <a href="https://documentation.tardigrade.io/api-reference/s3-gateway" target="_blank" rel="noopener noreferrer" class="upload-data-area__container__docs-area__option__link">
S3 Gateway Docs
</a>
</div>
<div class="upload-data-area__container__docs-area__option uplink-option">
<h2 class="upload-data-area__container__docs-area__option__title">
Upload Data from Your Local Environment
</h2> <img src="@/../static/images/onboardingTour/uplinkcli.png" alt="uplink cli image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
Uplink CLI
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Start uploading data from the command line.
</p> <a href="https://documentation.tardigrade.io/getting-started/uploading-your-first-object/set-up-uplink-cli" target="_blank" rel="noopener noreferrer" class="upload-data-area__container__docs-area__option__link">
Uplink CLI Docs
</a>
</div>
<div class="upload-data-area__container__docs-area__option">
<h2 class="upload-data-area__container__docs-area__option__title">
Use Tardigrade for your apps storage layer
</h2> <img src="@/../static/images/onboardingTour/connectors.png" alt="connectors image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
App Connectors
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Integrate Tardigrade into your existing stack.
</p> <a href="https://tardigrade.io/connectors/" target="_blank" rel="noopener noreferrer" class="upload-data-area__container__docs-area__option__link">
App Connectors
</a>
</div>
</div>
</div>
<div class="go-to-button container" style="width: 276px; height: 40px;"><span class="label">Go to Dashboard</span></div> <span class="upload-data-area__support-info">
Need help?
<a href="https://support.tardigrade.io/hc/en-us" target="_blank" rel="noopener noreferrer" class="upload-data-area__support-info__link">
Contact support
</a></span>
</div>
`;

View File

@ -11,8 +11,7 @@ exports[`AddCardState.vue renders correctly 1`] = `
Your card is secured by 128-bit SSL and AES-256 encryption. Your information is secure.
</span>
</div>
<div class="add-card-state__button">
<!----> <span class="add-card-state__button__label">Add Payment</span>
</div>
<p class="add-card-state__next-label">Next</p>
<vbutton-stub label="Create an Access Grant" width="252px" height="48px" onpress="function () { [native code] }"></vbutton-stub>
</div>
`;

View File

@ -20,7 +20,8 @@ exports[`AddStorjState.vue renders correctly 1`] = `
</div>
<!---->
</div>
<vbutton-stub label="Name Your Project" width="222px" height="48px" isdisabled="true" onpress="function () { [native code] }"></vbutton-stub>
<p class="add-storj-state__next-label">Next</p>
<vbutton-stub label="Create an Acess Grant" width="252px" height="48px" isdisabled="true" onpress="function () { [native code] }"></vbutton-stub>
</div>
`;
@ -44,7 +45,8 @@ exports[`AddStorjState.vue renders correctly with completed transaction 1`] = `
</div>
<!---->
</div>
<vbutton-stub label="Name Your Project" width="222px" height="48px" onpress="function () { [native code] }"></vbutton-stub>
<p class="add-storj-state__next-label">Next</p>
<vbutton-stub label="Create an Acess Grant" width="252px" height="48px" onpress="function () { [native code] }"></vbutton-stub>
</div>
`;
@ -68,6 +70,7 @@ exports[`AddStorjState.vue renders correctly with pending transaction 1`] = `
</div>
<!---->
</div>
<vbutton-stub label="Name Your Project" width="222px" height="48px" isdisabled="true" onpress="function () { [native code] }"></vbutton-stub>
<p class="add-storj-state__next-label">Next</p>
<vbutton-stub label="Create an Acess Grant" width="252px" height="48px" isdisabled="true" onpress="function () { [native code] }"></vbutton-stub>
</div>
`;

View File

@ -9,6 +9,6 @@ exports[`PayingStep.vue renders correctly 1`] = `
<!---->
</div>
<tokendepositselection-stub class="paying-step__form"></tokendepositselection-stub>
<vbutton-stub label="Continue to Coin Payments" width="100%" height="48px" isbluewhite="true" onpress="function () { [native code] }"></vbutton-stub>
<vbutton-stub label="Continue to Coin Payments" width="calc(100% - 4px)" height="48px" isbluewhite="true" onpress="function () { [native code] }"></vbutton-stub>
</div>
`;

View File

@ -4,8 +4,8 @@ exports[`VerifyingStep.vue renders correctly 1`] = `
<div class="verifying-step">
<verifyingimage-stub></verifyingimage-stub>
<h2 class="verifying-step__title">Verifying Payment</h2> <span class="verifying-step__sub-title">
Verification may take up to an hour. In the meantime, see how easy it is to get started visiting documentation
page, or check coin payment status.
Verification may take up to a few hours. In the meantime, see how easy it is to get started visiting
documentation page, or check coin payment status.
</span>
<div class="verifying-step__buttons-area"><a href="https://documentation.storj.io" target="_blank" rel="noopener noreferrer" class="verifying-step__buttons-area__how-to-button">
Documentation

View File

@ -2,7 +2,12 @@
exports[`ProjectDashboard.vue renders correctly 1`] = `
<div class="dashboard-area">
<h1 class="dashboard-area__title">test Dashboard</h1>
<div class="dashboard-area__header-wrapper">
<h1 class="dashboard-area__title">test Dashboard</h1>
<vinfo-stub text="" boldtext="Expect a delay of a few hours between network activity and the latest dashboard stats." class="dashboard-area__tooltip__wrapper">
<infoicon-stub class="dashboard-area__tooltip__icon"></infoicon-stub>
</vinfo-stub>
</div>
<projectusage-stub></projectusage-stub>
<projectsummary-stub></projectsummary-stub>
<bucketarea-stub></bucketarea-stub>

View File

@ -5,7 +5,7 @@ import Vuex from 'vuex';
import ProjectSummary from '@/components/project/summary/ProjectSummary.vue';
import { makeApiKeysModule } from '@/store/modules/apiKeys';
import { makeAccessGrantsModule } from '@/store/modules/accessGrants';
import { makeBucketsModule } from '@/store/modules/buckets';
import { makePaymentsModule } from '@/store/modules/payments';
import { makeProjectMembersModule } from '@/store/modules/projectMembers';
@ -13,7 +13,7 @@ import { makeProjectsModule } from '@/store/modules/projects';
import { NotificatorPlugin } from '@/utils/plugins/notificator';
import { createLocalVue, mount } from '@vue/test-utils';
import { ApiKeysMock } from '../../mock/api/apiKeys';
import { AccessGrantsMock } from '../../mock/api/accessGrants';
import { BucketsMock } from '../../mock/api/buckets';
import { PaymentsMock } from '../../mock/api/payments';
import { ProjectMembersApiMock } from '../../mock/api/projectMembers';
@ -35,12 +35,12 @@ const paymentsApi = new PaymentsMock();
const paymentsModule = makePaymentsModule(paymentsApi);
const bucketsApi = new BucketsMock();
const bucketUsageModule = makeBucketsModule(bucketsApi);
const apiKeysApi = new ApiKeysMock();
const apiKeysModule = makeApiKeysModule(apiKeysApi);
const accessGrantsApi = new AccessGrantsMock();
const accessGrantsModule = makeAccessGrantsModule(accessGrantsApi);
const projectMembersApi = new ProjectMembersApiMock();
const projectMembersModule = makeProjectMembersModule(projectMembersApi);
const store = new Vuex.Store({ modules: { projectsModule, paymentsModule, bucketUsageModule, projectMembersModule, apiKeysModule }});
const store = new Vuex.Store({ modules: { projectsModule, paymentsModule, bucketUsageModule, projectMembersModule, accessGrantsModule }});
describe('ProjectSummary.vue', (): void => {
it('renders correctly', (): void => {

View File

@ -9,7 +9,7 @@ exports[`ProjectSummary.vue renders correctly 1`] = `
<p class="summary-item__value" style="color: rgb(0, 0, 0);">0</p>
</div>
<div class="summary-item right-indent" style="background-color: rgb(245, 246, 250);">
<h1 class="summary-item__title" style="color: rgb(27, 37, 51);">API Keys</h1>
<h1 class="summary-item__title" style="color: rgb(27, 37, 51);">Access Grants</h1>
<p class="summary-item__value" style="color: rgb(0, 0, 0);">0</p>
</div>
<div class="summary-item right-indent" style="background-color: rgb(177, 193, 217);">

View File

@ -4,7 +4,7 @@
import Vuex from 'vuex';
import { RouteConfig, router } from '@/router';
import { makeApiKeysModule } from '@/store/modules/apiKeys';
import { makeAccessGrantsModule } from '@/store/modules/accessGrants';
import { appStateModule } from '@/store/modules/appState';
import { makeBucketsModule } from '@/store/modules/buckets';
import { makeNotificationsModule } from '@/store/modules/notifications';
@ -12,7 +12,6 @@ import { makePaymentsModule } from '@/store/modules/payments';
import { makeProjectMembersModule } from '@/store/modules/projectMembers';
import { makeProjectsModule } from '@/store/modules/projects';
import { makeUsersModule } from '@/store/modules/users';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { User } from '@/types/users';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { AppState } from '@/utils/constants/appStateEnum';
@ -21,6 +20,7 @@ import { SegmentioPlugin } from '@/utils/plugins/segment';
import DashboardArea from '@/views/DashboardArea.vue';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { AccessGrantsMock } from '../mock/api/accessGrants';
import { ApiKeysMock } from '../mock/api/apiKeys';
import { BucketsMock } from '../mock/api/buckets';
import { PaymentsMock } from '../mock/api/payments';
@ -43,7 +43,7 @@ projectsApi.setMockProjects([]);
const usersModule = makeUsersModule(usersApi);
const projectsModule = makeProjectsModule(projectsApi);
const apiKeysModule = makeApiKeysModule(new ApiKeysMock());
const accessGrantsModule = makeAccessGrantsModule(new AccessGrantsMock());
const teamMembersModule = makeProjectMembersModule(new ProjectMembersApiMock());
const bucketsModule = makeBucketsModule(new BucketsMock());
const notificationsModule = makeNotificationsModule();
@ -53,7 +53,7 @@ const store = new Vuex.Store({
modules: {
notificationsModule,
bucketsModule,
apiKeysModule,
accessGrantsModule,
usersModule,
projectsModule,
appStateModule,
@ -114,7 +114,7 @@ describe('Dashboard', () => {
it('loads routes correctly when authorithed without project with unavailable routes', async () => {
const unavailableWithoutProject = [
RouteConfig.ApiKeys.path,
RouteConfig.AccessGrants.path,
RouteConfig.Users.path,
RouteConfig.ProjectDashboard.path,
];