1
Fork 0

Add set password from share dropdown in sidebar

This commit is contained in:
viktorstrate 2020-06-14 15:07:07 +02:00
parent abfcea9072
commit bbb6e2eca2
5 changed files with 423 additions and 90 deletions

View File

@ -76,6 +76,7 @@ type ComplexityRoot struct {
DeleteShareToken func(childComplexity int, token string) int DeleteShareToken func(childComplexity int, token string) int
DeleteUser func(childComplexity int, id int) int DeleteUser func(childComplexity int, id int) int
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
ProtectShareToken func(childComplexity int, token string, password *string) int
RegisterUser func(childComplexity int, username string, password string, rootPath string) int RegisterUser func(childComplexity int, username string, password string, rootPath string) int
ScanAll func(childComplexity int) int ScanAll func(childComplexity int) int
ScanUser func(childComplexity int, userID int) int ScanUser func(childComplexity int, userID int) int
@ -163,6 +164,7 @@ type ComplexityRoot struct {
ShareToken struct { ShareToken struct {
Album func(childComplexity int) int Album func(childComplexity int) int
Expire func(childComplexity int) int Expire func(childComplexity int) int
HasPassword func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
Owner func(childComplexity int) int Owner func(childComplexity int) int
Photo func(childComplexity int) int Photo func(childComplexity int) int
@ -204,6 +206,7 @@ type MutationResolver interface {
ShareAlbum(ctx context.Context, albumID int, expire *time.Time, password *string) (*models.ShareToken, error) ShareAlbum(ctx context.Context, albumID int, expire *time.Time, password *string) (*models.ShareToken, error)
SharePhoto(ctx context.Context, photoID int, expire *time.Time, password *string) (*models.ShareToken, error) SharePhoto(ctx context.Context, photoID int, expire *time.Time, password *string) (*models.ShareToken, error)
DeleteShareToken(ctx context.Context, token string) (*models.ShareToken, error) DeleteShareToken(ctx context.Context, token string) (*models.ShareToken, error)
ProtectShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error)
UpdateUser(ctx context.Context, id int, username *string, rootPath *string, password *string, admin *bool) (*models.User, error) UpdateUser(ctx context.Context, id int, username *string, rootPath *string, password *string, admin *bool) (*models.User, error)
CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error) CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error)
DeleteUser(ctx context.Context, id int) (*models.User, error) DeleteUser(ctx context.Context, id int) (*models.User, error)
@ -230,6 +233,7 @@ type QueryResolver interface {
type ShareTokenResolver interface { type ShareTokenResolver interface {
Owner(ctx context.Context, obj *models.ShareToken) (*models.User, error) Owner(ctx context.Context, obj *models.ShareToken) (*models.User, error)
HasPassword(ctx context.Context, obj *models.ShareToken) (bool, error)
Album(ctx context.Context, obj *models.ShareToken) (*models.Album, error) Album(ctx context.Context, obj *models.ShareToken) (*models.Album, error)
Photo(ctx context.Context, obj *models.ShareToken) (*models.Photo, error) Photo(ctx context.Context, obj *models.ShareToken) (*models.Photo, error)
} }
@ -413,6 +417,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.InitialSetupWizard(childComplexity, args["username"].(string), args["password"].(string), args["rootPath"].(string)), true return e.complexity.Mutation.InitialSetupWizard(childComplexity, args["username"].(string), args["password"].(string), args["rootPath"].(string)), true
case "Mutation.protectShareToken":
if e.complexity.Mutation.ProtectShareToken == nil {
break
}
args, err := ec.field_Mutation_protectShareToken_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.ProtectShareToken(childComplexity, args["token"].(string), args["password"].(*string)), true
case "Mutation.registerUser": case "Mutation.registerUser":
if e.complexity.Mutation.RegisterUser == nil { if e.complexity.Mutation.RegisterUser == nil {
break break
@ -893,6 +909,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ShareToken.Expire(childComplexity), true return e.complexity.ShareToken.Expire(childComplexity), true
case "ShareToken.hasPassword":
if e.complexity.ShareToken.HasPassword == nil {
break
}
return e.complexity.ShareToken.HasPassword(childComplexity), true
case "ShareToken.id": case "ShareToken.id":
if e.complexity.ShareToken.ID == nil { if e.complexity.ShareToken.ID == nil {
break break
@ -1117,6 +1140,8 @@ type Mutation {
sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken
"Delete a share token by it's token value" "Delete a share token by it's token value"
deleteShareToken(token: String!): ShareToken deleteShareToken(token: String!): ShareToken
"Set a password for a token, if null is passed for the password argument, the password will be cleared"
protectShareToken(token: String!, password: String): ShareToken
updateUser( updateUser(
id: Int! id: Int!
@ -1178,6 +1203,8 @@ type ShareToken {
owner: User! owner: User!
"Optional expire date" "Optional expire date"
expire: Time expire: Time
"Whether or not a password is needed to access the share"
hasPassword: Boolean!
"The album this token shares" "The album this token shares"
album: Album album: Album
@ -1437,6 +1464,28 @@ func (ec *executionContext) field_Mutation_initialSetupWizard_args(ctx context.C
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_protectShareToken_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 string
if tmp, ok := rawArgs["token"]; ok {
arg0, err = ec.unmarshalNString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["token"] = arg0
var arg1 *string
if tmp, ok := rawArgs["password"]; ok {
arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["password"] = arg1
return args, nil
}
func (ec *executionContext) field_Mutation_registerUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_registerUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -2548,6 +2597,44 @@ func (ec *executionContext) _Mutation_deleteShareToken(ctx context.Context, fiel
return ec.marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res) return ec.marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res)
} }
func (ec *executionContext) _Mutation_protectShareToken(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_protectShareToken_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().ProtectShareToken(rctx, args["token"].(string), args["password"].(*string))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*models.ShareToken)
fc.Result = res
return ec.marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_updateUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_updateUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -4713,6 +4800,40 @@ func (ec *executionContext) _ShareToken_expire(ctx context.Context, field graphq
return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res) return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res)
} }
func (ec *executionContext) _ShareToken_hasPassword(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ShareToken",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.ShareToken().HasPassword(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _ShareToken_album(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) { func (ec *executionContext) _ShareToken_album(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -6311,6 +6432,8 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
out.Values[i] = ec._Mutation_sharePhoto(ctx, field) out.Values[i] = ec._Mutation_sharePhoto(ctx, field)
case "deleteShareToken": case "deleteShareToken":
out.Values[i] = ec._Mutation_deleteShareToken(ctx, field) out.Values[i] = ec._Mutation_deleteShareToken(ctx, field)
case "protectShareToken":
out.Values[i] = ec._Mutation_protectShareToken(ctx, field)
case "updateUser": case "updateUser":
out.Values[i] = ec._Mutation_updateUser(ctx, field) out.Values[i] = ec._Mutation_updateUser(ctx, field)
case "createUser": case "createUser":
@ -6896,6 +7019,20 @@ func (ec *executionContext) _ShareToken(ctx context.Context, sel ast.SelectionSe
}) })
case "expire": case "expire":
out.Values[i] = ec._ShareToken_expire(ctx, field, obj) out.Values[i] = ec._ShareToken_expire(ctx, field, obj)
case "hasPassword":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._ShareToken_hasPassword(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "album": case "album":
field := field field := field
out.Concurrently(i, func() (res graphql.Marshaler) { out.Concurrently(i, func() (res graphql.Marshaler) {

View File

@ -3,9 +3,10 @@ package resolvers
import ( import (
"context" "context"
"database/sql" "database/sql"
"errors"
"time" "time"
"github.com/pkg/errors"
api "github.com/viktorstrate/photoview/api/graphql" api "github.com/viktorstrate/photoview/api/graphql"
"github.com/viktorstrate/photoview/api/graphql/auth" "github.com/viktorstrate/photoview/api/graphql/auth"
"github.com/viktorstrate/photoview/api/graphql/models" "github.com/viktorstrate/photoview/api/graphql/models"
@ -54,6 +55,11 @@ func (r *shareTokenResolver) Photo(ctx context.Context, obj *models.ShareToken)
return photo, nil return photo, nil
} }
func (r *shareTokenResolver) HasPassword(ctx context.Context, obj *models.ShareToken) (bool, error) {
hasPassword := obj.Password != nil
return hasPassword, nil
}
func (r *queryResolver) ShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error) { func (r *queryResolver) ShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error) {
row := r.Database.QueryRow("SELECT * FROM share_token WHERE value = ? AND (password = ? OR password IS NULL)", token, password) row := r.Database.QueryRow("SELECT * FROM share_token WHERE value = ? AND (password = ? OR password IS NULL)", token, password)
@ -131,15 +137,10 @@ func (r *mutationResolver) SharePhoto(ctx context.Context, photoID int, expire *
} }
rows.Close() rows.Close()
var hashed_password *string = nil hashed_password, err := hashSharePassword(password)
if password != nil {
hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(*password), 12)
if err != nil { if err != nil {
return nil, err return nil, err
} }
hashed_str := string(hashedPassBytes)
hashed_password = &hashed_str
}
token := utils.GenerateToken() token := utils.GenerateToken()
res, err := r.Database.Exec("INSERT INTO share_token (value, owner_id, expire, password, photo_id) VALUES (?, ?, ?, ?, ?)", token, user.UserID, expire, hashed_password, photoID) res, err := r.Database.Exec("INSERT INTO share_token (value, owner_id, expire, password, photo_id) VALUES (?, ?, ?, ?, ?)", token, user.UserID, expire, hashed_password, photoID)
@ -169,7 +170,59 @@ func (r *mutationResolver) DeleteShareToken(ctx context.Context, tokenValue stri
return nil, auth.ErrUnauthorized return nil, auth.ErrUnauthorized
} }
row := r.Database.QueryRow(` token, err := getUserToken(r.Database, user, tokenValue)
if err != nil {
return nil, err
}
if _, err := r.Database.Exec("DELETE FROM share_token WHERE token_id = ?", token.TokenID); err != nil {
return nil, errors.Wrapf(err, "Error occurred when trying to delete share token (%s) from database", tokenValue)
}
return token, nil
}
func (r *mutationResolver) ProtectShareToken(ctx context.Context, tokenValue string, password *string) (*models.ShareToken, error) {
user := auth.UserFromContext(ctx)
if user == nil {
return nil, auth.ErrUnauthorized
}
token, err := getUserToken(r.Database, user, tokenValue)
if err != nil {
return nil, err
}
hashed_password, err := hashSharePassword(password)
if err != nil {
return nil, err
}
_, err = r.Database.Exec("UPDATE share_token SET password = ? WHERE token_id = ?", hashed_password, token.TokenID)
if err != nil {
return nil, errors.Wrap(err, "Failed to update password for share token")
}
updatedToken := r.Database.QueryRow("SELECT * FROM share_token WHERE value = ?", tokenValue)
return models.NewShareTokenFromRow(updatedToken)
}
func hashSharePassword(password *string) (*string, error) {
var hashed_password *string = nil
if password != nil {
hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(*password), 12)
if err != nil {
return nil, err
}
hashed_str := string(hashedPassBytes)
hashed_password = &hashed_str
}
return hashed_password, nil
}
func getUserToken(db *sql.DB, user *models.User, tokenValue string) (*models.ShareToken, error) {
row := db.QueryRow(`
SELECT share_token.* FROM share_token, user WHERE SELECT share_token.* FROM share_token, user WHERE
share_token.value = ? AND share_token.value = ? AND
share_token.owner_id = user.user_id AND share_token.owner_id = user.user_id AND
@ -181,9 +234,5 @@ func (r *mutationResolver) DeleteShareToken(ctx context.Context, tokenValue stri
return nil, err return nil, err
} }
if _, err := r.Database.Exec("DELETE FROM share_token WHERE token_id = ?", token.TokenID); err != nil {
return nil, err
}
return token, nil return token, nil
} }

View File

@ -71,6 +71,8 @@ type Mutation {
sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken
"Delete a share token by it's token value" "Delete a share token by it's token value"
deleteShareToken(token: String!): ShareToken deleteShareToken(token: String!): ShareToken
"Set a password for a token, if null is passed for the password argument, the password will be cleared"
protectShareToken(token: String!, password: String): ShareToken
updateUser( updateUser(
id: Int! id: Int!
@ -132,6 +134,8 @@ type ShareToken {
owner: User! owner: User!
"Optional expire date" "Optional expire date"
expire: Time expire: Time
"Whether or not a password is needed to access the share"
hasPassword: Boolean!
"The album this token shares" "The album this token shares"
album: Album album: Album

90
ui/package-lock.json generated
View File

@ -5563,25 +5563,25 @@
"dependencies": { "dependencies": {
"abbrev": { "abbrev": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"optional": true "optional": true
}, },
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "resolved": "",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"optional": true "optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "resolved": "",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"optional": true "optional": true
}, },
"are-we-there-yet": { "are-we-there-yet": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "resolved": "",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5591,13 +5591,13 @@
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"optional": true "optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5613,25 +5613,25 @@
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "resolved": "",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"optional": true "optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"optional": true "optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "resolved": "",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"optional": true "optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"optional": true "optional": true
}, },
@ -5646,19 +5646,19 @@
}, },
"deep-extend": { "deep-extend": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "resolved": "",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"optional": true "optional": true
}, },
"delegates": { "delegates": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "resolved": "",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"optional": true "optional": true
}, },
"detect-libc": { "detect-libc": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "resolved": "",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
"optional": true "optional": true
}, },
@ -5673,13 +5673,13 @@
}, },
"fs.realpath": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"optional": true "optional": true
}, },
"gauge": { "gauge": {
"version": "2.7.4", "version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "resolved": "",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5709,13 +5709,13 @@
}, },
"has-unicode": { "has-unicode": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "resolved": "",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"optional": true "optional": true
}, },
"iconv-lite": { "iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5733,7 +5733,7 @@
}, },
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5749,13 +5749,13 @@
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "resolved": "",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"optional": true "optional": true
}, },
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "resolved": "",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5764,13 +5764,13 @@
}, },
"isarray": { "isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"optional": true "optional": true
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5842,7 +5842,7 @@
}, },
"nopt": { "nopt": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "resolved": "",
"integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5877,7 +5877,7 @@
}, },
"npmlog": { "npmlog": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "resolved": "",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5889,19 +5889,19 @@
}, },
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "resolved": "",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"optional": true "optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"optional": true "optional": true
}, },
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5910,19 +5910,19 @@
}, },
"os-homedir": { "os-homedir": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "resolved": "",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"optional": true "optional": true
}, },
"os-tmpdir": { "os-tmpdir": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "resolved": "",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"optional": true "optional": true
}, },
"osenv": { "osenv": {
"version": "0.1.5", "version": "0.1.5",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "resolved": "",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5932,7 +5932,7 @@
}, },
"path-is-absolute": { "path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"optional": true "optional": true
}, },
@ -5944,7 +5944,7 @@
}, },
"rc": { "rc": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "resolved": "",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5964,7 +5964,7 @@
}, },
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -5988,19 +5988,19 @@
}, },
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"optional": true "optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"optional": true "optional": true
}, },
"sax": { "sax": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "resolved": "",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"optional": true "optional": true
}, },
@ -6012,19 +6012,19 @@
}, },
"set-blocking": { "set-blocking": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "resolved": "",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"optional": true "optional": true
}, },
"signal-exit": { "signal-exit": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "resolved": "",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"optional": true "optional": true
}, },
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -6035,7 +6035,7 @@
}, },
"string_decoder": { "string_decoder": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -6044,7 +6044,7 @@
}, },
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -6053,7 +6053,7 @@
}, },
"strip-json-comments": { "strip-json-comments": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "resolved": "",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"optional": true "optional": true
}, },
@ -6074,13 +6074,13 @@
}, },
"util-deprecate": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"optional": true "optional": true
}, },
"wide-align": { "wide-align": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "resolved": "",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -6089,7 +6089,7 @@
}, },
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"optional": true "optional": true
}, },

View File

@ -1,8 +1,15 @@
import React from 'react' import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { useMutation, useQuery } from 'react-apollo' import { useMutation, useQuery } from 'react-apollo'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { Table, Button, Dropdown } from 'semantic-ui-react' import {
Table,
Button,
Dropdown,
Checkbox,
Input,
Icon,
} from 'semantic-ui-react'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
const sharePhotoQuery = gql` const sharePhotoQuery = gql`
@ -11,6 +18,7 @@ const sharePhotoQuery = gql`
id id
shares { shares {
token token
hasPassword
} }
} }
} }
@ -22,6 +30,7 @@ const shareAlbumQuery = gql`
id id
shares { shares {
token token
hasPassword
} }
} }
} }
@ -43,6 +52,15 @@ const addAlbumShareMutation = gql`
} }
` `
const protectShareMutation = gql`
mutation sidebarProtectShare($token: String!, $password: String) {
protectShareToken(token: $token, password: $password) {
token
hasPassword
}
}
`
const deleteShareMutation = gql` const deleteShareMutation = gql`
mutation sidebareDeleteShare($token: String!) { mutation sidebareDeleteShare($token: String!) {
deleteShareToken(token: $token) { deleteShareToken(token: $token) {
@ -51,6 +69,153 @@ const deleteShareMutation = gql`
} }
` `
const ShareItemMoreDropdown = ({ id, share, isPhoto }) => {
const query = isPhoto ? sharePhotoQuery : shareAlbumQuery
const [deleteShare, { loading: deleteShareLoading }] = useMutation(
deleteShareMutation,
{
refetchQueries: [{ query: query, variables: { id } }],
}
)
const [addingPassword, setAddingPassword] = useState(false)
const showPasswordInput = addingPassword || share.hasPassword
const [passwordInputValue, setPasswordInputValue] = useState(
share.hasPassword ? '**********' : ''
)
const [passwordHidden, setPasswordHidden] = useState(share.hasPassword)
const hidePassword = hide => {
setPasswordHidden(hide)
if (hide) {
setPasswordInputValue('**********')
}
}
const [setPassword, { loading: setPasswordLoading }] = useMutation(
protectShareMutation,
{
refetchQueries: [{ query: query, variables: { id } }],
onCompleted: data => {
console.log('data', data)
hidePassword(data.protectShareToken.hasPassword)
},
// refetchQueries: [{ query: query, variables: { id } }],
variables: {
token: share.token,
},
}
)
let addPasswordInput = null
if (showPasswordInput) {
const setPasswordEvent = event => {
if (!passwordHidden && passwordInputValue != '' && event.key == 'Enter') {
event.preventDefault()
setPassword({
variables: {
password: event.target.value,
},
})
}
}
addPasswordInput = (
<Input
disabled={setPasswordLoading}
loading={setPasswordLoading}
style={{ marginTop: 8, marginRight: 0, display: 'block' }}
onClick={e => e.stopPropagation()}
value={passwordInputValue}
type={passwordHidden ? 'password' : 'text'}
onKeyUp={setPasswordEvent}
onChange={event => {
hidePassword(false)
setPasswordInputValue(event.target.value)
}}
placeholder="Password"
icon={
<Icon
name={passwordHidden ? 'lock' : 'arrow right'}
link={!passwordHidden}
onClick={setPasswordEvent}
/>
}
/>
)
}
const checkboxClick = () => {
const enable = !showPasswordInput
setAddingPassword(enable)
if (!enable) {
setPassword({
variables: {
password: null,
},
})
setPasswordInputValue('')
}
}
// const [dropdownOpen, setDropdownOpen] = useState(false)
return (
<Dropdown
// onBlur={event => {
// console.log('Blur')
// }}
// onClick={() => setDropdownOpen(state => !state)}
// onClose={() => setDropdownOpen(false)}
// open={dropdownOpen}
button
text="More"
closeOnChange={false}
closeOnBlur={false}
>
<Dropdown.Menu>
<Dropdown.Item
onKeyDown={e => e.stopPropagation()}
onClick={e => {
e.stopPropagation()
checkboxClick()
}}
>
<Checkbox
label="Password"
onClick={e => e.stopPropagation()}
checked={showPasswordInput}
onChange={() => {
checkboxClick()
}}
/>
{addPasswordInput}
</Dropdown.Item>
<Dropdown.Item
text="Delete"
icon="delete"
disabled={deleteShareLoading}
onClick={() => {
deleteShare({
variables: {
token: share.token,
},
})
}}
/>
</Dropdown.Menu>
</Dropdown>
)
}
ShareItemMoreDropdown.propTypes = {
id: PropTypes.number.isRequired,
isPhoto: PropTypes.bool.isRequired,
share: PropTypes.object.isRequired,
}
const SidebarShare = ({ photo, album }) => { const SidebarShare = ({ photo, album }) => {
if ((!photo || !photo.id) && (!album || !album.id)) return null if ((!photo || !photo.id) && (!album || !album.id)) return null
if (!localStorage.getItem('token')) return null if (!localStorage.getItem('token')) return null
@ -71,13 +236,6 @@ const SidebarShare = ({ photo, album }) => {
variables: { id }, variables: { id },
}) })
const [deleteShare, { loading: deleteShareLoading }] = useMutation(
deleteShareMutation,
{
refetchQueries: [{ query: query, variables: { id } }],
}
)
const [sharePhoto, { loading: sharePhotoLoading }] = useMutation( const [sharePhoto, { loading: sharePhotoLoading }] = useMutation(
addShareMutation, addShareMutation,
{ {
@ -112,22 +270,7 @@ const SidebarShare = ({ photo, album }) => {
copy(`${location.origin}/share/${share.token}`) copy(`${location.origin}/share/${share.token}`)
}} }}
/> />
<Dropdown button text="More"> <ShareItemMoreDropdown share={share} id={id} isPhoto={isPhoto} />
<Dropdown.Menu>
<Dropdown.Item
text="Delete"
icon="delete"
disabled={deleteShareLoading}
onClick={() => {
deleteShare({
variables: {
token: share.token,
},
})
}}
/>
</Dropdown.Menu>
</Dropdown>
</Button.Group> </Button.Group>
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>